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.authenticate.verify",
80 "Verifies the user provided a valid username and password."
81 "Params and are the same as open-ils.auth.authenticate.complete."
82 "Returns SUCCESS event on success, failure event on failure", 1, 0);
85 osrfAppRegisterMethod(
87 "open-ils.auth.session.retrieve",
88 "oilsAuthSessionRetrieve",
89 "Pass in the auth token and this retrieves the user object. The auth "
90 "timeout is reset when this call is made "
91 "Returns the user object (password blanked) for the given login session "
92 "PARAMS( authToken )", 1, 0 );
94 osrfAppRegisterMethod(
96 "open-ils.auth.session.delete",
97 "oilsAuthSessionDelete",
98 "Destroys the given login session "
99 "PARAMS( authToken )", 1, 0 );
101 osrfAppRegisterMethod(
103 "open-ils.auth.session.reset_timeout",
104 "oilsAuthResetTimeout",
105 "Resets the login timeout for the given session "
106 "Returns an ILS Event with payload = session_timeout of session "
107 "if found, otherwise returns the NO_SESSION event"
108 "PARAMS( authToken )", 1, 0 );
110 if(!_oilsAuthSeedTimeout) { /* Load the default timeouts */
112 jsonObject* value_obj;
114 value_obj = osrf_settings_host_value_object(
115 "/apps/open-ils.auth/app_settings/auth_limits/seed" );
116 _oilsAuthSeedTimeout = oilsUtilsIntervalToSeconds( jsonObjectGetString( value_obj ));
117 jsonObjectFree(value_obj);
118 if( -1 == _oilsAuthSeedTimeout ) {
119 osrfLogWarning( OSRF_LOG_MARK, "Invalid timeout for Auth Seeds - Using 30 seconds" );
120 _oilsAuthSeedTimeout = 30;
123 value_obj = osrf_settings_host_value_object(
124 "/apps/open-ils.auth/app_settings/auth_limits/block_time" );
125 _oilsAuthBlockTimeout = oilsUtilsIntervalToSeconds( jsonObjectGetString( value_obj ));
126 jsonObjectFree(value_obj);
127 if( -1 == _oilsAuthBlockTimeout ) {
128 osrfLogWarning( OSRF_LOG_MARK, "Invalid timeout for Blocking Timeout - Using 3x Seed" );
129 _oilsAuthBlockTimeout = _oilsAuthSeedTimeout * 3;
132 value_obj = osrf_settings_host_value_object(
133 "/apps/open-ils.auth/app_settings/auth_limits/block_count" );
134 _oilsAuthBlockCount = oilsUtilsIntervalToSeconds( jsonObjectGetString( value_obj ));
135 jsonObjectFree(value_obj);
136 if( -1 == _oilsAuthBlockCount ) {
137 osrfLogWarning( OSRF_LOG_MARK, "Invalid count for Blocking - Using 10" );
138 _oilsAuthBlockCount = 10;
141 osrfLogInfo(OSRF_LOG_MARK, "Set auth limits: "
142 "seed => %ld : block_timeout => %ld : block_count => %ld",
143 _oilsAuthSeedTimeout, _oilsAuthBlockTimeout, _oilsAuthBlockCount );
150 @brief Dummy placeholder for initializing a server drone.
152 There is nothing to do, so do nothing.
154 int osrfAppChildInit() {
159 static char* oilsAuthGetSalt(int user_id) {
160 char* salt_str = NULL;
162 jsonObject* params = jsonParseFmt( // free
163 "{\"from\":[\"actor.get_salt\",%d,\"%s\"]}", user_id, "main");
165 jsonObject* salt_obj = // free
166 oilsUtilsCStoreReq("open-ils.cstore.json_query", params);
168 jsonObjectFree(params);
172 if (salt_obj->type != JSON_NULL) {
174 const char* salt_val = jsonObjectGetString(
175 jsonObjectGetKeyConst(salt_obj, "actor.get_salt"));
177 // caller expects a free-able string, could be NULL.
178 if (salt_val) { salt_str = strdup(salt_val); }
181 jsonObjectFree(salt_obj);
187 // ident is either a username or barcode
188 // Returns the init seed -> requires free();
189 static char* oilsAuthBuildInitCache(
190 int user_id, const char* ident, const char* ident_type, const char* nonce) {
192 char* cache_key = va_list_to_string(
193 "%s%s%s", OILS_AUTH_CACHE_PRFX, ident, nonce);
195 char* count_key = va_list_to_string(
196 "%s%s%s", OILS_AUTH_CACHE_PRFX, ident, OILS_AUTH_COUNT_SFFX);
200 // user does not exist. Use a dummy seed
201 auth_seed = strdup("x");
203 auth_seed = oilsAuthGetSalt(user_id);
206 jsonObject* seed_object = jsonParseFmt(
207 "{\"%s\":\"%s\",\"user_id\":%d,\"seed\":\"%s\"}",
208 ident_type, ident, user_id, auth_seed);
210 jsonObject* count_object = osrfCacheGetObject(count_key);
212 count_object = jsonNewNumberObject((double) 0);
215 osrfCachePutObject(cache_key, seed_object, _oilsAuthSeedTimeout);
218 // Only track login counts for existing users, since a
219 // login for a nonexistent user will never succeed anyway.
220 osrfCachePutObject(count_key, count_object, _oilsAuthBlockTimeout);
223 osrfLogDebug(OSRF_LOG_MARK,
224 "oilsAuthInit(): has seed %s and key %s", auth_seed, cache_key);
228 jsonObjectFree(count_object);
229 jsonObjectFree(seed_object);
234 static int oilsAuthInitUsernameHandler(
235 osrfMethodContext* ctx, const char* username, const char* nonce) {
237 osrfLogInfo(OSRF_LOG_MARK,
238 "User logging in with username %s", username);
241 jsonObject* resp = NULL; // free
242 jsonObject* user_obj = oilsUtilsFetchUserByUsername(username); // free
244 if (user_obj && user_obj->type != JSON_NULL)
245 user_id = oilsFMGetObjectId(user_obj);
247 jsonObjectFree(user_obj); // NULL OK
249 char* seed = oilsAuthBuildInitCache(user_id, username, "username", nonce);
250 resp = jsonNewObject(seed);
253 osrfAppRespondComplete(ctx, resp);
254 jsonObjectFree(resp);
258 // open-ils.auth.authenticate.init.username
259 int oilsAuthInitUsername(osrfMethodContext* ctx) {
260 OSRF_METHOD_VERIFY_CONTEXT(ctx);
262 char* username = // free
263 jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, 0));
265 jsonObjectGetString(jsonObjectGetIndex(ctx->params, 1));
267 if (!nonce) nonce = "";
268 if (!username) return -1;
270 int resp = oilsAuthInitUsernameHandler(ctx, username, nonce);
276 static int oilsAuthInitBarcodeHandler(
277 osrfMethodContext* ctx, const char* barcode, const char* nonce) {
279 osrfLogInfo(OSRF_LOG_MARK,
280 "User logging in with barcode %s", barcode);
283 jsonObject* resp = NULL; // free
284 jsonObject* user_obj = oilsUtilsFetchUserByBarcode(barcode); // free
286 if (user_obj && user_obj->type != JSON_NULL)
287 user_id = oilsFMGetObjectId(user_obj);
289 jsonObjectFree(user_obj); // NULL OK
291 char* seed = oilsAuthBuildInitCache(user_id, barcode, "barcode", nonce);
292 resp = jsonNewObject(seed);
295 osrfAppRespondComplete(ctx, resp);
296 jsonObjectFree(resp);
301 // open-ils.auth.authenticate.init.barcode
302 int oilsAuthInitBarcode(osrfMethodContext* ctx) {
303 OSRF_METHOD_VERIFY_CONTEXT(ctx);
305 char* barcode = // free
306 jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, 0));
308 jsonObjectGetString(jsonObjectGetIndex(ctx->params, 1));
310 if (!nonce) nonce = "";
311 if (!barcode) return -1;
313 int resp = oilsAuthInitBarcodeHandler(ctx, barcode, nonce);
319 // returns true if the provided identifier matches the barcode regex.
320 static int oilsAuthIdentIsBarcode(const char* identifier) {
322 // before we can fetch the barcode regex unit setting,
323 // first determine what the root org unit ID is.
324 // TODO: add an org_unit param to the .init API for future use?
326 jsonObject *params = jsonParse("{\"parent_ou\":null}");
327 jsonObject *org_unit_id = oilsUtilsCStoreReq(
328 "open-ils.cstore.direct.actor.org_unit.id_list", params);
329 jsonObjectFree(params);
331 char* bc_regex = oilsUtilsFetchOrgSetting(
332 (int) jsonObjectGetNumber(org_unit_id), "opac.barcode_regex");
333 jsonObjectFree(org_unit_id);
336 // if no regex is set, assume any identifier starting
337 // with a number is a barcode.
338 bc_regex = strdup("^\\d"); // dupe for later free'ing
342 int err_offset, match_ret;
344 pcre *compiled = pcre_compile(
345 bc_regex, 0, &err_str, &err_offset, NULL);
347 if (compiled == NULL) {
348 osrfLogError(OSRF_LOG_MARK,
349 "Could not compile '%s': %s", bc_regex, err_str);
355 pcre_extra *extra = pcre_study(compiled, 0, &err_str);
357 if(err_str != NULL) {
358 osrfLogError(OSRF_LOG_MARK,
359 "Could not study regex '%s': %s", bc_regex, err_str);
365 match_ret = pcre_exec(
366 compiled, extra, identifier, strlen(identifier), 0, 0, NULL, 0);
370 if (extra) pcre_free(extra);
372 if (match_ret >= 0) return 1; // regex matched
374 if (match_ret != PCRE_ERROR_NOMATCH)
375 osrfLogError(OSRF_LOG_MARK, "Unknown error processing barcode regex");
377 return 0; // regex did not match
382 @brief Implement the "init" method.
383 @param ctx The method context.
384 @return Zero if successful, or -1 if not.
388 - nonce : optional login seed (string) provided by the caller which
389 is added to the auth init cache to differentiate between logins
390 using the same username and thus avoiding cache collisions for
391 near-simultaneous logins.
393 Return to client: Intermediate authentication seed.
395 int oilsAuthInit(osrfMethodContext* ctx) {
396 OSRF_METHOD_VERIFY_CONTEXT(ctx);
399 char* identifier = // free
400 jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, 0));
402 jsonObjectGetString(jsonObjectGetIndex(ctx->params, 1));
404 if (!nonce) nonce = "";
405 if (!identifier) return -1; // we need an identifier
407 if (oilsAuthIdentIsBarcode(identifier)) {
408 resp = oilsAuthInitBarcodeHandler(ctx, identifier, nonce);
410 resp = oilsAuthInitUsernameHandler(ctx, identifier, nonce);
418 Returns 1 if the password provided matches the user's real password
423 @brief Verify the password received from the client.
424 @param ctx The method context.
425 @param userObj An object from the database, representing the user.
426 @param password An obfuscated password received from the client.
427 @return 1 if the password is valid; 0 if it isn't; or -1 upon error.
429 (None of the so-called "passwords" used here are in plaintext. All have been passed
430 through at least one layer of hashing to obfuscate them.)
432 Take the password from the user object. Append it to the username seed from memcache,
433 as stored previously by a call to the init method. Take an md5 hash of the result.
434 Then compare this hash to the password received from the client.
436 In order for the two to match, other than by dumb luck, the client had to construct
437 the password it passed in the same way. That means it neded to know not only the
438 original password (either hashed or plaintext), but also the seed. The latter requirement
439 means that the client process needs either to be the same process that called the init
440 method or to receive the seed from the process that did so.
442 static int oilsAuthVerifyPassword( const osrfMethodContext* ctx, int user_id,
443 const char* identifier, const char* password, const char* nonce) {
447 // We won't be needing the seed again, remove it
448 osrfCacheRemove("%s%s%s", OILS_AUTH_CACHE_PRFX, identifier, nonce);
450 // Ask the DB to verify the user's password.
451 // Here, the password is md5(md5(password) + salt)
453 jsonObject* params = jsonParseFmt( // free
454 "{\"from\":[\"actor.verify_passwd\",%d,\"main\",\"%s\"]}",
457 jsonObject* verify_obj = // free
458 oilsUtilsCStoreReq("open-ils.cstore.json_query", params);
460 jsonObjectFree(params);
463 verified = oilsUtilsIsDBTrue(
465 jsonObjectGetKeyConst(
466 verify_obj, "actor.verify_passwd")));
468 jsonObjectFree(verify_obj);
471 char* countkey = va_list_to_string("%s%s%s",
472 OILS_AUTH_CACHE_PRFX, identifier, OILS_AUTH_COUNT_SFFX );
473 jsonObject* countobject = osrfCacheGetObject( countkey );
475 long failcount = (long) jsonObjectGetNumber( countobject );
476 if(failcount >= _oilsAuthBlockCount) {
478 osrfLogInfo(OSRF_LOG_MARK,
479 "oilsAuth found too many recent failures for '%s' : %i, "
480 "forcing failure state.", identifier, failcount);
485 jsonObjectSetNumber( countobject, failcount );
486 osrfCachePutObject( countkey, countobject, _oilsAuthBlockTimeout );
487 jsonObjectFree(countobject);
495 Adds the authentication token to the user cache. The timeout for the
496 auth token is based on the type of login as well as (if type=='opac')
498 Returns the event that should be returned to the user.
501 static oilsEvent* oilsAuthHandleLoginOK( jsonObject* userObj, const char* uname,
502 const char* type, int orgloc, const char* workstation ) {
504 oilsEvent* response = NULL;
506 jsonObject* params = jsonNewObject(NULL);
507 jsonObjectSetKey(params, "user_id",
508 jsonNewNumberObject(oilsFMGetObjectId(userObj)));
509 jsonObjectSetKey(params,"org_unit", jsonNewNumberObject(orgloc));
510 jsonObjectSetKey(params, "login_type", jsonNewObject(type));
512 jsonObjectSetKey(params, "workstation", jsonNewObject(workstation));
514 jsonObject* authEvt = oilsUtilsQuickReq(
515 "open-ils.auth_internal",
516 "open-ils.auth_internal.session.create", params);
517 jsonObjectFree(params);
521 response = oilsNewEvent2(
523 jsonObjectGetString(jsonObjectGetKey(authEvt, "textcode")),
524 jsonObjectGetKey(authEvt, "payload") // cloned within Event
527 jsonObjectFree(authEvt);
530 osrfLogError(OSRF_LOG_MARK,
531 "Error caching auth session in open-ils.auth_internal");
539 @brief Implement the "complete" method.
540 @param ctx The method context.
541 @return -1 upon error; zero if successful, and if a STATUS message has been sent to the
542 client to indicate completion; a positive integer if successful but no such STATUS
543 message has been sent.
546 - a hash with some combination of the following elements:
549 - "password" (hashed with the cached seed; not plaintext)
553 - "agent" (what software/interface/3rd-party is making the request)
554 - "nonce" optional login seed to differentiate logins using the same username.
556 The password is required. Either a username or a barcode must also be present.
558 Return to client: Intermediate authentication seed.
560 Validate the password, using the username if available, or the barcode if not. The
561 user must be active, and not barred from logging on. The barcode, if used for
562 authentication, must be active as well. The workstation, if specified, must be valid.
564 Upon deciding whether to allow the logon, return a corresponding event to the client.
566 int oilsAuthComplete( osrfMethodContext* ctx ) {
567 OSRF_METHOD_VERIFY_CONTEXT(ctx);
569 const jsonObject* args = jsonObjectGetIndex(ctx->params, 0);
571 const char* uname = jsonObjectGetString(jsonObjectGetKeyConst(args, "username"));
572 const char* identifier = jsonObjectGetString(jsonObjectGetKeyConst(args, "identifier"));
573 const char* password = jsonObjectGetString(jsonObjectGetKeyConst(args, "password"));
574 const char* type = jsonObjectGetString(jsonObjectGetKeyConst(args, "type"));
575 int orgloc = (int) jsonObjectGetNumber(jsonObjectGetKeyConst(args, "org"));
576 const char* workstation = jsonObjectGetString(jsonObjectGetKeyConst(args, "workstation"));
577 const char* barcode = jsonObjectGetString(jsonObjectGetKeyConst(args, "barcode"));
578 const char* ewho = jsonObjectGetString(jsonObjectGetKeyConst(args, "agent"));
579 const char* nonce = jsonObjectGetString(jsonObjectGetKeyConst(args, "nonce"));
581 const char* ws = (workstation) ? workstation : "";
582 if (!nonce) nonce = "";
584 // we no longer care how the identifier reaches us,
585 // as long as we have one.
589 } else if (barcode) {
590 identifier = barcode;
595 return osrfAppRequestRespondException(ctx->session, ctx->request,
596 "username/barcode and password required for method: %s",
600 osrfLogInfo(OSRF_LOG_MARK,
601 "Patron completing authentication with identifer %s", identifier);
603 /* Use __FILE__, harmless_line_number for creating
604 * OILS_EVENT_AUTH_FAILED events (instead of OSRF_LOG_MARK) to avoid
605 * giving away information about why an authentication attempt failed.
607 int harmless_line_number = __LINE__;
610 type = OILS_AUTH_STAFF;
612 oilsEvent* response = NULL; // free
613 jsonObject* userObj = NULL; // free
614 int card_active = 1; // boolean; assume active until proven otherwise
616 char* cache_key = va_list_to_string(
617 "%s%s%s", OILS_AUTH_CACHE_PRFX, identifier, nonce);
618 jsonObject* cacheObj = osrfCacheGetObject(cache_key); // free
621 return osrfAppRequestRespondException(ctx->session,
622 ctx->request, "No authentication seed found. "
623 "open-ils.auth.authenticate.init must be called first "
624 " (check that memcached is running and can be connected to) "
628 int user_id = jsonObjectGetNumber(
629 jsonObjectGetKeyConst(cacheObj, "user_id"));
632 // User was not found during init. Clean up and exit early.
633 response = oilsNewEvent(
634 __FILE__, harmless_line_number, OILS_EVENT_AUTH_FAILED);
635 osrfAppRespondComplete(ctx, oilsEventToJSON(response));
636 oilsEventFree(response); // frees event JSON
637 osrfCacheRemove(cache_key);
638 jsonObjectFree(cacheObj);
642 jsonObject* param = jsonNewNumberObject(user_id); // free
643 userObj = oilsUtilsCStoreReq(
644 "open-ils.cstore.direct.actor.user.retrieve", param);
645 jsonObjectFree(param);
647 char* freeable_uname = NULL;
649 uname = freeable_uname = oilsFMGetString(userObj, "usrname");
652 // See if the user is allowed to login.
654 jsonObject* params = jsonNewObject(NULL);
655 jsonObjectSetKey(params, "user_id",
656 jsonNewNumberObject(oilsFMGetObjectId(userObj)));
657 jsonObjectSetKey(params,"org_unit", jsonNewNumberObject(orgloc));
658 jsonObjectSetKey(params, "login_type", jsonNewObject(type));
659 if (barcode) jsonObjectSetKey(params, "barcode", jsonNewObject(barcode));
661 jsonObject* authEvt = oilsUtilsQuickReq( // freed after password test
662 "open-ils.auth_internal",
663 "open-ils.auth_internal.user.validate", params);
664 jsonObjectFree(params);
667 // Something went seriously wrong. Get outta here before
668 // we start segfaulting.
669 jsonObjectFree(userObj);
670 if(freeable_uname) free(freeable_uname);
674 const char* authEvtCode =
675 jsonObjectGetString(jsonObjectGetKey(authEvt, "textcode"));
677 if (!strcmp(authEvtCode, OILS_EVENT_AUTH_FAILED)) {
678 // Received the generic login failure event.
680 osrfLogInfo(OSRF_LOG_MARK,
681 "failed login: username=%s, barcode=%s, workstation=%s",
682 uname, (barcode ? barcode : "(none)"), ws);
684 response = oilsNewEvent(
685 __FILE__, harmless_line_number, OILS_EVENT_AUTH_FAILED);
691 // User exists and is not barred, etc. Test the password.
693 passOK = oilsAuthVerifyPassword(
694 ctx, user_id, identifier, password, nonce);
697 // Password check failed. Return generic login failure.
699 response = oilsNewEvent(
700 __FILE__, harmless_line_number, OILS_EVENT_AUTH_FAILED);
702 osrfLogInfo(OSRF_LOG_MARK,
703 "failed login: username=%s, barcode=%s, workstation=%s",
704 uname, (barcode ? barcode : "(none)"), ws );
709 // Below here, we know the password check succeeded if no response
710 // object is present.
713 !strcmp(authEvtCode, "PATRON_INACTIVE") ||
714 !strcmp(authEvtCode, "PATRON_CARD_INACTIVE"))) {
715 // Patron and/or card is inactive but the correct password
716 // was provided. Alert the caller to the inactive-ness.
717 response = oilsNewEvent2(
718 OSRF_LOG_MARK, authEvtCode,
719 jsonObjectGetKey(authEvt, "payload") // cloned within Event
723 if (!response && strcmp(authEvtCode, OILS_EVENT_SUCCESS)) {
724 // Validate API returned an unexpected non-success event.
725 // To be safe, treat this as a generic login failure.
727 response = oilsNewEvent(
728 __FILE__, harmless_line_number, OILS_EVENT_AUTH_FAILED);
732 // password OK and no other events have prevented login completion.
734 char* ewhat = "login";
736 if (0 == strcmp(ctx->method->name, "open-ils.auth.authenticate.verify")) {
737 response = oilsNewEvent( OSRF_LOG_MARK, OILS_EVENT_SUCCESS );
741 response = oilsAuthHandleLoginOK(
742 userObj, uname, type, orgloc, workstation);
745 oilsUtilsTrackUserActivity(
746 oilsFMGetObjectId(userObj),
748 osrfAppSessionGetIngress()
753 osrfAppRespondComplete(ctx, oilsEventToJSON(response));
756 oilsEventFree(response);
757 jsonObjectFree(userObj);
758 jsonObjectFree(authEvt);
759 jsonObjectFree(cacheObj);
761 free(freeable_uname);
768 int oilsAuthSessionDelete( osrfMethodContext* ctx ) {
769 OSRF_METHOD_VERIFY_CONTEXT(ctx);
771 const char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0) );
772 jsonObject* resp = NULL;
775 osrfLogDebug(OSRF_LOG_MARK, "Removing auth session: %s", authToken );
776 char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken ); /**/
777 osrfCacheRemove(key);
778 resp = jsonNewObject(authToken); /**/
782 osrfAppRespondComplete( ctx, resp );
783 jsonObjectFree(resp);
788 * Fetches the user object from the database and updates the user object in
789 * the cache object, which then has to be re-inserted into the cache.
790 * User object is retrieved inside a transaction to avoid replication issues.
792 static int _oilsAuthReloadUser(jsonObject* cacheObj) {
794 osrfAppSession* session;
796 jsonObject *param, *userObj, *newUserObj = NULL;
798 userObj = jsonObjectGetKey( cacheObj, "userobj" );
799 userId = oilsFMGetObjectId( userObj );
801 session = osrfAppSessionClientInit( "open-ils.cstore" );
802 osrfAppSessionConnect(session);
804 reqid = osrfAppSessionSendRequest(session, NULL, "open-ils.cstore.transaction.begin", 1);
805 omsg = osrfAppSessionRequestRecv(session, reqid, 60);
809 osrfMessageFree(omsg);
810 param = jsonNewNumberObject(userId);
811 reqid = osrfAppSessionSendRequest(session, param, "open-ils.cstore.direct.actor.user.retrieve", 1);
812 omsg = osrfAppSessionRequestRecv(session, reqid, 60);
813 jsonObjectFree(param);
816 newUserObj = jsonObjectClone( osrfMessageGetResult(omsg) );
817 osrfMessageFree(omsg);
818 reqid = osrfAppSessionSendRequest(session, NULL, "open-ils.cstore.transaction.rollback", 1);
819 omsg = osrfAppSessionRequestRecv(session, reqid, 60);
820 osrfMessageFree(omsg);
824 osrfAppSessionFree(session); // calls disconnect internally
828 // ws_ou and wsid are ephemeral and need to be manually propagated
829 // oilsFMSetString dupe()'s internally, no need to clone the string
830 oilsFMSetString(newUserObj, "wsid", oilsFMGetStringConst(userObj, "wsid"));
831 oilsFMSetString(newUserObj, "ws_ou", oilsFMGetStringConst(userObj, "ws_ou"));
833 jsonObjectRemoveKey(cacheObj, "userobj"); // this also frees the old user object
834 jsonObjectSetKey(cacheObj, "userobj", newUserObj);
838 osrfLogError(OSRF_LOG_MARK, "Error retrieving user %d from database", userId);
843 Resets the auth login timeout
844 @return The event object, OILS_EVENT_SUCCESS, or OILS_EVENT_NO_SESSION
846 static oilsEvent* _oilsAuthResetTimeout( const char* authToken, int reloadUser ) {
847 if(!authToken) return NULL;
849 oilsEvent* evt = NULL;
852 osrfLogDebug(OSRF_LOG_MARK, "Resetting auth timeout for session %s", authToken);
853 char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken );
854 jsonObject* cacheObj = osrfCacheGetObject( key );
857 osrfLogInfo(OSRF_LOG_MARK, "No user in the cache exists with key %s", key);
858 evt = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_NO_SESSION);
863 _oilsAuthReloadUser(cacheObj);
866 // Determine a new timeout value
867 jsonObject* endtime_obj = jsonObjectGetKey( cacheObj, "endtime" );
869 // Extend the current endtime by a fixed amount
870 time_t endtime = (time_t) jsonObjectGetNumber( endtime_obj );
871 int reset_interval = DEFAULT_RESET_INTERVAL;
872 const jsonObject* reset_interval_obj = jsonObjectGetKeyConst(
873 cacheObj, "reset_interval" );
874 if( reset_interval_obj ) {
875 reset_interval = (int) jsonObjectGetNumber( reset_interval_obj );
876 if( reset_interval <= 0 )
877 reset_interval = DEFAULT_RESET_INTERVAL;
880 time_t now = time( NULL );
881 time_t new_endtime = now + reset_interval;
882 if( new_endtime > endtime ) {
883 // Keep the session alive a little longer
884 jsonObjectSetNumber( endtime_obj, (double) new_endtime );
885 timeout = reset_interval;
886 osrfCachePutObject( key, cacheObj, timeout );
888 // The session isn't close to expiring, so don't reset anything.
889 // Just report the time remaining.
890 timeout = endtime - now;
893 // Reapply the existing timeout from the current time
894 timeout = (time_t) jsonObjectGetNumber( jsonObjectGetKeyConst( cacheObj, "authtime"));
895 osrfCachePutObject( key, cacheObj, timeout );
898 jsonObject* payload = jsonNewNumberObject( (double) timeout );
899 evt = oilsNewEvent2(OSRF_LOG_MARK, OILS_EVENT_SUCCESS, payload);
900 jsonObjectFree(payload);
901 jsonObjectFree(cacheObj);
908 int oilsAuthResetTimeout( osrfMethodContext* ctx ) {
909 OSRF_METHOD_VERIFY_CONTEXT(ctx);
910 const char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0));
911 double reloadUser = jsonObjectGetNumber( jsonObjectGetIndex(ctx->params, 1));
912 oilsEvent* evt = _oilsAuthResetTimeout(authToken, (int) reloadUser);
913 osrfAppRespondComplete( ctx, oilsEventToJSON(evt) );
919 int oilsAuthSessionRetrieve( osrfMethodContext* ctx ) {
920 OSRF_METHOD_VERIFY_CONTEXT(ctx);
921 bool returnFull = false;
923 const char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0));
925 if(ctx->params->size > 1) {
926 // caller wants full cached object, with authtime, etc.
927 const char* rt = jsonObjectGetString(jsonObjectGetIndex(ctx->params, 1));
928 if(rt && strcmp(rt, "0") != 0)
932 jsonObject* cacheObj = NULL;
933 oilsEvent* evt = NULL;
937 // Reset the timeout to keep the session alive
938 evt = _oilsAuthResetTimeout(authToken, 0);
940 if( evt && strcmp(evt->event, OILS_EVENT_SUCCESS) ) {
941 osrfAppRespondComplete( ctx, oilsEventToJSON( evt )); // can't reset timeout
945 // Retrieve the cached session object
946 osrfLogDebug(OSRF_LOG_MARK, "Retrieving auth session: %s", authToken);
947 char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken );
948 cacheObj = osrfCacheGetObject( key );
950 // Return a copy of the cached user object
952 osrfAppRespondComplete( ctx, cacheObj);
954 osrfAppRespondComplete( ctx, jsonObjectGetKeyConst( cacheObj, "userobj"));
955 jsonObjectFree(cacheObj);
957 // Auth token is invalid or expired
958 oilsEvent* evt2 = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_NO_SESSION);
959 osrfAppRespondComplete( ctx, oilsEventToJSON(evt2) ); /* should be event.. */
968 evt = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_NO_SESSION);
969 osrfAppRespondComplete( ctx, oilsEventToJSON(evt) );