/** @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; }
/** @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; }