#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"
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: "
// 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
int reqid, userId;
osrfAppSession* session;
osrfMessage* omsg;
- jsonObject *param, *userObj, *newUserObj;
+ jsonObject *param, *userObj, *newUserObj = NULL;
userObj = jsonObjectGetKey( cacheObj, "userobj" );
userId = oilsFMGetObjectId( userObj );