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(ctx, 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(ctx, 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 // Assumes barcode regex is a global setting.
323 // TODO: add an org_unit param to the .init API for future use?
324 char* bc_regex = oilsUtilsFetchOrgSetting(
325 oilsUtilsGetRootOrgId(), "opac.barcode_regex");
328 // if no regex is set, assume any identifier starting
329 // with a number is a barcode.
330 bc_regex = strdup("^\\d"); // dupe for later free'ing
334 int err_offset, match_ret;
336 pcre *compiled = pcre_compile(
337 bc_regex, 0, &err_str, &err_offset, NULL);
339 if (compiled == NULL) {
340 osrfLogError(OSRF_LOG_MARK,
341 "Could not compile '%s': %s", bc_regex, err_str);
347 pcre_extra *extra = pcre_study(compiled, 0, &err_str);
349 if(err_str != NULL) {
350 osrfLogError(OSRF_LOG_MARK,
351 "Could not study regex '%s': %s", bc_regex, err_str);
357 match_ret = pcre_exec(
358 compiled, extra, identifier, strlen(identifier), 0, 0, NULL, 0);
362 if (extra) pcre_free(extra);
364 if (match_ret >= 0) return 1; // regex matched
366 if (match_ret != PCRE_ERROR_NOMATCH)
367 osrfLogError(OSRF_LOG_MARK, "Unknown error processing barcode regex");
369 return 0; // regex did not match
374 @brief Implement the "init" method.
375 @param ctx The method context.
376 @return Zero if successful, or -1 if not.
380 - nonce : optional login seed (string) provided by the caller which
381 is added to the auth init cache to differentiate between logins
382 using the same username and thus avoiding cache collisions for
383 near-simultaneous logins.
385 Return to client: Intermediate authentication seed.
387 int oilsAuthInit(osrfMethodContext* ctx) {
388 OSRF_METHOD_VERIFY_CONTEXT(ctx);
391 char* identifier = // free
392 jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, 0));
394 jsonObjectGetString(jsonObjectGetIndex(ctx->params, 1));
396 if (!nonce) nonce = "";
397 if (!identifier) return -1; // we need an identifier
399 if (oilsAuthIdentIsBarcode(identifier)) {
400 resp = oilsAuthInitBarcodeHandler(ctx, identifier, nonce);
402 resp = oilsAuthInitUsernameHandler(ctx, identifier, nonce);
410 Returns 1 if the password provided matches the user's real password
415 @brief Verify the password received from the client.
416 @param ctx The method context.
417 @param userObj An object from the database, representing the user.
418 @param password An obfuscated password received from the client.
419 @return 1 if the password is valid; 0 if it isn't; or -1 upon error.
421 (None of the so-called "passwords" used here are in plaintext. All have been passed
422 through at least one layer of hashing to obfuscate them.)
424 Take the password from the user object. Append it to the username seed from memcache,
425 as stored previously by a call to the init method. Take an md5 hash of the result.
426 Then compare this hash to the password received from the client.
428 In order for the two to match, other than by dumb luck, the client had to construct
429 the password it passed in the same way. That means it neded to know not only the
430 original password (either hashed or plaintext), but also the seed. The latter requirement
431 means that the client process needs either to be the same process that called the init
432 method or to receive the seed from the process that did so.
434 static int oilsAuthVerifyPassword( const osrfMethodContext* ctx, int user_id,
435 const char* identifier, const char* password, const char* nonce) {
439 // We won't be needing the seed again, remove it
440 osrfCacheRemove("%s%s%s", OILS_AUTH_CACHE_PRFX, identifier, nonce);
442 // Ask the DB to verify the user's password.
443 // Here, the password is md5(md5(password) + salt)
445 jsonObject* params = jsonParseFmt( // free
446 "{\"from\":[\"actor.verify_passwd\",%d,\"main\",\"%s\"]}",
449 jsonObject* verify_obj = // free
450 oilsUtilsCStoreReq("open-ils.cstore.json_query", params);
452 jsonObjectFree(params);
455 verified = oilsUtilsIsDBTrue(
457 jsonObjectGetKeyConst(
458 verify_obj, "actor.verify_passwd")));
460 jsonObjectFree(verify_obj);
463 char* countkey = va_list_to_string("%s%s%s",
464 OILS_AUTH_CACHE_PRFX, identifier, OILS_AUTH_COUNT_SFFX );
465 jsonObject* countobject = osrfCacheGetObject( countkey );
467 long failcount = (long) jsonObjectGetNumber( countobject );
468 if(failcount >= _oilsAuthBlockCount) {
470 osrfLogInfo(OSRF_LOG_MARK,
471 "oilsAuth found too many recent failures for '%s' : %i, "
472 "forcing failure state.", identifier, failcount);
477 jsonObjectSetNumber( countobject, failcount );
478 osrfCachePutObject( countkey, countobject, _oilsAuthBlockTimeout );
479 jsonObjectFree(countobject);
487 Adds the authentication token to the user cache. The timeout for the
488 auth token is based on the type of login as well as (if type=='opac')
490 Returns the event that should be returned to the user.
493 static oilsEvent* oilsAuthHandleLoginOK( osrfMethodContext* ctx, jsonObject* userObj, const char* uname,
494 const char* type, int orgloc, const char* workstation ) {
496 oilsEvent* response = NULL;
498 jsonObject* params = jsonNewObject(NULL);
499 jsonObjectSetKey(params, "user_id",
500 jsonNewNumberObject(oilsFMGetObjectId(userObj)));
501 jsonObjectSetKey(params,"org_unit", jsonNewNumberObject(orgloc));
502 jsonObjectSetKey(params, "login_type", jsonNewObject(type));
504 jsonObjectSetKey(params, "workstation", jsonNewObject(workstation));
506 jsonObject* authEvt = oilsUtilsQuickReqCtx(
508 "open-ils.auth_internal",
509 "open-ils.auth_internal.session.create", params);
510 jsonObjectFree(params);
514 response = oilsNewEvent2(
516 jsonObjectGetString(jsonObjectGetKey(authEvt, "textcode")),
517 jsonObjectGetKey(authEvt, "payload") // cloned within Event
520 osrfLogActivity(OSRF_LOG_MARK,
521 "successful login: username=%s, authtoken=%s, workstation=%s",
524 jsonObjectGetKeyConst(
525 jsonObjectGetKeyConst(authEvt, "payload"),
529 workstation ? workstation : ""
532 jsonObjectFree(authEvt);
535 osrfLogError(OSRF_LOG_MARK,
536 "Error caching auth session in open-ils.auth_internal");
544 @brief Implement the "complete" method.
545 @param ctx The method context.
546 @return -1 upon error; zero if successful, and if a STATUS message has been sent to the
547 client to indicate completion; a positive integer if successful but no such STATUS
548 message has been sent.
551 - a hash with some combination of the following elements:
554 - "password" (hashed with the cached seed; not plaintext)
558 - "agent" (what software/interface/3rd-party is making the request)
559 - "nonce" optional login seed to differentiate logins using the same username.
561 The password is required. Either a username or a barcode must also be present.
563 Return to client: Intermediate authentication seed.
565 Validate the password, using the username if available, or the barcode if not. The
566 user must be active, and not barred from logging on. The barcode, if used for
567 authentication, must be active as well. The workstation, if specified, must be valid.
569 Upon deciding whether to allow the logon, return a corresponding event to the client.
571 int oilsAuthComplete( osrfMethodContext* ctx ) {
572 OSRF_METHOD_VERIFY_CONTEXT(ctx);
574 const jsonObject* args = jsonObjectGetIndex(ctx->params, 0);
576 const char* uname = jsonObjectGetString(jsonObjectGetKeyConst(args, "username"));
577 const char* identifier = jsonObjectGetString(jsonObjectGetKeyConst(args, "identifier"));
578 const char* password = jsonObjectGetString(jsonObjectGetKeyConst(args, "password"));
579 const char* type = jsonObjectGetString(jsonObjectGetKeyConst(args, "type"));
580 int orgloc = (int) jsonObjectGetNumber(jsonObjectGetKeyConst(args, "org"));
581 const char* workstation = jsonObjectGetString(jsonObjectGetKeyConst(args, "workstation"));
582 const char* barcode = jsonObjectGetString(jsonObjectGetKeyConst(args, "barcode"));
583 const char* ewho = jsonObjectGetString(jsonObjectGetKeyConst(args, "agent"));
584 const char* nonce = jsonObjectGetString(jsonObjectGetKeyConst(args, "nonce"));
586 const char* ws = (workstation) ? workstation : "";
587 if (!nonce) nonce = "";
589 // we no longer care how the identifier reaches us,
590 // as long as we have one.
594 } else if (barcode) {
595 identifier = barcode;
600 return osrfAppRequestRespondException(ctx->session, ctx->request,
601 "username/barcode and password required for method: %s",
605 osrfLogInfo(OSRF_LOG_MARK,
606 "Patron completing authentication with identifer %s", identifier);
608 /* Use __FILE__, harmless_line_number for creating
609 * OILS_EVENT_AUTH_FAILED events (instead of OSRF_LOG_MARK) to avoid
610 * giving away information about why an authentication attempt failed.
612 int harmless_line_number = __LINE__;
615 type = OILS_AUTH_STAFF;
617 oilsEvent* response = NULL; // free
618 jsonObject* userObj = NULL; // free
619 int card_active = 1; // boolean; assume active until proven otherwise
621 char* cache_key = va_list_to_string(
622 "%s%s%s", OILS_AUTH_CACHE_PRFX, identifier, nonce);
623 jsonObject* cacheObj = osrfCacheGetObject(cache_key); // free
626 return osrfAppRequestRespondException(ctx->session,
627 ctx->request, "No authentication seed found. "
628 "open-ils.auth.authenticate.init must be called first "
629 " (check that memcached is running and can be connected to) "
633 int user_id = jsonObjectGetNumber(
634 jsonObjectGetKeyConst(cacheObj, "user_id"));
637 // User was not found during init. Clean up and exit early.
638 response = oilsNewEvent(
639 __FILE__, harmless_line_number, OILS_EVENT_AUTH_FAILED);
640 osrfAppRespondComplete(ctx, oilsEventToJSON(response));
641 oilsEventFree(response); // frees event JSON
642 osrfCacheRemove(cache_key);
643 jsonObjectFree(cacheObj);
647 jsonObject* param = jsonNewNumberObject(user_id); // free
648 userObj = oilsUtilsCStoreReqCtx(
649 ctx, "open-ils.cstore.direct.actor.user.retrieve", param);
650 jsonObjectFree(param);
652 char* freeable_uname = NULL;
654 uname = freeable_uname = oilsFMGetString(userObj, "usrname");
657 // See if the user is allowed to login.
659 jsonObject* params = jsonNewObject(NULL);
660 jsonObjectSetKey(params, "user_id",
661 jsonNewNumberObject(oilsFMGetObjectId(userObj)));
662 jsonObjectSetKey(params,"org_unit", jsonNewNumberObject(orgloc));
663 jsonObjectSetKey(params, "login_type", jsonNewObject(type));
664 if (barcode) jsonObjectSetKey(params, "barcode", jsonNewObject(barcode));
666 jsonObject* authEvt = oilsUtilsQuickReqCtx( // freed after password test
668 "open-ils.auth_internal",
669 "open-ils.auth_internal.user.validate", params);
670 jsonObjectFree(params);
673 // Something went seriously wrong. Get outta here before
674 // we start segfaulting.
675 jsonObjectFree(userObj);
676 if(freeable_uname) free(freeable_uname);
680 const char* authEvtCode =
681 jsonObjectGetString(jsonObjectGetKey(authEvt, "textcode"));
683 if (!strcmp(authEvtCode, OILS_EVENT_AUTH_FAILED)) {
684 // Received the generic login failure event.
686 osrfLogInfo(OSRF_LOG_MARK,
687 "failed login: username=%s, barcode=%s, workstation=%s",
688 uname, (barcode ? barcode : "(none)"), ws);
690 response = oilsNewEvent(
691 __FILE__, harmless_line_number, OILS_EVENT_AUTH_FAILED);
697 // User exists and is not barred, etc. Test the password.
699 passOK = oilsAuthVerifyPassword(
700 ctx, user_id, identifier, password, nonce);
703 // Password check failed. Return generic login failure.
705 response = oilsNewEvent(
706 __FILE__, harmless_line_number, OILS_EVENT_AUTH_FAILED);
708 osrfLogInfo(OSRF_LOG_MARK,
709 "failed login: username=%s, barcode=%s, workstation=%s",
710 uname, (barcode ? barcode : "(none)"), ws );
715 // Below here, we know the password check succeeded if no response
716 // object is present.
719 !strcmp(authEvtCode, "PATRON_INACTIVE") ||
720 !strcmp(authEvtCode, "PATRON_CARD_INACTIVE"))) {
721 // Patron and/or card is inactive but the correct password
722 // was provided. Alert the caller to the inactive-ness.
723 response = oilsNewEvent2(
724 OSRF_LOG_MARK, authEvtCode,
725 jsonObjectGetKey(authEvt, "payload") // cloned within Event
729 if (!response && strcmp(authEvtCode, OILS_EVENT_SUCCESS)) {
730 // Validate API returned an unexpected non-success event.
731 // To be safe, treat this as a generic login failure.
733 response = oilsNewEvent(
734 __FILE__, harmless_line_number, OILS_EVENT_AUTH_FAILED);
738 // password OK and no other events have prevented login completion.
740 char* ewhat = "login";
742 if (0 == strcmp(ctx->method->name, "open-ils.auth.authenticate.verify")) {
743 response = oilsNewEvent( OSRF_LOG_MARK, OILS_EVENT_SUCCESS );
747 response = oilsAuthHandleLoginOK(
748 ctx, userObj, uname, type, orgloc, workstation);
751 oilsUtilsTrackUserActivity(
753 oilsFMGetObjectId(userObj),
755 osrfAppSessionGetIngress()
760 osrfAppRespondComplete(ctx, oilsEventToJSON(response));
763 oilsEventFree(response);
764 jsonObjectFree(userObj);
765 jsonObjectFree(authEvt);
766 jsonObjectFree(cacheObj);
768 free(freeable_uname);
775 int oilsAuthSessionDelete( osrfMethodContext* ctx ) {
776 OSRF_METHOD_VERIFY_CONTEXT(ctx);
778 const char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0) );
779 jsonObject* resp = NULL;
782 osrfLogDebug(OSRF_LOG_MARK, "Removing auth session: %s", authToken );
783 char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken ); /**/
784 osrfCacheRemove(key);
785 resp = jsonNewObject(authToken); /**/
789 osrfAppRespondComplete( ctx, resp );
790 jsonObjectFree(resp);
795 * Fetches the user object from the database and updates the user object in
796 * the cache object, which then has to be re-inserted into the cache.
797 * User object is retrieved inside a transaction to avoid replication issues.
799 static int _oilsAuthReloadUser(jsonObject* cacheObj) {
801 osrfAppSession* session;
803 jsonObject *param, *userObj, *newUserObj = NULL;
805 userObj = jsonObjectGetKey( cacheObj, "userobj" );
806 userId = oilsFMGetObjectId( userObj );
808 session = osrfAppSessionClientInit( "open-ils.cstore" );
809 osrfAppSessionConnect(session);
811 reqid = osrfAppSessionSendRequest(session, NULL, "open-ils.cstore.transaction.begin", 1);
812 omsg = osrfAppSessionRequestRecv(session, reqid, 60);
816 osrfMessageFree(omsg);
817 param = jsonNewNumberObject(userId);
818 reqid = osrfAppSessionSendRequest(session, param, "open-ils.cstore.direct.actor.user.retrieve", 1);
819 omsg = osrfAppSessionRequestRecv(session, reqid, 60);
820 jsonObjectFree(param);
823 newUserObj = jsonObjectClone( osrfMessageGetResult(omsg) );
824 osrfMessageFree(omsg);
825 reqid = osrfAppSessionSendRequest(session, NULL, "open-ils.cstore.transaction.rollback", 1);
826 omsg = osrfAppSessionRequestRecv(session, reqid, 60);
827 osrfMessageFree(omsg);
831 osrfAppSessionFree(session); // calls disconnect internally
835 // ws_ou and wsid are ephemeral and need to be manually propagated
836 // oilsFMSetString dupe()'s internally, no need to clone the string
837 oilsFMSetString(newUserObj, "wsid", oilsFMGetStringConst(userObj, "wsid"));
838 oilsFMSetString(newUserObj, "ws_ou", oilsFMGetStringConst(userObj, "ws_ou"));
840 jsonObjectRemoveKey(cacheObj, "userobj"); // this also frees the old user object
841 jsonObjectSetKey(cacheObj, "userobj", newUserObj);
845 osrfLogError(OSRF_LOG_MARK, "Error retrieving user %d from database", userId);
850 Resets the auth login timeout
851 @return The event object, OILS_EVENT_SUCCESS, or OILS_EVENT_NO_SESSION
853 static oilsEvent* _oilsAuthResetTimeout( const char* authToken, int reloadUser ) {
854 if(!authToken) return NULL;
856 oilsEvent* evt = NULL;
859 osrfLogDebug(OSRF_LOG_MARK, "Resetting auth timeout for session %s", authToken);
860 char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken );
861 jsonObject* cacheObj = osrfCacheGetObject( key );
864 osrfLogInfo(OSRF_LOG_MARK, "No user in the cache exists with key %s", key);
865 evt = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_NO_SESSION);
870 _oilsAuthReloadUser(cacheObj);
873 // Determine a new timeout value
874 jsonObject* endtime_obj = jsonObjectGetKey( cacheObj, "endtime" );
876 // Extend the current endtime by a fixed amount
877 time_t endtime = (time_t) jsonObjectGetNumber( endtime_obj );
878 int reset_interval = DEFAULT_RESET_INTERVAL;
879 const jsonObject* reset_interval_obj = jsonObjectGetKeyConst(
880 cacheObj, "reset_interval" );
881 if( reset_interval_obj ) {
882 reset_interval = (int) jsonObjectGetNumber( reset_interval_obj );
883 if( reset_interval <= 0 )
884 reset_interval = DEFAULT_RESET_INTERVAL;
887 time_t now = time( NULL );
888 time_t new_endtime = now + reset_interval;
889 if( new_endtime > endtime ) {
890 // Keep the session alive a little longer
891 jsonObjectSetNumber( endtime_obj, (double) new_endtime );
892 timeout = reset_interval;
893 osrfCachePutObject( key, cacheObj, timeout );
895 // The session isn't close to expiring, so don't reset anything.
896 // Just report the time remaining.
897 timeout = endtime - now;
900 // Reapply the existing timeout from the current time
901 timeout = (time_t) jsonObjectGetNumber( jsonObjectGetKeyConst( cacheObj, "authtime"));
902 osrfCachePutObject( key, cacheObj, timeout );
905 jsonObject* payload = jsonNewNumberObject( (double) timeout );
906 evt = oilsNewEvent2(OSRF_LOG_MARK, OILS_EVENT_SUCCESS, payload);
907 jsonObjectFree(payload);
908 jsonObjectFree(cacheObj);
915 int oilsAuthResetTimeout( osrfMethodContext* ctx ) {
916 OSRF_METHOD_VERIFY_CONTEXT(ctx);
917 const char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0));
918 double reloadUser = jsonObjectGetNumber( jsonObjectGetIndex(ctx->params, 1));
919 oilsEvent* evt = _oilsAuthResetTimeout(authToken, (int) reloadUser);
920 osrfAppRespondComplete( ctx, oilsEventToJSON(evt) );
926 int oilsAuthSessionRetrieve( osrfMethodContext* ctx ) {
927 OSRF_METHOD_VERIFY_CONTEXT(ctx);
928 bool returnFull = false;
930 const char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0));
932 if(ctx->params->size > 1) {
933 // caller wants full cached object, with authtime, etc.
934 const char* rt = jsonObjectGetString(jsonObjectGetIndex(ctx->params, 1));
935 if(rt && strcmp(rt, "0") != 0)
939 jsonObject* cacheObj = NULL;
940 oilsEvent* evt = NULL;
944 // Reset the timeout to keep the session alive
945 evt = _oilsAuthResetTimeout(authToken, 0);
947 if( evt && strcmp(evt->event, OILS_EVENT_SUCCESS) ) {
948 osrfAppRespondComplete( ctx, oilsEventToJSON( evt )); // can't reset timeout
952 // Retrieve the cached session object
953 osrfLogDebug(OSRF_LOG_MARK, "Retrieving auth session: %s", authToken);
954 char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken );
955 cacheObj = osrfCacheGetObject( key );
957 // Return a copy of the cached user object
959 osrfAppRespondComplete( ctx, cacheObj);
961 osrfAppRespondComplete( ctx, jsonObjectGetKeyConst( cacheObj, "userobj"));
962 jsonObjectFree(cacheObj);
964 // Auth token is invalid or expired
965 oilsEvent* evt2 = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_NO_SESSION);
966 osrfAppRespondComplete( ctx, oilsEventToJSON(evt2) ); /* should be event.. */
975 evt = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_NO_SESSION);
976 osrfAppRespondComplete( ctx, oilsEventToJSON(evt) );