/** * Fetches the user object from the database and updates the user object in * the cache object, which then has to be re-inserted into the cache. * User object is retrieved inside a transaction to avoid replication issues. */ static int _oilsAuthReloadUser(jsonObject* cacheObj) { int reqid, userId; osrfAppSession* session; osrfMessage* omsg; jsonObject *param, *userObj, *newUserObj = NULL; userObj = jsonObjectGetKey( cacheObj, "userobj" ); userId = oilsFMGetObjectId( userObj ); session = osrfAppSessionClientInit( "open-ils.cstore" ); osrfAppSessionConnect(session); reqid = osrfAppSessionSendRequest(session, NULL, "open-ils.cstore.transaction.begin", 1); omsg = osrfAppSessionRequestRecv(session, reqid, 60); if(omsg) { osrfMessageFree(omsg); param = jsonNewNumberObject(userId); reqid = osrfAppSessionSendRequest(session, param, "open-ils.cstore.direct.actor.user.retrieve", 1); omsg = osrfAppSessionRequestRecv(session, reqid, 60); jsonObjectFree(param); if(omsg) { newUserObj = jsonObjectClone( osrfMessageGetResult(omsg) ); osrfMessageFree(omsg); reqid = osrfAppSessionSendRequest(session, NULL, "open-ils.cstore.transaction.rollback", 1); omsg = osrfAppSessionRequestRecv(session, reqid, 60); osrfMessageFree(omsg); } } osrfAppSessionFree(session); // calls disconnect internally if(newUserObj) { // ws_ou and wsid are ephemeral and need to be manually propagated // oilsFMSetString dupe()'s internally, no need to clone the string oilsFMSetString(newUserObj, "wsid", oilsFMGetStringConst(userObj, "wsid")); oilsFMSetString(newUserObj, "ws_ou", oilsFMGetStringConst(userObj, "ws_ou")); jsonObjectRemoveKey(cacheObj, "userobj"); // this also frees the old user object jsonObjectSetKey(cacheObj, "userobj", newUserObj); return 1; } osrfLogError(OSRF_LOG_MARK, "Error retrieving user %d from database", userId); return 0; }
int oilsAuthLogin(osrfMethodContext* ctx) { OSRF_METHOD_VERIFY_CONTEXT(ctx); const jsonObject* args = jsonObjectGetIndex(ctx->params, 0); const char* username = jsonObjectGetString(jsonObjectGetKeyConst(args, "username")); const char* identifier = jsonObjectGetString(jsonObjectGetKeyConst(args, "identifier")); 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* ewho = jsonObjectGetString(jsonObjectGetKeyConst(args, "agent")); const char* ws = (workstation) ? workstation : ""; if (!type) type = OILS_AUTH_STAFF; jsonObject* userObj = NULL; // free me oilsEvent* response = NULL; // free me /* Use __FILE__, harmless_line_number for creating * OILS_EVENT_AUTH_FAILED events (instead of OSRF_LOG_MARK) to avoid * giving away information about why an authentication attempt failed. */ int harmless_line_number = __LINE__; // translate a generic identifier into a username or barcode if necessary. if (identifier && !username && !barcode) { if (oilsAuthIdentIsBarcode(identifier, orgloc)) { barcode = identifier; } else { username = identifier; } } if (username) { barcode = NULL; // avoid superfluous identifiers userObj = oilsUtilsFetchUserByUsername(ctx, username); } else if (barcode) { userObj = oilsUtilsFetchUserByBarcode(ctx, barcode); } else { // not enough params return osrfAppRequestRespondException(ctx->session, ctx->request, "username/barcode and password required for method: %s", ctx->method->name); } if (!userObj) { // user not found. response = oilsNewEvent( __FILE__, harmless_line_number, OILS_EVENT_AUTH_FAILED); osrfAppRespondComplete(ctx, oilsEventToJSON(response)); oilsEventFree(response); // frees event JSON return 0; } long user_id = oilsFMGetObjectId(userObj); // username is freed when userObj is freed. // From here we can use the username as the generic identifier // since it's guaranteed to have a value. if (!username) username = oilsFMGetStringConst(userObj, "usrname"); // See if the user is allowed to login. jsonObject* params = jsonNewObject(NULL); jsonObjectSetKey(params, "user_id", jsonNewNumberObject(user_id)); jsonObjectSetKey(params,"org_unit", jsonNewNumberObject(orgloc)); jsonObjectSetKey(params, "login_type", jsonNewObject(type)); if (barcode) jsonObjectSetKey(params, "barcode", jsonNewObject(barcode)); jsonObject* authEvt = oilsUtilsQuickReqCtx( // freed after password test ctx, "open-ils.auth_internal", "open-ils.auth_internal.user.validate", params); jsonObjectFree(params); if (!authEvt) { // unknown error jsonObjectFree(userObj); return -1; } const char* authEvtCode = jsonObjectGetString(jsonObjectGetKey(authEvt, "textcode")); if (!strcmp(authEvtCode, OILS_EVENT_AUTH_FAILED)) { // Received the generic login failure event. osrfLogInfo(OSRF_LOG_MARK, "failed login: username=%s, barcode=%s, workstation=%s", username, (barcode ? barcode : "(none)"), ws); response = oilsNewEvent( __FILE__, harmless_line_number, OILS_EVENT_AUTH_FAILED); } if (!response && // user exists and is not barred, etc. !oilsAuthLoginVerifyPassword(ctx, user_id, username, password)) { // User provided the wrong password or is blocked from too // many previous login failures. response = oilsNewEvent( __FILE__, harmless_line_number, OILS_EVENT_AUTH_FAILED); osrfLogInfo(OSRF_LOG_MARK, "failed login: username=%s, barcode=%s, workstation=%s", username, (barcode ? barcode : "(none)"), ws ); } // Below here, we know the password check succeeded if no response // object is present. if (!response && ( !strcmp(authEvtCode, "PATRON_INACTIVE") || !strcmp(authEvtCode, "PATRON_CARD_INACTIVE"))) { // Patron and/or card is inactive but the correct password // was provided. Alert the caller to the inactive-ness. response = oilsNewEvent2( OSRF_LOG_MARK, authEvtCode, jsonObjectGetKey(authEvt, "payload") // cloned within Event ); } if (!response && strcmp(authEvtCode, OILS_EVENT_SUCCESS)) { // Validate API returned an unexpected non-success event. // To be safe, treat this as a generic login failure. response = oilsNewEvent( __FILE__, harmless_line_number, OILS_EVENT_AUTH_FAILED); } if (!response) { // password OK and no other events have prevented login completion. char* ewhat = "login"; if (0 == strcmp(ctx->method->name, "open-ils.auth.authenticate.verify")) { response = oilsNewEvent( OSRF_LOG_MARK, OILS_EVENT_SUCCESS ); ewhat = "verify"; } else { response = oilsAuthHandleLoginOK( ctx, userObj, username, type, orgloc, workstation); } oilsUtilsTrackUserActivity( ctx, oilsFMGetObjectId(userObj), ewho, ewhat, osrfAppSessionGetIngress() ); } // reply osrfAppRespondComplete(ctx, oilsEventToJSON(response)); // clean up oilsEventFree(response); jsonObjectFree(userObj); jsonObjectFree(authEvt); return 0; }
/** @brief Implement the session create 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: - "user_id" -- actor.usr (au) ID for the user to cache. - "org_unit" -- actor.org_unit (aou) ID representing the physical location / context used for timeout, etc. settings. - "login_type" -- login type (opac, staff, temp, persist) - "workstation" -- workstation name */ int oilsAuthInternalCreateSession(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* login_type = jsonObjectGetString(jsonObjectGetKeyConst(args, "login_type")); const char* workstation = jsonObjectGetString(jsonObjectGetKeyConst(args, "workstation")); 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; // fetch the user object jsonObject* idParam = jsonNewNumberStringObject(user_id); jsonObject* userObj = oilsUtilsCStoreReqCtx( ctx, "open-ils.cstore.direct.actor.user.retrieve", idParam); jsonObjectFree(idParam); if (!userObj) { return osrfAppRequestRespondException(ctx->session, ctx->request, "No user found with ID %s", user_id); } // If a workstation is defined, add the workstation info if (workstation) { response = oilsAuthVerifyWorkstation(ctx, userObj, workstation); if (response) { // invalid workstation. jsonObjectFree(userObj); osrfAppRespondComplete(ctx, oilsEventToJSON(response)); oilsEventFree(response); return 0; } else { // workstation OK. // The worksation org unit supersedes any org unit value // provided via the API. oilsAuthVerifyWorkstation() sets the // ws_ou value to the WS owning lib. A value is guaranteed. org_unit = atoi(oilsFMGetStringConst(userObj, "ws_ou")); } } else { // no workstation // For backwards compatibility, when no workstation is provided, use // the users's home org as its workstation org unit, regardless of // any API-level org unit value provided. const char* orgid = oilsFMGetStringConst(userObj, "home_ou"); oilsFMSetString(userObj, "ws_ou", orgid); // The context org unit defaults to the user's home library when // no workstation is used and no API-level value is provided. if (org_unit < 1) org_unit = atoi(orgid); } // determine the auth/cache timeout long timeout = oilsAuthGetTimeout(userObj, login_type, org_unit); char* string = va_list_to_string("%d.%ld.%ld", (long) getpid(), time(NULL), oilsFMGetObjectId(userObj)); char* authToken = md5sum(string); char* authKey = va_list_to_string( "%s%s", OILS_AUTH_CACHE_PRFX, authToken); oilsFMSetString(userObj, "passwd", ""); jsonObject* cacheObj = jsonParseFmt("{\"authtime\": %ld}", timeout); jsonObjectSetKey(cacheObj, "userobj", jsonObjectClone(userObj)); if( !strcmp(login_type, OILS_AUTH_PERSIST)) { // Add entries for endtime and reset_interval, so that we can gracefully // extend the session a bit if the user is active toward the end of the // timeout originally specified. time_t endtime = time( NULL ) + timeout; jsonObjectSetKey(cacheObj, "endtime", jsonNewNumberObject( (double) endtime )); // Reset interval is hard-coded for now, but if we ever want to make it // configurable, this is the place to do it: jsonObjectSetKey(cacheObj, "reset_interval", jsonNewNumberObject( (double) DEFAULT_RESET_INTERVAL)); } osrfCachePutObject(authKey, cacheObj, (time_t) timeout); jsonObjectFree(cacheObj); jsonObject* payload = jsonParseFmt( "{\"authtoken\": \"%s\", \"authtime\": %ld}", authToken, timeout); response = oilsNewEvent2(OSRF_LOG_MARK, OILS_EVENT_SUCCESS, payload); free(string); free(authToken); free(authKey); jsonObjectFree(payload); jsonObjectFree(userObj); osrfAppRespondComplete(ctx, oilsEventToJSON(response)); oilsEventFree(response); return 0; }