/** @brief Verify the password received from the client. @param ctx The method context. @param userObj An object from the database, representing the user. @param password An obfuscated password received from the client. @return 1 if the password is valid; 0 if it isn't; or -1 upon error. (None of the so-called "passwords" used here are in plaintext. All have been passed through at least one layer of hashing to obfuscate them.) Take the password from the user object. Append it to the username seed from memcache, as stored previously by a call to the init method. Take an md5 hash of the result. Then compare this hash to the password received from the client. In order for the two to match, other than by dumb luck, the client had to construct the password it passed in the same way. That means it neded to know not only the original password (either hashed or plaintext), but also the seed. The latter requirement means that the client process needs either to be the same process that called the init method or to receive the seed from the process that did so. */ static int oilsAuthVerifyPassword( const osrfMethodContext* ctx, int user_id, const char* identifier, const char* password, const char* nonce) { int verified = 0; // We won't be needing the seed again, remove it osrfCacheRemove("%s%s%s", OILS_AUTH_CACHE_PRFX, identifier, nonce); // Ask the DB to verify the user's password. // Here, the password is md5(md5(password) + salt) jsonObject* params = jsonParseFmt( // free "{\"from\":[\"actor.verify_passwd\",%d,\"main\",\"%s\"]}", user_id, password); jsonObject* verify_obj = // free oilsUtilsCStoreReq("open-ils.cstore.json_query", params); jsonObjectFree(params); if (verify_obj) { verified = oilsUtilsIsDBTrue( jsonObjectGetString( jsonObjectGetKeyConst( verify_obj, "actor.verify_passwd"))); jsonObjectFree(verify_obj); } char* countkey = va_list_to_string("%s%s%s", OILS_AUTH_CACHE_PRFX, identifier, OILS_AUTH_COUNT_SFFX ); jsonObject* countobject = osrfCacheGetObject( countkey ); if(countobject) { long failcount = (long) jsonObjectGetNumber( countobject ); if(failcount >= _oilsAuthBlockCount) { verified = 0; osrfLogInfo(OSRF_LOG_MARK, "oilsAuth found too many recent failures for '%s' : %i, " "forcing failure state.", identifier, failcount); } if(verified == 0) { failcount += 1; } jsonObjectSetNumber( countobject, failcount ); osrfCachePutObject( countkey, countobject, _oilsAuthBlockTimeout ); jsonObjectFree(countobject); } free(countkey); return verified; }
/** * Returns true if the provided password is correct. * Turn the password into the nested md5 hash required of migrated * passwords, then check the password in the DB. */ static int oilsAuthLoginCheckPassword(int user_id, const char* password) { growing_buffer* gb = buffer_init(33); // free me 1 char* salt = oilsAuthGetSalt(user_id); // free me 2 char* passhash = md5sum(password); // free me 3 buffer_add(gb, salt); // gb strdup's internally buffer_add(gb, passhash); free(salt); // free 2 free(passhash); // free 3 // salt + md5(password) passhash = buffer_release(gb); // free 1 ; free me 4 char* finalpass = md5sum(passhash); // free me 5 free(passhash); // free 4 jsonObject *arr = jsonNewObjectType(JSON_ARRAY); jsonObjectPush(arr, jsonNewObject("actor.verify_passwd")); jsonObjectPush(arr, jsonNewNumberObject((long) user_id)); jsonObjectPush(arr, jsonNewObject("main")); jsonObjectPush(arr, jsonNewObject(finalpass)); jsonObject *params = jsonNewObjectType(JSON_HASH); // free me 6 jsonObjectSetKey(params, "from", arr); free(finalpass); // free 5 jsonObject* verify_obj = // free oilsUtilsCStoreReq("open-ils.cstore.json_query", params); jsonObjectFree(params); // free 6 if (!verify_obj) return 0; // error int verified = oilsUtilsIsDBTrue( jsonObjectGetString( jsonObjectGetKeyConst(verify_obj, "actor.verify_passwd") ) ); jsonObjectFree(verify_obj); return verified; }
oilsEvent* oilsUtilsCheckPerms( int userid, int orgid, char* permissions[], int size ) { if (!permissions) return NULL; int i; // Check perms against the root org unit if no org unit is provided. if (orgid == -1) orgid = oilsUtilsGetRootOrgId(); for( i = 0; i < size && permissions[i]; i++ ) { oilsEvent* evt = NULL; char* perm = permissions[i]; jsonObject* params = jsonParseFmt( "{\"from\":[\"permission.usr_has_perm\",\"%d\",\"%s\",\"%d\"]}", userid, perm, orgid ); // Execute the query jsonObject* result = oilsUtilsCStoreReq( "open-ils.cstore.json_query", params); const jsonObject* hasPermStr = jsonObjectGetKeyConst(result, "permission.usr_has_perm"); if (!oilsUtilsIsDBTrue(jsonObjectGetString(hasPermStr))) { evt = oilsNewEvent3( OSRF_LOG_MARK, OILS_EVENT_PERM_FAILURE, perm, orgid); } jsonObjectFree(params); jsonObjectFree(result); // return first failed permission check. if (evt) return evt; } return NULL; // all perm checks succeeded }
/** @brief Implement the "complete" method. @param ctx The method context. @return -1 upon error; zero if successful, and if a STATUS message has been sent to the client to indicate completion; a positive integer if successful but no such STATUS message has been sent. Method parameters: - a hash with some combination of the following elements: - "username" - "barcode" - "password" (hashed with the cached seed; not plaintext) - "type" - "org" - "workstation" The password is required. Either a username or a barcode must also be present. Return to client: Intermediate authentication seed. Validate the password, using the username if available, or the barcode if not. The user must be active, and not barred from logging on. The barcode, if used for authentication, must be active as well. The workstation, if specified, must be valid. Upon deciding whether to allow the logon, return a corresponding event to the client. */ int oilsAuthComplete( osrfMethodContext* ctx ) { OSRF_METHOD_VERIFY_CONTEXT(ctx); const jsonObject* args = jsonObjectGetIndex(ctx->params, 0); const char* uname = jsonObjectGetString(jsonObjectGetKeyConst(args, "username")); const char* password = jsonObjectGetString(jsonObjectGetKeyConst(args, "password")); const char* type = jsonObjectGetString(jsonObjectGetKeyConst(args, "type")); int orgloc = (int) jsonObjectGetNumber(jsonObjectGetKeyConst(args, "org")); const char* workstation = jsonObjectGetString(jsonObjectGetKeyConst(args, "workstation")); const char* barcode = jsonObjectGetString(jsonObjectGetKeyConst(args, "barcode")); const char* ws = (workstation) ? workstation : ""; if( !type ) type = OILS_AUTH_STAFF; if( !( (uname || barcode) && password) ) { return osrfAppRequestRespondException( ctx->session, ctx->request, "username/barcode and password required for method: %s", ctx->method->name ); } oilsEvent* response = NULL; jsonObject* userObj = NULL; int card_active = 1; // boolean; assume active until proven otherwise // Fetch a row from the actor.usr table, by username if available, // or by barcode if not. if(uname) { userObj = oilsUtilsFetchUserByUsername( uname ); if( userObj && JSON_NULL == userObj->type ) { jsonObjectFree( userObj ); userObj = NULL; // username not found } } else if(barcode) { // Read from actor.card by barcode osrfLogInfo( OSRF_LOG_MARK, "Fetching user by barcode %s", barcode ); jsonObject* params = jsonParseFmt("{\"barcode\":\"%s\"}", barcode); jsonObject* card = oilsUtilsQuickReq( "open-ils.cstore", "open-ils.cstore.direct.actor.card.search", params ); jsonObjectFree( params ); if( card && card->type != JSON_NULL ) { // Determine whether the card is active char* card_active_str = oilsFMGetString( card, "active" ); card_active = oilsUtilsIsDBTrue( card_active_str ); free( card_active_str ); // Look up the user who owns the card char* userid = oilsFMGetString( card, "usr" ); jsonObjectFree( card ); params = jsonParseFmt( "[%s]", userid ); free( userid ); userObj = oilsUtilsQuickReq( "open-ils.cstore", "open-ils.cstore.direct.actor.user.retrieve", params ); jsonObjectFree( params ); if( userObj && JSON_NULL == userObj->type ) { // user not found (shouldn't happen, due to foreign key) jsonObjectFree( userObj ); userObj = NULL; } } } if(!userObj) { response = oilsNewEvent( OSRF_LOG_MARK, OILS_EVENT_AUTH_FAILED ); osrfLogInfo(OSRF_LOG_MARK, "failed login: username=%s, barcode=%s, workstation=%s", uname, (barcode ? barcode : "(none)"), ws ); osrfAppRespondComplete( ctx, oilsEventToJSON(response) ); oilsEventFree(response); return 0; // No such user } // Such a user exists. Now see if he or she has the right credentials. int passOK = -1; if(uname) passOK = oilsAuthVerifyPassword( ctx, userObj, uname, password ); else if (barcode) passOK = oilsAuthVerifyPassword( ctx, userObj, barcode, password ); if( passOK < 0 ) { jsonObjectFree(userObj); return passOK; } // See if the account is active char* active = oilsFMGetString(userObj, "active"); if( !oilsUtilsIsDBTrue(active) ) { if( passOK ) response = oilsNewEvent( OSRF_LOG_MARK, "PATRON_INACTIVE" ); else response = oilsNewEvent( OSRF_LOG_MARK, OILS_EVENT_AUTH_FAILED ); osrfAppRespondComplete( ctx, oilsEventToJSON(response) ); oilsEventFree(response); jsonObjectFree(userObj); free(active); return 0; } free(active); osrfLogInfo( OSRF_LOG_MARK, "Fetching card by barcode %s", barcode ); if( !card_active ) { osrfLogInfo( OSRF_LOG_MARK, "barcode %s is not active, returning event", barcode ); response = oilsNewEvent( OSRF_LOG_MARK, "PATRON_CARD_INACTIVE" ); osrfAppRespondComplete( ctx, oilsEventToJSON( response ) ); oilsEventFree( response ); jsonObjectFree( userObj ); return 0; } // See if the user is even allowed to log in if( oilsAuthCheckLoginPerm( ctx, userObj, type ) == -1 ) { jsonObjectFree(userObj); return 0; } // If a workstation is defined, add the workstation info if( workstation != NULL ) { osrfLogDebug(OSRF_LOG_MARK, "Workstation is %s", workstation); response = oilsAuthVerifyWorkstation( ctx, userObj, workstation ); if(response) { jsonObjectFree(userObj); osrfAppRespondComplete( ctx, oilsEventToJSON(response) ); oilsEventFree(response); return 0; } } else { // Otherwise, use the home org as the workstation org on the user char* orgid = oilsFMGetString(userObj, "home_ou"); oilsFMSetString(userObj, "ws_ou", orgid); free(orgid); } char* freeable_uname = NULL; if(!uname) { uname = freeable_uname = oilsFMGetString( userObj, "usrname" ); } if( passOK ) { response = oilsAuthHandleLoginOK( userObj, uname, type, orgloc, workstation ); } else { response = oilsNewEvent( OSRF_LOG_MARK, OILS_EVENT_AUTH_FAILED ); osrfLogInfo(OSRF_LOG_MARK, "failed login: username=%s, barcode=%s, workstation=%s", uname, (barcode ? barcode : "(none)"), ws ); } jsonObjectFree(userObj); osrfAppRespondComplete( ctx, oilsEventToJSON(response) ); oilsEventFree(response); if(freeable_uname) free(freeable_uname); return 0; }
int oilsAuthInternalValidate(osrfMethodContext* ctx) { OSRF_METHOD_VERIFY_CONTEXT(ctx); const jsonObject* args = jsonObjectGetIndex(ctx->params, 0); const char* user_id = jsonObjectGetString(jsonObjectGetKeyConst(args, "user_id")); const char* barcode = jsonObjectGetString(jsonObjectGetKeyConst(args, "barcode")); const char* login_type = jsonObjectGetString(jsonObjectGetKeyConst(args, "login_type")); int org_unit = jsonObjectGetNumber(jsonObjectGetKeyConst(args, "org_unit")); if ( !(user_id && login_type) ) { return osrfAppRequestRespondException( ctx->session, ctx->request, "Missing parameters for method: %s", ctx->method->name ); } oilsEvent* response = NULL; jsonObject *userObj = NULL, *params = NULL; char* tmp_str = NULL; int user_exists = 0, user_active = 0, user_barred = 0, user_deleted = 0; // Confirm user exists, active=true, barred=false, deleted=false params = jsonNewNumberStringObject(user_id); userObj = oilsUtilsCStoreReqCtx( ctx, "open-ils.cstore.direct.actor.user.retrieve", params); jsonObjectFree(params); if (userObj && userObj->type != JSON_NULL) { user_exists = 1; tmp_str = oilsFMGetString(userObj, "active"); user_active = oilsUtilsIsDBTrue(tmp_str); free(tmp_str); tmp_str = oilsFMGetString(userObj, "barred"); user_barred = oilsUtilsIsDBTrue(tmp_str); free(tmp_str); tmp_str = oilsFMGetString(userObj, "deleted"); user_deleted = oilsUtilsIsDBTrue(tmp_str); free(tmp_str); } if (!user_exists || user_barred || user_deleted) { response = oilsNewEvent(OILS_LOG_MARK_SAFE, OILS_EVENT_AUTH_FAILED); } if (!response && !user_active) { // In some cases, it's useful for the caller to know if the // patron was unable to login becuase the account is inactive. // Return a specific event for this. response = oilsNewEvent(OILS_LOG_MARK_SAFE, "PATRON_INACTIVE"); } if (!response && barcode) { // Caller provided a barcode. Ensure it exists and is active. int card_ok = 0; params = jsonParseFmt("{\"barcode\":\"%s\"}", barcode); jsonObject* card = oilsUtilsCStoreReqCtx( ctx, "open-ils.cstore.direct.actor.card.search", params); jsonObjectFree(params); if (card && card->type != JSON_NULL) { tmp_str = oilsFMGetString(card, "active"); card_ok = oilsUtilsIsDBTrue(tmp_str); free(tmp_str); } jsonObjectFree(card); // card=NULL OK here. if (!card_ok) { response = oilsNewEvent( OILS_LOG_MARK_SAFE, "PATRON_CARD_INACTIVE"); } } // XXX: login permission checks are always global (see // oilsAuthCheckLoginPerm()). No need to extract the // workstation org unit here. if (!response) { // Still OK // Confirm user has permission to login w/ the requested type. response = oilsAuthCheckLoginPerm( ctx, atoi(user_id), org_unit, login_type); } if (!response) { // No tests failed. Return SUCCESS. response = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_SUCCESS); } jsonObjectFree(userObj); // userObj=NULL OK here. osrfAppRespondComplete(ctx, oilsEventToJSON(response)); oilsEventFree(response); return 0; }