From d01a54ffb8517d970c7b091a6475518a75d33ebe Mon Sep 17 00:00:00 2001 From: Bill Erickson Date: Thu, 23 Jul 2015 11:23:35 -0400 Subject: [PATCH] LP#1468422 open-ils.auth API changes, in progress Signed-off-by: Bill Erickson --- Open-ILS/src/c-apps/oils_auth.c | 289 ++++++++++++++++++++++++++-------------- 1 file changed, 186 insertions(+), 103 deletions(-) diff --git a/Open-ILS/src/c-apps/oils_auth.c b/Open-ILS/src/c-apps/oils_auth.c index a0d52de605..5f890d904c 100644 --- a/Open-ILS/src/c-apps/oils_auth.c +++ b/Open-ILS/src/c-apps/oils_auth.c @@ -6,6 +6,7 @@ #include "openils/oils_utils.h" #include "openils/oils_constants.h" #include "openils/oils_event.h" +#include #define OILS_AUTH_CACHE_PRFX "oils_auth_" #define OILS_AUTH_COUNT_SFFX "_count" @@ -52,6 +53,20 @@ int osrfAppInitialize() { osrfAppRegisterMethod( MODULENAME, + "open-ils.auth.authenticate.init.barcode", + "oilsAuthInitBarcode", + "Start the authentication process using a patron barcode and return " + "the intermediate authentication seed. PARAMS(barcode)", 1, 0); + + osrfAppRegisterMethod( + MODULENAME, + "open-ils.auth.authenticate.init.username", + "oilsAuthInitUsername", + "Start the authentication process using a patron username and return " + "the intermediate authentication seed. PARAMS(username)", 1, 0); + + osrfAppRegisterMethod( + MODULENAME, "open-ils.auth.authenticate.complete", "oilsAuthComplete", "Completes the authentication process. Returns an object like so: " @@ -146,176 +161,244 @@ int osrfAppChildInit() { // free() response static char* oilsAuthGetSalt(int userId) { - char* saltString = NULL; + char* salt_str = NULL; jsonObject* params = jsonParseFmt( "{\"from\":[\"actor.get_salt\",%d,\"%s\"]}", userId, "main"); - jsonObject* saltObj = oilsUtilsQuickReq( + jsonObject* salt_obj = oilsUtilsQuickReq( "open-ils.cstore", "open-ils.cstore.json_query", params); jsonObjectFree(params); - if (saltObj) { + if (salt_obj) { - if (saltObj->type != JSON_NULL) { + if (salt_obj->type != JSON_NULL) { - const char* saltValue = jsonObjectGetString( - jsonObjectGetKeyConst(saltObj, "get_salt")); + const char* salt_val = jsonObjectGetString( + jsonObjectGetKeyConst(salt_obj, "get_salt")); - // caller expects a free-able string - if (saltValue) { saltString = strdup(saltValue); } + // caller expects a free-able string, could be NULL. + if (salt_val) { salt_str = strdup(salt_val); } } - jsonObjectFree(saltObj); + jsonObjectFree(salt_obj); } - return saltString; + return salt_str; } // ident is either a username or barcode // Returns the init seed -> requires free(); static char* oilsAuthBuildInitCache( - int userId, const char* ident, const char* ident_type, const char* nonce) { + int user_id, const char* ident, const char* ident_type, const char* nonce) { - char* cachekey = va_list_to_string( + char* cache_key = va_list_to_string( "%s%s%s", OILS_AUTH_CACHE_PRFX, ident, nonce); - char* countkey = va_list_to_string( + char* count_key = va_list_to_string( "%s%s%s", OILS_AUTH_CACHE_PRFX, ident, OILS_AUTH_COUNT_SFFX); - char* seed = oilsAuthGetSalt(userId); + char* auth_seed = oilsAuthGetSalt(user_id); - jsonObject* seedobject = jsonParseFmt( + jsonObject* seed_object = jsonParseFmt( "{\"%s\":\"%s\",\"user_id\":%d,\"seed\":\"%s\"}", - ident_type, ident, userId, seed); + ident_type, ident, user_id, auth_seed); - jsonObject* countobject = osrfCacheGetObject(countkey); - if(!countobject) { - countobject = jsonNewNumberObject((double) 0); + jsonObject* count_object = osrfCacheGetObject(count_key); + if(!count_object) { + count_object = jsonNewNumberObject((double) 0); } - osrfCachePutObject(cachekey, seedobject, _oilsAuthSeedTimeout); - osrfCachePutObject(countkey, countobject, _oilsAuthBlockTimeout); + osrfCachePutObject(cache_key, seed_object, _oilsAuthSeedTimeout); + osrfCachePutObject(count_key, count_object, _oilsAuthBlockTimeout); osrfLogDebug(OSRF_LOG_MARK, - "oilsAuthInit(): has seed %s and key %s", seed, cachekey); + "oilsAuthInit(): has seed %s and key %s", auth_seed, cache_key); - free(cachekey); - free(countkey); - jsonObjectFree(countobject); - jsonObjectFree(seedobject); + free(cache_key); + free(count_key); + jsonObjectFree(count_object); + jsonObjectFree(seed_object); - return seed; + return auth_seed; } +static int oilsAuthInitUsernameHandler( + osrfMethodContext* ctx, const char* username, const char* nonce) { -/** - @brief Implement the "init" method. - @param ctx The method context. - @return Zero if successful, or -1 if not. - - Method parameters: - - username - - nonce : optional login seed (string) provided by the caller which - is added to the auth init cache to differentiate between logins - using the same username and thus avoiding cache collisions for - near-simultaneous logins. + jsonObject* resp = NULL; // free + jsonObject* user_obj = oilsUtilsFetchUserByUsername(username); // free - Return to client: Intermediate authentication seed. + if (user_obj) { - Combine the username with a timestamp and process ID, and take an md5 hash of the result. - Store the hash in memcache, with a key based on the username. Then return the hash to - the client. + if (JSON_NULL == user_obj->type) { // user not found + resp = jsonNewObject("x"); - However: if the username includes one or more embedded blank spaces, return a dummy - hash without storing anything in memcache. The dummy will never match a stored hash, so - any attempt to authenticate with it will fail. -*/ -int oilsAuthInit( osrfMethodContext* ctx ) { - OSRF_METHOD_VERIFY_CONTEXT(ctx); + } else { + char* seed = oilsAuthBuildInitCache( + oilsFMGetObjectId(user_obj), username, "username", nonce); + resp = jsonNewObject(seed); + free(seed); + } - char* username = jsonObjectToSimpleString( jsonObjectGetIndex(ctx->params, 0) ); - const char* nonce = jsonObjectGetString(jsonObjectGetIndex(ctx->params, 1)); - if (!nonce) nonce = ""; + jsonObjectFree(user_obj); - if( username ) { + } else { + resp = jsonNewObject("x"); + } - jsonObject* resp; + osrfAppRespondComplete(ctx, resp); + jsonObjectFree(resp); + return 0; +} - if( strchr( username, ' ' ) ) { +// open-ils.auth.authenticate.init.username +int oilsAuthInitUsername(osrfMethodContext* ctx) { + OSRF_METHOD_VERIFY_CONTEXT(ctx); - // Embedded spaces are not allowed in a username. Use "x" as a dummy - // seed. It will never be a valid seed because 'x' is not a hex digit. - resp = jsonNewObject( "x" ); + char* username = // free + jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, 0)); + const char* nonce = + jsonObjectGetString(jsonObjectGetIndex(ctx->params, 1)); - } else { + if (!nonce) nonce = ""; + if (!username) return -1; - // Build a key and a seed; store them in memcache. - char* key = va_list_to_string( "%s%s%s", OILS_AUTH_CACHE_PRFX, username, nonce ); - char* countkey = va_list_to_string( "%s%s%s", OILS_AUTH_CACHE_PRFX, username, OILS_AUTH_COUNT_SFFX ); - char* seed = md5sum( "%d.%ld.%s.%s", (int) time(NULL), (long) getpid(), username, nonce ); - jsonObject* countobject = osrfCacheGetObject( countkey ); - if(!countobject) { - countobject = jsonNewNumberObject( (double) 0 ); - } - osrfCachePutString( key, seed, _oilsAuthSeedTimeout ); - osrfCachePutObject( countkey, countobject, _oilsAuthBlockTimeout ); + int resp = oilsAuthInitUsernameHandler(ctx, username, nonce); - osrfLogDebug( OSRF_LOG_MARK, "oilsAuthInit(): has seed %s and key %s", seed, key ); + free(username); + return resp; +} - // Build a returnable object containing the seed. - resp = jsonNewObject( seed ); +static int oilsAuthInitBarcodeHandler( + osrfMethodContext* ctx, const char* barcode, const char* nonce) { - free( seed ); - free( key ); - free( countkey ); - jsonObjectFree( countobject ); - } + jsonObject* resp = NULL; // free + jsonObject* user_obj = oilsUtilsFetchUserByBarcode(barcode); // free - // Return the seed to the client. - osrfAppRespondComplete( ctx, resp ); + if (user_obj) { + if (JSON_NULL == user_obj->type) { // not found + resp = jsonNewObject("x"); + } else { + char* seed = oilsAuthBuildInitCache( + oilsFMGetObjectId(user_obj), barcode, "barcode", nonce); + resp = jsonNewObject(seed); + free(seed); + } - jsonObjectFree(resp); - free(username); - return 0; - } + jsonObjectFree(user_obj); + } else { + resp = jsonNewObject("x"); + } - return -1; // Error: no username parameter + osrfAppRespondComplete(ctx, resp); + jsonObjectFree(resp); + return 0; } -int oilsAuthInitUsername(osrfMethodContext* ctx) { + +// open-ils.auth.authenticate.init.barcode +int oilsAuthInitBarcode(osrfMethodContext* ctx) { OSRF_METHOD_VERIFY_CONTEXT(ctx); - char* username = jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, 0)); - const char* nonce = jsonObjectGetString(jsonObjectGetIndex(ctx->params, 1)); + char* barcode = // free + jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, 0)); + const char* nonce = + jsonObjectGetString(jsonObjectGetIndex(ctx->params, 1)); + if (!nonce) nonce = ""; - if (!username) return -1; + if (!barcode) return -1; - jsonObject* resp = NULL; - jsonObject* userObj = NULL; + int resp = oilsAuthInitBarcodeHandler(ctx, barcode, nonce); - userObj = oilsUtilsFetchUserByUsername(username); + free(barcode); + return resp; +} - if (userObj && JSON_NULL == userObj->type) { // user not found - jsonObjectFree(userObj); - userObj = NULL; - resp = jsonNewObject("x"); +// returns true if the provided identifier matches the barcode regex. +static int oilsAuthIdentIsBarcode(const char* identifier) { + + // before we can fetch the barcode regex unit setting, + // first determine what the root org unit ID is. + // TODO: add an org_unit param to the .init API for future use? + + jsonObject *params = jsonParse("{\"parent_ou\":null}"); + jsonObject *org_unit_id = oilsUtilsCStoreReq( + "open-ils.cstore.direct.actor.org_unit.id_list", params); + jsonObjectFree(params); + + char* bc_regex = oilsUtilsFetchOrgSetting( + (int) jsonObjectGetNumber(org_unit_id), "opac.barcode_regex"); + jsonObjectFree(org_unit_id); + + if (!bc_regex) { + // if no regex is set, assume any identifier starting + // with a number is a barcode. + bc_regex = strdup("^\\d"); // dupe for later free'ing + } + + regex_t regex; + int is_barcode = 0; + int regret; + char regerr[100]; + regret = regcomp(®ex, bc_regex, 0); + if (regret) { + osrfLogError(OSRF_LOG_MARK, + "Cannot compile barcode regex: %s", bc_regex); } else { - char* seed = oilsAuthBuildInitCache( - oilsFMGetObjectId(userObj), username, "username", nonce); - resp = jsonNewObject(seed); - free(seed); + regret = regexec(®ex, identifier, 0, NULL, 0); + if (!regret) { + is_barcode = true; + } else if (regret != REG_NOMATCH) { + regerror(regret, ®ex, regerr, sizeof(regerr)); + osrfLogError(OSRF_LOG_MARK, + "Error processing regex %s on %s : %s", + bc_regex, identifier, regerr); + } } - osrfAppRespondComplete(ctx, resp); - jsonObjectFree(resp); - free(username); - return 0; + free(bc_regex); + return is_barcode; } +/** + @brief Implement the "init" method. + @param ctx The method context. + @return Zero if successful, or -1 if not. + + Method parameters: + - username + - nonce : optional login seed (string) provided by the caller which + is added to the auth init cache to differentiate between logins + using the same username and thus avoiding cache collisions for + near-simultaneous logins. + + Return to client: Intermediate authentication seed. +*/ +int oilsAuthInit(osrfMethodContext* ctx) { + OSRF_METHOD_VERIFY_CONTEXT(ctx); + int resp = 0; + + char* identifier = // free + jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, 0)); + const char* nonce = + jsonObjectGetString(jsonObjectGetIndex(ctx->params, 1)); + + if (!nonce) nonce = ""; + if (!identifier) return -1; // we need an identifier + + if (oilsAuthIdentIsBarcode(identifier)) { + resp = oilsAuthInitBarcodeHandler(ctx, identifier, nonce); + } else { + resp = oilsAuthInitUsernameHandler(ctx, identifier, nonce); + } + + free(identifier); + return resp; +} /** Verifies that the user has permission to login with the @@ -914,7 +997,7 @@ static int _oilsAuthReloadUser(jsonObject* cacheObj) { int reqid, userId; osrfAppSession* session; osrfMessage* omsg; - jsonObject *param, *userObj, *newUserObj; + jsonObject *param, *userObj, *newUserObj = NULL; userObj = jsonObjectGetKey( cacheObj, "userobj" ); userId = oilsFMGetObjectId( userObj ); -- 2.11.0