1 #include "opensrf/osrf_app_session.h"
2 #include "opensrf/osrf_application.h"
3 #include "opensrf/osrf_settings.h"
4 #include "opensrf/osrf_json.h"
5 #include "opensrf/log.h"
6 #include "openils/oils_utils.h"
7 #include "openils/oils_constants.h"
8 #include "openils/oils_event.h"
11 #define OILS_AUTH_CACHE_PRFX "oils_auth_"
12 #define OILS_AUTH_COUNT_SFFX "_count"
14 #define MODULENAME "open-ils.auth"
16 #define OILS_AUTH_OPAC "opac"
17 #define OILS_AUTH_STAFF "staff"
18 #define OILS_AUTH_TEMP "temp"
19 #define OILS_AUTH_PERSIST "persist"
21 // Default time for extending a persistent session: ten minutes
22 #define DEFAULT_RESET_INTERVAL 10 * 60
24 int osrfAppInitialize();
25 int osrfAppChildInit();
27 static long _oilsAuthSeedTimeout = 0;
28 static long _oilsAuthBlockTimeout = 0;
29 static long _oilsAuthBlockCount = 0;
33 @brief Initialize the application by registering functions for method calls.
34 @return Zero in all cases.
36 int osrfAppInitialize() {
38 osrfLogInfo(OSRF_LOG_MARK, "Initializing Auth Server...");
40 /* load and parse the IDL */
41 if (!oilsInitIDL(NULL)) return 1; /* return non-zero to indicate error */
43 osrfAppRegisterMethod(
45 "open-ils.auth.authenticate.init",
47 "Start the authentication process and returns the intermediate authentication seed"
48 " PARAMS( username )", 1, 0 );
50 osrfAppRegisterMethod(
52 "open-ils.auth.authenticate.init.barcode",
53 "oilsAuthInitBarcode",
54 "Start the authentication process using a patron barcode and return "
55 "the intermediate authentication seed. PARAMS(barcode)", 1, 0);
57 osrfAppRegisterMethod(
59 "open-ils.auth.authenticate.init.username",
60 "oilsAuthInitUsername",
61 "Start the authentication process using a patron username and return "
62 "the intermediate authentication seed. PARAMS(username)", 1, 0);
64 osrfAppRegisterMethod(
66 "open-ils.auth.authenticate.complete",
68 "Completes the authentication process. Returns an object like so: "
69 "{authtoken : <token>, authtime:<time>}, where authtoken is the login "
70 "token and authtime is the number of seconds the session will be active"
71 "PARAMS(username, md5sum( seed + md5sum( password ) ), type, org_id ) "
72 "type can be one of 'opac','staff', or 'temp' and it defaults to 'staff' "
73 "org_id is the location at which the login should be considered "
74 "active for login timeout purposes", 1, 0 );
76 osrfAppRegisterMethod(
78 "open-ils.auth.login",
80 "Request an authentication token logging in with username or "
81 "barcode. Parameter is a keyword arguments hash with keys "
82 "username, barcode, identifier, password, type, org, workstation, "
83 "agent. The 'identifier' option is used when the caller wants the "
84 "API to determine if an identifier string is a username or barcode "
85 "using the barcode format configuration.",
88 osrfAppRegisterMethod(
90 "open-ils.auth.authenticate.verify",
92 "Verifies the user provided a valid username and password."
93 "Params and are the same as open-ils.auth.authenticate.complete."
94 "Returns SUCCESS event on success, failure event on failure", 1, 0);
97 osrfAppRegisterMethod(
99 "open-ils.auth.session.retrieve",
100 "oilsAuthSessionRetrieve",
101 "Pass in the auth token and this retrieves the user object. By "
102 "default, the auth timeout is reset when this call is made. If "
103 "a second non-zero parameter is passed, the auth timeout info is "
104 "returned to the caller along with the user object. If a 3rd "
105 "non-zero parameter is passed, the auth timeout will not be reset."
106 "Returns the user object (password blanked) for the given login session "
107 "PARAMS( authToken[, returnTime[, doNotResetSession]] )", 1, 0 );
109 osrfAppRegisterMethod(
111 "open-ils.auth.session.delete",
112 "oilsAuthSessionDelete",
113 "Destroys the given login session "
114 "PARAMS( authToken )", 1, 0 );
116 osrfAppRegisterMethod(
118 "open-ils.auth.session.reset_timeout",
119 "oilsAuthResetTimeout",
120 "Resets the login timeout for the given session "
121 "Returns an ILS Event with payload = session_timeout of session "
122 "if found, otherwise returns the NO_SESSION event"
123 "PARAMS( authToken )", 1, 0 );
125 if(!_oilsAuthSeedTimeout) { /* Load the default timeouts */
127 jsonObject* value_obj;
129 value_obj = osrf_settings_host_value_object(
130 "/apps/open-ils.auth/app_settings/auth_limits/seed" );
131 _oilsAuthSeedTimeout = oilsUtilsIntervalToSeconds( jsonObjectGetString( value_obj ));
132 jsonObjectFree(value_obj);
133 if( -1 == _oilsAuthSeedTimeout ) {
134 osrfLogWarning( OSRF_LOG_MARK, "Invalid timeout for Auth Seeds - Using 30 seconds" );
135 _oilsAuthSeedTimeout = 30;
138 value_obj = osrf_settings_host_value_object(
139 "/apps/open-ils.auth/app_settings/auth_limits/block_time" );
140 _oilsAuthBlockTimeout = oilsUtilsIntervalToSeconds( jsonObjectGetString( value_obj ));
141 jsonObjectFree(value_obj);
142 if( -1 == _oilsAuthBlockTimeout ) {
143 osrfLogWarning( OSRF_LOG_MARK, "Invalid timeout for Blocking Timeout - Using 3x Seed" );
144 _oilsAuthBlockTimeout = _oilsAuthSeedTimeout * 3;
147 value_obj = osrf_settings_host_value_object(
148 "/apps/open-ils.auth/app_settings/auth_limits/block_count" );
149 _oilsAuthBlockCount = oilsUtilsIntervalToSeconds( jsonObjectGetString( value_obj ));
150 jsonObjectFree(value_obj);
151 if( -1 == _oilsAuthBlockCount ) {
152 osrfLogWarning( OSRF_LOG_MARK, "Invalid count for Blocking - Using 10" );
153 _oilsAuthBlockCount = 10;
156 osrfLogInfo(OSRF_LOG_MARK, "Set auth limits: "
157 "seed => %ld : block_timeout => %ld : block_count => %ld",
158 _oilsAuthSeedTimeout, _oilsAuthBlockTimeout, _oilsAuthBlockCount );
165 @brief Dummy placeholder for initializing a server drone.
167 There is nothing to do, so do nothing.
169 int osrfAppChildInit() {
174 static char* oilsAuthGetSalt(int user_id) {
175 char* salt_str = NULL;
177 jsonObject* params = jsonParseFmt( // free
178 "{\"from\":[\"actor.get_salt\",%d,\"%s\"]}", user_id, "main");
180 jsonObject* salt_obj = // free
181 oilsUtilsCStoreReq("open-ils.cstore.json_query", params);
183 jsonObjectFree(params);
187 if (salt_obj->type != JSON_NULL) {
189 const char* salt_val = jsonObjectGetString(
190 jsonObjectGetKeyConst(salt_obj, "actor.get_salt"));
192 // caller expects a free-able string, could be NULL.
193 if (salt_val) { salt_str = strdup(salt_val); }
196 jsonObjectFree(salt_obj);
202 // ident is either a username or barcode
203 // Returns the init seed -> requires free();
204 static char* oilsAuthBuildInitCache(
205 int user_id, const char* ident, const char* ident_type, const char* nonce) {
207 char* cache_key = va_list_to_string(
208 "%s%s%s", OILS_AUTH_CACHE_PRFX, ident, nonce);
210 char* count_key = va_list_to_string(
211 "%s%s%s", OILS_AUTH_CACHE_PRFX, ident, OILS_AUTH_COUNT_SFFX);
215 // user does not exist. Use a dummy seed
216 auth_seed = strdup("x");
218 auth_seed = oilsAuthGetSalt(user_id);
221 jsonObject* seed_object = jsonParseFmt(
222 "{\"%s\":\"%s\",\"user_id\":%d,\"seed\":\"%s\"}",
223 ident_type, ident, user_id, auth_seed);
225 jsonObject* count_object = osrfCacheGetObject(count_key);
227 count_object = jsonNewNumberObject((double) 0);
230 osrfCachePutObject(cache_key, seed_object, _oilsAuthSeedTimeout);
233 // Only track login counts for existing users, since a
234 // login for a nonexistent user will never succeed anyway.
235 osrfCachePutObject(count_key, count_object, _oilsAuthBlockTimeout);
238 osrfLogDebug(OSRF_LOG_MARK,
239 "oilsAuthInit(): has seed %s and key %s", auth_seed, cache_key);
243 jsonObjectFree(count_object);
244 jsonObjectFree(seed_object);
249 static int oilsAuthInitUsernameHandler(
250 osrfMethodContext* ctx, const char* username, const char* nonce) {
252 osrfLogInfo(OSRF_LOG_MARK,
253 "User logging in with username %s", username);
256 jsonObject* resp = NULL; // free
257 jsonObject* user_obj = oilsUtilsFetchUserByUsername(ctx, username); // free
259 if (user_obj && user_obj->type != JSON_NULL)
260 user_id = oilsFMGetObjectId(user_obj);
262 jsonObjectFree(user_obj); // NULL OK
264 char* seed = oilsAuthBuildInitCache(user_id, username, "username", nonce);
265 resp = jsonNewObject(seed);
268 osrfAppRespondComplete(ctx, resp);
269 jsonObjectFree(resp);
273 // open-ils.auth.authenticate.init.username
274 int oilsAuthInitUsername(osrfMethodContext* ctx) {
275 OSRF_METHOD_VERIFY_CONTEXT(ctx);
277 char* username = // free
278 jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, 0));
280 jsonObjectGetString(jsonObjectGetIndex(ctx->params, 1));
282 if (!nonce) nonce = "";
283 if (!username) return -1;
285 int resp = oilsAuthInitUsernameHandler(ctx, username, nonce);
291 static int oilsAuthInitBarcodeHandler(
292 osrfMethodContext* ctx, const char* barcode, const char* nonce) {
294 osrfLogInfo(OSRF_LOG_MARK,
295 "User logging in with barcode %s", barcode);
298 jsonObject* resp = NULL; // free
299 jsonObject* user_obj = oilsUtilsFetchUserByBarcode(ctx, barcode); // free
301 if (user_obj && user_obj->type != JSON_NULL)
302 user_id = oilsFMGetObjectId(user_obj);
304 jsonObjectFree(user_obj); // NULL OK
306 char* seed = oilsAuthBuildInitCache(user_id, barcode, "barcode", nonce);
307 resp = jsonNewObject(seed);
310 osrfAppRespondComplete(ctx, resp);
311 jsonObjectFree(resp);
316 // open-ils.auth.authenticate.init.barcode
317 int oilsAuthInitBarcode(osrfMethodContext* ctx) {
318 OSRF_METHOD_VERIFY_CONTEXT(ctx);
320 char* barcode = // free
321 jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, 0));
323 jsonObjectGetString(jsonObjectGetIndex(ctx->params, 1));
325 if (!nonce) nonce = "";
326 if (!barcode) return -1;
328 int resp = oilsAuthInitBarcodeHandler(ctx, barcode, nonce);
334 // returns true if the provided identifier matches the barcode regex.
335 static int oilsAuthIdentIsBarcode(const char* identifier, int org_id) {
338 org_id = oilsUtilsGetRootOrgId();
340 char* bc_regex = oilsUtilsFetchOrgSetting(org_id, "opac.barcode_regex");
343 // if no regex is set, assume any identifier starting
344 // with a number is a barcode.
345 bc_regex = strdup("^\\d"); // dupe for later free'ing
349 int err_offset, match_ret;
351 pcre *compiled = pcre_compile(
352 bc_regex, 0, &err_str, &err_offset, NULL);
354 if (compiled == NULL) {
355 osrfLogError(OSRF_LOG_MARK,
356 "Could not compile '%s': %s", bc_regex, err_str);
362 pcre_extra *extra = pcre_study(compiled, 0, &err_str);
364 if(err_str != NULL) {
365 osrfLogError(OSRF_LOG_MARK,
366 "Could not study regex '%s': %s", bc_regex, err_str);
372 match_ret = pcre_exec(
373 compiled, extra, identifier, strlen(identifier), 0, 0, NULL, 0);
377 if (extra) pcre_free(extra);
379 if (match_ret >= 0) return 1; // regex matched
381 if (match_ret != PCRE_ERROR_NOMATCH)
382 osrfLogError(OSRF_LOG_MARK, "Unknown error processing barcode regex");
384 return 0; // regex did not match
389 @brief Implement the "init" method.
390 @param ctx The method context.
391 @return Zero if successful, or -1 if not.
395 - nonce : optional login seed (string) provided by the caller which
396 is added to the auth init cache to differentiate between logins
397 using the same username and thus avoiding cache collisions for
398 near-simultaneous logins.
400 Return to client: Intermediate authentication seed.
402 int oilsAuthInit(osrfMethodContext* ctx) {
403 OSRF_METHOD_VERIFY_CONTEXT(ctx);
406 char* identifier = // free
407 jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, 0));
409 jsonObjectGetString(jsonObjectGetIndex(ctx->params, 1));
411 if (!nonce) nonce = "";
412 if (!identifier) return -1; // we need an identifier
414 if (oilsAuthIdentIsBarcode(identifier, 0)) {
415 resp = oilsAuthInitBarcodeHandler(ctx, identifier, nonce);
417 resp = oilsAuthInitUsernameHandler(ctx, identifier, nonce);
425 Returns 1 if the password provided matches the user's real password
430 @brief Verify the password received from the client.
431 @param ctx The method context.
432 @param userObj An object from the database, representing the user.
433 @param password An obfuscated password received from the client.
434 @return 1 if the password is valid; 0 if it isn't; or -1 upon error.
436 (None of the so-called "passwords" used here are in plaintext. All have been passed
437 through at least one layer of hashing to obfuscate them.)
439 Take the password from the user object. Append it to the username seed from memcache,
440 as stored previously by a call to the init method. Take an md5 hash of the result.
441 Then compare this hash to the password received from the client.
443 In order for the two to match, other than by dumb luck, the client had to construct
444 the password it passed in the same way. That means it neded to know not only the
445 original password (either hashed or plaintext), but also the seed. The latter requirement
446 means that the client process needs either to be the same process that called the init
447 method or to receive the seed from the process that did so.
449 static int oilsAuthVerifyPassword( const osrfMethodContext* ctx, int user_id,
450 const char* identifier, const char* password, const char* nonce) {
454 // We won't be needing the seed again, remove it
455 char* key = va_list_to_string("%s%s%s", OILS_AUTH_CACHE_PRFX, identifier, nonce ); /**/
456 osrfCacheRemove(key);
459 // Ask the DB to verify the user's password.
460 // Here, the password is md5(md5(password) + salt)
462 jsonObject* params = jsonParseFmt( // free
463 "{\"from\":[\"actor.verify_passwd\",%d,\"main\",\"%s\"]}",
466 jsonObject* verify_obj = // free
467 oilsUtilsCStoreReq("open-ils.cstore.json_query", params);
469 jsonObjectFree(params);
472 verified = oilsUtilsIsDBTrue(
474 jsonObjectGetKeyConst(
475 verify_obj, "actor.verify_passwd")));
477 jsonObjectFree(verify_obj);
480 char* countkey = va_list_to_string("%s%s%s",
481 OILS_AUTH_CACHE_PRFX, identifier, OILS_AUTH_COUNT_SFFX );
482 jsonObject* countobject = osrfCacheGetObject( countkey );
484 long failcount = (long) jsonObjectGetNumber( countobject );
485 if(failcount >= _oilsAuthBlockCount) {
487 osrfLogInfo(OSRF_LOG_MARK,
488 "oilsAuth found too many recent failures for '%s' : %i, "
489 "forcing failure state.", identifier, failcount);
494 jsonObjectSetNumber( countobject, failcount );
495 osrfCachePutObject( countkey, countobject, _oilsAuthBlockTimeout );
496 jsonObjectFree(countobject);
504 * Returns true if the provided password is correct.
505 * Turn the password into the nested md5 hash required of migrated
506 * passwords, then check the password in the DB.
508 static int oilsAuthLoginCheckPassword(int user_id, const char* password) {
510 growing_buffer* gb = buffer_init(33); // free me 1
511 char* salt = oilsAuthGetSalt(user_id); // free me 2
512 char* passhash = md5sum(password); // free me 3
514 buffer_add(gb, salt); // gb strdup's internally
515 buffer_add(gb, passhash);
517 free(salt); // free 2
518 free(passhash); // free 3
520 // salt + md5(password)
521 passhash = buffer_release(gb); // free 1 ; free me 4
522 char* finalpass = md5sum(passhash); // free me 5
524 free(passhash); // free 4
526 jsonObject *arr = jsonNewObjectType(JSON_ARRAY);
527 jsonObjectPush(arr, jsonNewObject("actor.verify_passwd"));
528 jsonObjectPush(arr, jsonNewNumberObject((long) user_id));
529 jsonObjectPush(arr, jsonNewObject("main"));
530 jsonObjectPush(arr, jsonNewObject(finalpass));
531 jsonObject *params = jsonNewObjectType(JSON_HASH); // free me 6
532 jsonObjectSetKey(params, "from", arr);
534 free(finalpass); // free 5
536 jsonObject* verify_obj = // free
537 oilsUtilsCStoreReq("open-ils.cstore.json_query", params);
539 jsonObjectFree(params); // free 6
541 if (!verify_obj) return 0; // error
543 int verified = oilsUtilsIsDBTrue(
545 jsonObjectGetKeyConst(verify_obj, "actor.verify_passwd")
549 jsonObjectFree(verify_obj);
554 static int oilsAuthLoginVerifyPassword(const osrfMethodContext* ctx,
555 int user_id, const char* username, const char* password) {
557 // build the cache key
558 growing_buffer* gb = buffer_init(64); // free me
559 buffer_add(gb, OILS_AUTH_CACHE_PRFX);
560 buffer_add(gb, username);
561 buffer_add(gb, OILS_AUTH_COUNT_SFFX);
562 char* countkey = buffer_release(gb); // free me
564 jsonObject* countobject = osrfCacheGetObject(countkey); // free me
568 failcount = (long) jsonObjectGetNumber(countobject);
570 if (failcount >= _oilsAuthBlockCount) {
571 // User is blocked. Don't waste any more CPU cycles on them.
573 osrfLogInfo(OSRF_LOG_MARK,
574 "oilsAuth found too many recent failures for '%s' : %i, "
575 "forcing failure state.", username, failcount);
577 jsonObjectFree(countobject);
583 int verified = oilsAuthLoginCheckPassword(user_id, password);
585 if (!verified) { // login failed. increment failure counter.
589 // append to existing counter
590 jsonObjectSetNumber(countobject, failcount);
593 // first failure, create a new counter
594 countobject = jsonNewNumberObject((double) failcount);
597 osrfCachePutObject(countkey, countobject, _oilsAuthBlockTimeout);
600 jsonObjectFree(countobject); // NULL OK
608 Adds the authentication token to the user cache. The timeout for the
609 auth token is based on the type of login as well as (if type=='opac')
611 Returns the event that should be returned to the user.
614 static oilsEvent* oilsAuthHandleLoginOK( osrfMethodContext* ctx, jsonObject* userObj, const char* uname,
615 const char* type, int orgloc, const char* workstation ) {
617 oilsEvent* response = NULL;
619 jsonObject* params = jsonNewObject(NULL);
620 jsonObjectSetKey(params, "user_id",
621 jsonNewNumberObject(oilsFMGetObjectId(userObj)));
622 jsonObjectSetKey(params,"org_unit", jsonNewNumberObject(orgloc));
623 jsonObjectSetKey(params, "login_type", jsonNewObject(type));
625 jsonObjectSetKey(params, "workstation", jsonNewObject(workstation));
627 jsonObject* authEvt = oilsUtilsQuickReqCtx(
629 "open-ils.auth_internal",
630 "open-ils.auth_internal.session.create", params);
631 jsonObjectFree(params);
635 response = oilsNewEvent2(
637 jsonObjectGetString(jsonObjectGetKey(authEvt, "textcode")),
638 jsonObjectGetKey(authEvt, "payload") // cloned within Event
641 osrfLogActivity(OSRF_LOG_MARK,
642 "successful login: username=%s, authtoken=%s, workstation=%s",
645 jsonObjectGetKeyConst(
646 jsonObjectGetKeyConst(authEvt, "payload"),
650 workstation ? workstation : ""
653 jsonObjectFree(authEvt);
656 osrfLogError(OSRF_LOG_MARK,
657 "Error caching auth session in open-ils.auth_internal");
665 @brief Implement the "complete" method.
666 @param ctx The method context.
667 @return -1 upon error; zero if successful, and if a STATUS message has been sent to the
668 client to indicate completion; a positive integer if successful but no such STATUS
669 message has been sent.
672 - a hash with some combination of the following elements:
675 - "password" (hashed with the cached seed; not plaintext)
679 - "agent" (what software/interface/3rd-party is making the request)
680 - "nonce" optional login seed to differentiate logins using the same username.
682 The password is required. Either a username or a barcode must also be present.
684 Return to client: Intermediate authentication seed.
686 Validate the password, using the username if available, or the barcode if not. The
687 user must be active, and not barred from logging on. The barcode, if used for
688 authentication, must be active as well. The workstation, if specified, must be valid.
690 Upon deciding whether to allow the logon, return a corresponding event to the client.
692 int oilsAuthComplete( osrfMethodContext* ctx ) {
693 OSRF_METHOD_VERIFY_CONTEXT(ctx);
695 const jsonObject* args = jsonObjectGetIndex(ctx->params, 0);
697 const char* uname = jsonObjectGetString(jsonObjectGetKeyConst(args, "username"));
698 const char* identifier = jsonObjectGetString(jsonObjectGetKeyConst(args, "identifier"));
699 const char* password = jsonObjectGetString(jsonObjectGetKeyConst(args, "password"));
700 const char* type = jsonObjectGetString(jsonObjectGetKeyConst(args, "type"));
701 int orgloc = (int) jsonObjectGetNumber(jsonObjectGetKeyConst(args, "org"));
702 const char* workstation = jsonObjectGetString(jsonObjectGetKeyConst(args, "workstation"));
703 const char* barcode = jsonObjectGetString(jsonObjectGetKeyConst(args, "barcode"));
704 const char* ewho = jsonObjectGetString(jsonObjectGetKeyConst(args, "agent"));
705 const char* nonce = jsonObjectGetString(jsonObjectGetKeyConst(args, "nonce"));
707 const char* ws = (workstation) ? workstation : "";
708 if (!nonce) nonce = "";
710 // we no longer care how the identifier reaches us,
711 // as long as we have one.
715 } else if (barcode) {
716 identifier = barcode;
721 return osrfAppRequestRespondException(ctx->session, ctx->request,
722 "username/barcode and password required for method: %s",
726 osrfLogInfo(OSRF_LOG_MARK,
727 "Patron completing authentication with identifer %s", identifier);
729 /* Use __FILE__, harmless_line_number for creating
730 * OILS_EVENT_AUTH_FAILED events (instead of OSRF_LOG_MARK) to avoid
731 * giving away information about why an authentication attempt failed.
733 int harmless_line_number = __LINE__;
736 type = OILS_AUTH_STAFF;
738 oilsEvent* response = NULL; // free
739 jsonObject* userObj = NULL; // free
741 char* cache_key = va_list_to_string(
742 "%s%s%s", OILS_AUTH_CACHE_PRFX, identifier, nonce);
743 jsonObject* cacheObj = osrfCacheGetObject(cache_key); // free
746 return osrfAppRequestRespondException(ctx->session,
747 ctx->request, "No authentication seed found. "
748 "open-ils.auth.authenticate.init must be called first "
749 " (check that memcached is running and can be connected to) "
753 int user_id = jsonObjectGetNumber(
754 jsonObjectGetKeyConst(cacheObj, "user_id"));
757 // User was not found during init. Clean up and exit early.
758 response = oilsNewEvent(
759 __FILE__, harmless_line_number, OILS_EVENT_AUTH_FAILED);
760 osrfAppRespondComplete(ctx, oilsEventToJSON(response));
761 oilsEventFree(response); // frees event JSON
762 osrfCacheRemove(cache_key);
763 jsonObjectFree(cacheObj);
767 jsonObject* param = jsonNewNumberObject(user_id); // free
768 userObj = oilsUtilsCStoreReqCtx(
769 ctx, "open-ils.cstore.direct.actor.user.retrieve", param);
770 jsonObjectFree(param);
772 // determine if authenticate.init had found the user by barcode,
773 // regardless of whether authenticate.complete is being passed
774 // a username or identifier key.
775 bool initFoundUserByBarcode = false;
776 jsonObject* value = NULL;
777 jsonIterator* cacheIter = jsonNewIterator(cacheObj);
778 while (value = jsonIteratorNext(cacheIter)) {
779 const char *key_name = cacheIter->key;
780 if (!strcmp(key_name, "barcode")) {
781 initFoundUserByBarcode = true;
785 jsonIteratorFree(cacheIter);
787 char* freeable_uname = NULL;
789 uname = freeable_uname = oilsFMGetString(userObj, "usrname");
792 // See if the user is allowed to login.
794 jsonObject* params = jsonNewObject(NULL);
795 jsonObjectSetKey(params, "user_id",
796 jsonNewNumberObject(oilsFMGetObjectId(userObj)));
797 jsonObjectSetKey(params,"org_unit", jsonNewNumberObject(orgloc));
798 jsonObjectSetKey(params, "login_type", jsonNewObject(type));
799 if (initFoundUserByBarcode) {
800 jsonObjectSetKey(params, "barcode", jsonNewObject(identifier));
801 } else if (barcode) {
802 jsonObjectSetKey(params, "barcode", jsonNewObject(barcode));
805 jsonObject* authEvt = oilsUtilsQuickReqCtx( // freed after password test
807 "open-ils.auth_internal",
808 "open-ils.auth_internal.user.validate", params);
809 jsonObjectFree(params);
812 // Something went seriously wrong. Get outta here before
813 // we start segfaulting.
814 jsonObjectFree(userObj);
815 if(freeable_uname) free(freeable_uname);
819 const char* authEvtCode =
820 jsonObjectGetString(jsonObjectGetKey(authEvt, "textcode"));
822 if (!strcmp(authEvtCode, OILS_EVENT_AUTH_FAILED)) {
823 // Received the generic login failure event.
825 osrfLogInfo(OSRF_LOG_MARK,
826 "failed login: username=%s, barcode=%s, workstation=%s",
827 uname, (barcode ? barcode : "(none)"), ws);
829 response = oilsNewEvent(
830 __FILE__, harmless_line_number, OILS_EVENT_AUTH_FAILED);
836 // User exists and is not barred, etc. Test the password.
838 passOK = oilsAuthVerifyPassword(
839 ctx, user_id, identifier, password, nonce);
842 // Password check failed. Return generic login failure.
844 response = oilsNewEvent(
845 __FILE__, harmless_line_number, OILS_EVENT_AUTH_FAILED);
847 osrfLogInfo(OSRF_LOG_MARK,
848 "failed login: username=%s, barcode=%s, workstation=%s",
849 uname, (barcode ? barcode : "(none)"), ws );
854 // Below here, we know the password check succeeded if no response
855 // object is present.
858 !strcmp(authEvtCode, "PATRON_INACTIVE") ||
859 !strcmp(authEvtCode, "PATRON_CARD_INACTIVE"))) {
860 // Patron and/or card is inactive but the correct password
861 // was provided. Alert the caller to the inactive-ness.
862 response = oilsNewEvent2(
863 OSRF_LOG_MARK, authEvtCode,
864 jsonObjectGetKey(authEvt, "payload") // cloned within Event
868 if (!response && strcmp(authEvtCode, OILS_EVENT_SUCCESS)) {
869 // Validate API returned an unexpected non-success event.
870 // To be safe, treat this as a generic login failure.
872 response = oilsNewEvent(
873 __FILE__, harmless_line_number, OILS_EVENT_AUTH_FAILED);
877 // password OK and no other events have prevented login completion.
879 char* ewhat = "login";
881 if (0 == strcmp(ctx->method->name, "open-ils.auth.authenticate.verify")) {
882 response = oilsNewEvent( OSRF_LOG_MARK, OILS_EVENT_SUCCESS );
886 response = oilsAuthHandleLoginOK(
887 ctx, userObj, uname, type, orgloc, workstation);
890 oilsUtilsTrackUserActivity(
892 oilsFMGetObjectId(userObj),
894 osrfAppSessionGetIngress()
899 osrfAppRespondComplete(ctx, oilsEventToJSON(response));
902 oilsEventFree(response);
903 jsonObjectFree(userObj);
904 jsonObjectFree(authEvt);
905 jsonObjectFree(cacheObj);
907 free(freeable_uname);
913 int oilsAuthLogin(osrfMethodContext* ctx) {
914 OSRF_METHOD_VERIFY_CONTEXT(ctx);
916 const jsonObject* args = jsonObjectGetIndex(ctx->params, 0);
918 const char* username = jsonObjectGetString(jsonObjectGetKeyConst(args, "username"));
919 const char* identifier = jsonObjectGetString(jsonObjectGetKeyConst(args, "identifier"));
920 const char* password = jsonObjectGetString(jsonObjectGetKeyConst(args, "password"));
921 const char* type = jsonObjectGetString(jsonObjectGetKeyConst(args, "type"));
922 int orgloc = (int) jsonObjectGetNumber(jsonObjectGetKeyConst(args, "org"));
923 const char* workstation = jsonObjectGetString(jsonObjectGetKeyConst(args, "workstation"));
924 const char* barcode = jsonObjectGetString(jsonObjectGetKeyConst(args, "barcode"));
925 const char* ewho = jsonObjectGetString(jsonObjectGetKeyConst(args, "agent"));
927 const char* ws = (workstation) ? workstation : "";
928 if (!type) type = OILS_AUTH_STAFF;
930 jsonObject* userObj = NULL; // free me
931 oilsEvent* response = NULL; // free me
933 /* Use __FILE__, harmless_line_number for creating
934 * OILS_EVENT_AUTH_FAILED events (instead of OSRF_LOG_MARK) to avoid
935 * giving away information about why an authentication attempt failed.
937 int harmless_line_number = __LINE__;
939 // translate a generic identifier into a username or barcode if necessary.
940 if (identifier && !username && !barcode) {
941 if (oilsAuthIdentIsBarcode(identifier, orgloc)) {
942 barcode = identifier;
944 username = identifier;
949 barcode = NULL; // avoid superfluous identifiers
950 userObj = oilsUtilsFetchUserByUsername(ctx, username);
952 } else if (barcode) {
953 userObj = oilsUtilsFetchUserByBarcode(ctx, barcode);
957 return osrfAppRequestRespondException(ctx->session, ctx->request,
958 "username/barcode and password required for method: %s",
962 if (!userObj) { // user not found.
963 response = oilsNewEvent(
964 __FILE__, harmless_line_number, OILS_EVENT_AUTH_FAILED);
965 osrfAppRespondComplete(ctx, oilsEventToJSON(response));
966 oilsEventFree(response); // frees event JSON
970 long user_id = oilsFMGetObjectId(userObj);
972 // username is freed when userObj is freed.
973 // From here we can use the username as the generic identifier
974 // since it's guaranteed to have a value.
975 if (!username) username = oilsFMGetStringConst(userObj, "usrname");
977 // See if the user is allowed to login.
978 jsonObject* params = jsonNewObject(NULL);
979 jsonObjectSetKey(params, "user_id", jsonNewNumberObject(user_id));
980 jsonObjectSetKey(params,"org_unit", jsonNewNumberObject(orgloc));
981 jsonObjectSetKey(params, "login_type", jsonNewObject(type));
982 if (barcode) jsonObjectSetKey(params, "barcode", jsonNewObject(barcode));
984 jsonObject* authEvt = oilsUtilsQuickReqCtx( // freed after password test
986 "open-ils.auth_internal",
987 "open-ils.auth_internal.user.validate", params);
988 jsonObjectFree(params);
990 if (!authEvt) { // unknown error
991 jsonObjectFree(userObj);
995 const char* authEvtCode =
996 jsonObjectGetString(jsonObjectGetKey(authEvt, "textcode"));
998 if (!strcmp(authEvtCode, OILS_EVENT_AUTH_FAILED)) {
999 // Received the generic login failure event.
1001 osrfLogInfo(OSRF_LOG_MARK,
1002 "failed login: username=%s, barcode=%s, workstation=%s",
1003 username, (barcode ? barcode : "(none)"), ws);
1005 response = oilsNewEvent(
1006 __FILE__, harmless_line_number, OILS_EVENT_AUTH_FAILED);
1009 if (!response && // user exists and is not barred, etc.
1010 !oilsAuthLoginVerifyPassword(ctx, user_id, username, password)) {
1011 // User provided the wrong password or is blocked from too
1012 // many previous login failures.
1014 response = oilsNewEvent(
1015 __FILE__, harmless_line_number, OILS_EVENT_AUTH_FAILED);
1017 osrfLogInfo(OSRF_LOG_MARK,
1018 "failed login: username=%s, barcode=%s, workstation=%s",
1019 username, (barcode ? barcode : "(none)"), ws );
1022 // Below here, we know the password check succeeded if no response
1023 // object is present.
1026 !strcmp(authEvtCode, "PATRON_INACTIVE") ||
1027 !strcmp(authEvtCode, "PATRON_CARD_INACTIVE"))) {
1028 // Patron and/or card is inactive but the correct password
1029 // was provided. Alert the caller to the inactive-ness.
1030 response = oilsNewEvent2(
1031 OSRF_LOG_MARK, authEvtCode,
1032 jsonObjectGetKey(authEvt, "payload") // cloned within Event
1036 if (!response && strcmp(authEvtCode, OILS_EVENT_SUCCESS)) {
1037 // Validate API returned an unexpected non-success event.
1038 // To be safe, treat this as a generic login failure.
1040 response = oilsNewEvent(
1041 __FILE__, harmless_line_number, OILS_EVENT_AUTH_FAILED);
1045 // password OK and no other events have prevented login completion.
1047 char* ewhat = "login";
1049 if (0 == strcmp(ctx->method->name, "open-ils.auth.authenticate.verify")) {
1050 response = oilsNewEvent( OSRF_LOG_MARK, OILS_EVENT_SUCCESS );
1054 response = oilsAuthHandleLoginOK(
1055 ctx, userObj, username, type, orgloc, workstation);
1058 oilsUtilsTrackUserActivity(
1060 oilsFMGetObjectId(userObj),
1062 osrfAppSessionGetIngress()
1067 osrfAppRespondComplete(ctx, oilsEventToJSON(response));
1070 oilsEventFree(response);
1071 jsonObjectFree(userObj);
1072 jsonObjectFree(authEvt);
1079 int oilsAuthSessionDelete( osrfMethodContext* ctx ) {
1080 OSRF_METHOD_VERIFY_CONTEXT(ctx);
1082 const char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0) );
1083 jsonObject* resp = NULL;
1086 osrfLogDebug(OSRF_LOG_MARK, "Removing auth session: %s", authToken );
1087 char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken ); /**/
1088 osrfCacheRemove(key);
1089 resp = jsonNewObject(authToken); /**/
1093 osrfAppRespondComplete( ctx, resp );
1094 jsonObjectFree(resp);
1099 * Fetches the user object from the database and updates the user object in
1100 * the cache object, which then has to be re-inserted into the cache.
1101 * User object is retrieved inside a transaction to avoid replication issues.
1103 static int _oilsAuthReloadUser(jsonObject* cacheObj) {
1105 osrfAppSession* session;
1107 jsonObject *param, *userObj, *newUserObj = NULL;
1109 userObj = jsonObjectGetKey( cacheObj, "userobj" );
1110 userId = oilsFMGetObjectId( userObj );
1112 session = osrfAppSessionClientInit( "open-ils.cstore" );
1113 osrfAppSessionConnect(session);
1115 reqid = osrfAppSessionSendRequest(session, NULL, "open-ils.cstore.transaction.begin", 1);
1116 omsg = osrfAppSessionRequestRecv(session, reqid, 60);
1120 osrfMessageFree(omsg);
1121 param = jsonNewNumberObject(userId);
1122 reqid = osrfAppSessionSendRequest(session, param, "open-ils.cstore.direct.actor.user.retrieve", 1);
1123 omsg = osrfAppSessionRequestRecv(session, reqid, 60);
1124 jsonObjectFree(param);
1127 newUserObj = jsonObjectClone( osrfMessageGetResult(omsg) );
1128 osrfMessageFree(omsg);
1129 reqid = osrfAppSessionSendRequest(session, NULL, "open-ils.cstore.transaction.rollback", 1);
1130 omsg = osrfAppSessionRequestRecv(session, reqid, 60);
1131 osrfMessageFree(omsg);
1135 osrfAppSessionFree(session); // calls disconnect internally
1139 // ws_ou and wsid are ephemeral and need to be manually propagated
1140 // oilsFMSetString dupe()'s internally, no need to clone the string
1141 oilsFMSetString(newUserObj, "wsid", oilsFMGetStringConst(userObj, "wsid"));
1142 oilsFMSetString(newUserObj, "ws_ou", oilsFMGetStringConst(userObj, "ws_ou"));
1144 jsonObjectRemoveKey(cacheObj, "userobj"); // this also frees the old user object
1145 jsonObjectSetKey(cacheObj, "userobj", newUserObj);
1149 osrfLogError(OSRF_LOG_MARK, "Error retrieving user %d from database", userId);
1154 Resets the auth login timeout
1155 @return The event object, OILS_EVENT_SUCCESS, or OILS_EVENT_NO_SESSION
1157 static oilsEvent* _oilsAuthResetTimeout( const char* authToken, int reloadUser ) {
1158 if(!authToken) return NULL;
1160 oilsEvent* evt = NULL;
1163 osrfLogDebug(OSRF_LOG_MARK, "Resetting auth timeout for session %s", authToken);
1164 char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken );
1165 jsonObject* cacheObj = osrfCacheGetObject( key );
1168 osrfLogInfo(OSRF_LOG_MARK, "No user in the cache exists with key %s", key);
1169 evt = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_NO_SESSION);
1174 _oilsAuthReloadUser(cacheObj);
1177 // Determine a new timeout value
1178 jsonObject* endtime_obj = jsonObjectGetKey( cacheObj, "endtime" );
1180 // Extend the current endtime by a fixed amount
1181 time_t endtime = (time_t) jsonObjectGetNumber( endtime_obj );
1182 int reset_interval = DEFAULT_RESET_INTERVAL;
1183 const jsonObject* reset_interval_obj = jsonObjectGetKeyConst(
1184 cacheObj, "reset_interval" );
1185 if( reset_interval_obj ) {
1186 reset_interval = (int) jsonObjectGetNumber( reset_interval_obj );
1187 if( reset_interval <= 0 )
1188 reset_interval = DEFAULT_RESET_INTERVAL;
1191 time_t now = time( NULL );
1192 time_t new_endtime = now + reset_interval;
1193 if( new_endtime > endtime ) {
1194 // Keep the session alive a little longer
1195 jsonObjectSetNumber( endtime_obj, (double) new_endtime );
1196 timeout = reset_interval;
1197 osrfCachePutObject( key, cacheObj, timeout );
1199 // The session isn't close to expiring, so don't reset anything.
1200 // Just report the time remaining.
1201 timeout = endtime - now;
1204 // Reapply the existing timeout from the current time
1205 timeout = (time_t) jsonObjectGetNumber( jsonObjectGetKeyConst( cacheObj, "authtime"));
1206 osrfCachePutObject( key, cacheObj, timeout );
1209 jsonObject* payload = jsonNewNumberObject( (double) timeout );
1210 evt = oilsNewEvent2(OSRF_LOG_MARK, OILS_EVENT_SUCCESS, payload);
1211 jsonObjectFree(payload);
1212 jsonObjectFree(cacheObj);
1219 int oilsAuthResetTimeout( osrfMethodContext* ctx ) {
1220 OSRF_METHOD_VERIFY_CONTEXT(ctx);
1221 const char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0));
1222 double reloadUser = jsonObjectGetNumber( jsonObjectGetIndex(ctx->params, 1));
1223 oilsEvent* evt = _oilsAuthResetTimeout(authToken, (int) reloadUser);
1224 osrfAppRespondComplete( ctx, oilsEventToJSON(evt) );
1230 int oilsAuthSessionRetrieve( osrfMethodContext* ctx ) {
1231 OSRF_METHOD_VERIFY_CONTEXT(ctx);
1232 bool returnFull = false;
1233 bool noTimeoutReset = false;
1235 const char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0));
1237 if(ctx->params->size > 1) {
1238 // caller wants full cached object, with authtime, etc.
1239 const char* rt = jsonObjectGetString(jsonObjectGetIndex(ctx->params, 1));
1240 if(rt && strcmp(rt, "0") != 0)
1243 if (ctx->params->size > 2) {
1244 // Avoid resetting the auth session timeout.
1245 const char* noReset =
1246 jsonObjectGetString(jsonObjectGetIndex(ctx->params, 2));
1247 if (noReset && strcmp(noReset, "0") != 0)
1248 noTimeoutReset = true;
1252 jsonObject* cacheObj = NULL;
1253 oilsEvent* evt = NULL;
1257 // Reset the timeout to keep the session alive
1258 if (!noTimeoutReset)
1259 evt = _oilsAuthResetTimeout(authToken, 0);
1261 if( evt && strcmp(evt->event, OILS_EVENT_SUCCESS) ) {
1262 osrfAppRespondComplete( ctx, oilsEventToJSON( evt )); // can't reset timeout
1266 // Retrieve the cached session object
1267 osrfLogDebug(OSRF_LOG_MARK, "Retrieving auth session: %s", authToken);
1268 char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken );
1269 cacheObj = osrfCacheGetObject( key );
1271 // Return a copy of the cached user object
1273 osrfAppRespondComplete( ctx, cacheObj);
1275 osrfAppRespondComplete( ctx, jsonObjectGetKeyConst( cacheObj, "userobj"));
1276 jsonObjectFree(cacheObj);
1278 // Auth token is invalid or expired
1279 oilsEvent* evt2 = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_NO_SESSION);
1280 osrfAppRespondComplete( ctx, oilsEventToJSON(evt2) ); /* should be event.. */
1281 oilsEventFree(evt2);
1289 evt = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_NO_SESSION);
1290 osrfAppRespondComplete( ctx, oilsEventToJSON(evt) );