LP#1468422 open-ils.auth API changes, in progress
authorBill Erickson <berickxx@gmail.com>
Thu, 23 Jul 2015 15:23:35 +0000 (11:23 -0400)
committerBill Erickson <berickxx@gmail.com>
Mon, 23 Nov 2015 16:17:05 +0000 (11:17 -0500)
Signed-off-by: Bill Erickson <berickxx@gmail.com>
Open-ILS/src/c-apps/oils_auth.c

index a0d52de..5f890d9 100644 (file)
@@ -6,6 +6,7 @@
 #include "openils/oils_utils.h"
 #include "openils/oils_constants.h"
 #include "openils/oils_event.h"
+#include <regex.h>
 
 #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(&regex, 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(&regex, identifier, 0, NULL, 0);
+        if (!regret) {
+            is_barcode = true;
+        } else if (regret != REG_NOMATCH) {
+            regerror(regret, &regex, 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 );