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);
198 char* auth_seed = oilsAuthGetSalt(user_id);
200 jsonObject* seed_object = jsonParseFmt(
201 "{\"%s\":\"%s\",\"user_id\":%d,\"seed\":\"%s\"}",
202 ident_type, ident, user_id, auth_seed);
204 jsonObject* count_object = osrfCacheGetObject(count_key);
206 count_object = jsonNewNumberObject((double) 0);
209 osrfCachePutObject(cache_key, seed_object, _oilsAuthSeedTimeout);
210 osrfCachePutObject(count_key, count_object, _oilsAuthBlockTimeout);
212 osrfLogDebug(OSRF_LOG_MARK,
213 "oilsAuthInit(): has seed %s and key %s", auth_seed, cache_key);
217 jsonObjectFree(count_object);
218 jsonObjectFree(seed_object);
223 static int oilsAuthInitUsernameHandler(
224 osrfMethodContext* ctx, const char* username, const char* nonce) {
226 osrfLogInfo(OSRF_LOG_MARK,
227 "User logging in with username %s", username);
229 jsonObject* resp = NULL; // free
230 jsonObject* user_obj = oilsUtilsFetchUserByUsername(username); // free
234 if (JSON_NULL == user_obj->type) { // user not found
235 resp = jsonNewObject("x");
238 char* seed = oilsAuthBuildInitCache(
239 oilsFMGetObjectId(user_obj), username, "username", nonce);
240 resp = jsonNewObject(seed);
244 jsonObjectFree(user_obj);
247 resp = jsonNewObject("x");
250 osrfAppRespondComplete(ctx, resp);
251 jsonObjectFree(resp);
255 // open-ils.auth.authenticate.init.username
256 int oilsAuthInitUsername(osrfMethodContext* ctx) {
257 OSRF_METHOD_VERIFY_CONTEXT(ctx);
259 char* username = // free
260 jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, 0));
262 jsonObjectGetString(jsonObjectGetIndex(ctx->params, 1));
264 if (!nonce) nonce = "";
265 if (!username) return -1;
267 int resp = oilsAuthInitUsernameHandler(ctx, username, nonce);
273 static int oilsAuthInitBarcodeHandler(
274 osrfMethodContext* ctx, const char* barcode, const char* nonce) {
276 osrfLogInfo(OSRF_LOG_MARK,
277 "User logging in with barcode %s", barcode);
279 jsonObject* resp = NULL; // free
280 jsonObject* user_obj = oilsUtilsFetchUserByBarcode(barcode); // free
283 if (JSON_NULL == user_obj->type) { // not found
284 resp = jsonNewObject("x");
286 char* seed = oilsAuthBuildInitCache(
287 oilsFMGetObjectId(user_obj), barcode, "barcode", nonce);
288 resp = jsonNewObject(seed);
292 jsonObjectFree(user_obj);
294 resp = jsonNewObject("x");
297 osrfAppRespondComplete(ctx, resp);
298 jsonObjectFree(resp);
303 // open-ils.auth.authenticate.init.barcode
304 int oilsAuthInitBarcode(osrfMethodContext* ctx) {
305 OSRF_METHOD_VERIFY_CONTEXT(ctx);
307 char* barcode = // free
308 jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, 0));
310 jsonObjectGetString(jsonObjectGetIndex(ctx->params, 1));
312 if (!nonce) nonce = "";
313 if (!barcode) return -1;
315 int resp = oilsAuthInitBarcodeHandler(ctx, barcode, nonce);
321 // returns true if the provided identifier matches the barcode regex.
322 static int oilsAuthIdentIsBarcode(const char* identifier) {
324 // before we can fetch the barcode regex unit setting,
325 // first determine what the root org unit ID is.
326 // TODO: add an org_unit param to the .init API for future use?
328 jsonObject *params = jsonParse("{\"parent_ou\":null}");
329 jsonObject *org_unit_id = oilsUtilsCStoreReq(
330 "open-ils.cstore.direct.actor.org_unit.id_list", params);
331 jsonObjectFree(params);
333 char* bc_regex = oilsUtilsFetchOrgSetting(
334 (int) jsonObjectGetNumber(org_unit_id), "opac.barcode_regex");
335 jsonObjectFree(org_unit_id);
338 // if no regex is set, assume any identifier starting
339 // with a number is a barcode.
340 bc_regex = strdup("^\\d"); // dupe for later free'ing
344 int err_offset, match_ret;
346 pcre *compiled = pcre_compile(
347 bc_regex, 0, &err_str, &err_offset, NULL);
349 if (compiled == NULL) {
350 osrfLogError(OSRF_LOG_MARK,
351 "Could not compile '%s': %s", bc_regex, err_str);
357 pcre_extra *extra = pcre_study(compiled, 0, &err_str);
359 if(err_str != NULL) {
360 osrfLogError(OSRF_LOG_MARK,
361 "Could not study regex '%s': %s", bc_regex, err_str);
367 match_ret = pcre_exec(
368 compiled, extra, identifier, strlen(identifier), 0, 0, NULL, 0);
372 if (extra) pcre_free(extra);
374 if (match_ret >= 0) return 1; // regex matched
376 if (match_ret != PCRE_ERROR_NOMATCH)
377 osrfLogError(OSRF_LOG_MARK, "Unknown error processing barcode regex");
379 return 0; // regex did not match
384 @brief Implement the "init" method.
385 @param ctx The method context.
386 @return Zero if successful, or -1 if not.
390 - nonce : optional login seed (string) provided by the caller which
391 is added to the auth init cache to differentiate between logins
392 using the same username and thus avoiding cache collisions for
393 near-simultaneous logins.
395 Return to client: Intermediate authentication seed.
397 int oilsAuthInit(osrfMethodContext* ctx) {
398 OSRF_METHOD_VERIFY_CONTEXT(ctx);
401 char* identifier = // free
402 jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, 0));
404 jsonObjectGetString(jsonObjectGetIndex(ctx->params, 1));
406 if (!nonce) nonce = "";
407 if (!identifier) return -1; // we need an identifier
409 if (oilsAuthIdentIsBarcode(identifier)) {
410 resp = oilsAuthInitBarcodeHandler(ctx, identifier, nonce);
412 resp = oilsAuthInitUsernameHandler(ctx, identifier, nonce);
420 Verifies that the user has permission to login with the
421 given type. If the permission fails, an oilsEvent is returned
423 @return -1 if the permission check failed, 0 if the permission
426 static int oilsAuthCheckLoginPerm(
427 osrfMethodContext* ctx, const jsonObject* userObj, const char* type ) {
429 if(!(userObj && type)) return -1;
430 oilsEvent* perm = NULL;
432 if(!strcasecmp(type, OILS_AUTH_OPAC)) {
433 char* permissions[] = { "OPAC_LOGIN" };
434 perm = oilsUtilsCheckPerms( oilsFMGetObjectId( userObj ), -1, permissions, 1 );
436 } else if(!strcasecmp(type, OILS_AUTH_STAFF)) {
437 char* permissions[] = { "STAFF_LOGIN" };
438 perm = oilsUtilsCheckPerms( oilsFMGetObjectId( userObj ), -1, permissions, 1 );
440 } else if(!strcasecmp(type, OILS_AUTH_TEMP)) {
441 char* permissions[] = { "STAFF_LOGIN" };
442 perm = oilsUtilsCheckPerms( oilsFMGetObjectId( userObj ), -1, permissions, 1 );
443 } else if(!strcasecmp(type, OILS_AUTH_PERSIST)) {
444 char* permissions[] = { "PERSISTENT_LOGIN" };
445 perm = oilsUtilsCheckPerms( oilsFMGetObjectId( userObj ), -1, permissions, 1 );
449 osrfAppRespondComplete( ctx, oilsEventToJSON(perm) );
458 Returns 1 if the password provided matches the user's real password
463 @brief Verify the password received from the client.
464 @param ctx The method context.
465 @param userObj An object from the database, representing the user.
466 @param password An obfuscated password received from the client.
467 @return 1 if the password is valid; 0 if it isn't; or -1 upon error.
469 (None of the so-called "passwords" used here are in plaintext. All have been passed
470 through at least one layer of hashing to obfuscate them.)
472 Take the password from the user object. Append it to the username seed from memcache,
473 as stored previously by a call to the init method. Take an md5 hash of the result.
474 Then compare this hash to the password received from the client.
476 In order for the two to match, other than by dumb luck, the client had to construct
477 the password it passed in the same way. That means it neded to know not only the
478 original password (either hashed or plaintext), but also the seed. The latter requirement
479 means that the client process needs either to be the same process that called the init
480 method or to receive the seed from the process that did so.
482 static int oilsAuthVerifyPassword( const osrfMethodContext* ctx, int user_id,
483 const char* identifier, const char* password, const char* nonce) {
487 // We won't be needing the seed again, remove it
488 osrfCacheRemove("%s%s%s", OILS_AUTH_CACHE_PRFX, identifier, nonce);
490 // Ask the DB to verify the user's password.
491 // Here, the password is md5(md5(password) + salt)
493 jsonObject* params = jsonParseFmt( // free
494 "{\"from\":[\"actor.verify_passwd\",%d,\"main\",\"%s\"]}",
497 jsonObject* verify_obj = // free
498 oilsUtilsCStoreReq("open-ils.cstore.json_query", params);
500 jsonObjectFree(params);
503 verified = oilsUtilsIsDBTrue(
505 jsonObjectGetKeyConst(
506 verify_obj, "actor.verify_passwd")));
508 jsonObjectFree(verify_obj);
511 char* countkey = va_list_to_string("%s%s%s",
512 OILS_AUTH_CACHE_PRFX, identifier, OILS_AUTH_COUNT_SFFX );
513 jsonObject* countobject = osrfCacheGetObject( countkey );
515 long failcount = (long) jsonObjectGetNumber( countobject );
516 if(failcount >= _oilsAuthBlockCount) {
518 osrfLogInfo(OSRF_LOG_MARK,
519 "oilsAuth found too many recent failures for '%s' : %i, "
520 "forcing failure state.", identifier, failcount);
525 jsonObjectSetNumber( countobject, failcount );
526 osrfCachePutObject( countkey, countobject, _oilsAuthBlockTimeout );
527 jsonObjectFree(countobject);
535 Adds the authentication token to the user cache. The timeout for the
536 auth token is based on the type of login as well as (if type=='opac')
538 Returns the event that should be returned to the user.
541 static oilsEvent* oilsAuthHandleLoginOK( jsonObject* userObj, const char* uname,
542 const char* type, int orgloc, const char* workstation ) {
544 oilsEvent* response = NULL;
546 jsonObject* params = jsonNewObject(NULL);
547 jsonObjectSetKey(params, "user_id",
548 jsonNewNumberObject(oilsFMGetObjectId(userObj)));
549 jsonObjectSetKey(params,"org_unit", jsonNewNumberObject(orgloc));
550 jsonObjectSetKey(params, "login_type", jsonNewObject(type));
552 jsonObjectSetKey(params, "workstation", jsonNewObject(workstation));
554 jsonObject* authEvt = oilsUtilsQuickReq(
555 "open-ils.auth_internal",
556 "open-ils.auth_internal.session.create", params);
557 jsonObjectFree(params);
561 response = oilsNewEvent2(
563 jsonObjectGetString(jsonObjectGetKey(authEvt, "textcode")),
564 jsonObjectGetKey(authEvt, "payload") // cloned within Event
567 jsonObjectFree(authEvt);
570 osrfLogError(OSRF_LOG_MARK,
571 "Error caching auth session in open-ils.auth_internal");
579 @brief Implement the "complete" method.
580 @param ctx The method context.
581 @return -1 upon error; zero if successful, and if a STATUS message has been sent to the
582 client to indicate completion; a positive integer if successful but no such STATUS
583 message has been sent.
586 - a hash with some combination of the following elements:
589 - "password" (hashed with the cached seed; not plaintext)
593 - "agent" (what software/interface/3rd-party is making the request)
594 - "nonce" optional login seed to differentiate logins using the same username.
596 The password is required. Either a username or a barcode must also be present.
598 Return to client: Intermediate authentication seed.
600 Validate the password, using the username if available, or the barcode if not. The
601 user must be active, and not barred from logging on. The barcode, if used for
602 authentication, must be active as well. The workstation, if specified, must be valid.
604 Upon deciding whether to allow the logon, return a corresponding event to the client.
606 int oilsAuthComplete( osrfMethodContext* ctx ) {
607 OSRF_METHOD_VERIFY_CONTEXT(ctx);
609 const jsonObject* args = jsonObjectGetIndex(ctx->params, 0);
611 const char* uname = jsonObjectGetString(jsonObjectGetKeyConst(args, "username"));
612 const char* identifier = jsonObjectGetString(jsonObjectGetKeyConst(args, "identifier"));
613 const char* password = jsonObjectGetString(jsonObjectGetKeyConst(args, "password"));
614 const char* type = jsonObjectGetString(jsonObjectGetKeyConst(args, "type"));
615 int orgloc = (int) jsonObjectGetNumber(jsonObjectGetKeyConst(args, "org"));
616 const char* workstation = jsonObjectGetString(jsonObjectGetKeyConst(args, "workstation"));
617 const char* barcode = jsonObjectGetString(jsonObjectGetKeyConst(args, "barcode"));
618 const char* ewho = jsonObjectGetString(jsonObjectGetKeyConst(args, "agent"));
619 const char* nonce = jsonObjectGetString(jsonObjectGetKeyConst(args, "nonce"));
621 const char* ws = (workstation) ? workstation : "";
622 if (!nonce) nonce = "";
624 // we no longer care how the identifier reaches us,
625 // as long as we have one.
629 } else if (barcode) {
630 identifier = barcode;
635 return osrfAppRequestRespondException(ctx->session, ctx->request,
636 "username/barcode and password required for method: %s",
640 osrfLogInfo(OSRF_LOG_MARK,
641 "Patron completing authentication with identifer %s", identifier);
643 /* Use __FILE__, harmless_line_number for creating
644 * OILS_EVENT_AUTH_FAILED events (instead of OSRF_LOG_MARK) to avoid
645 * giving away information about why an authentication attempt failed.
647 int harmless_line_number = __LINE__;
650 type = OILS_AUTH_STAFF;
652 oilsEvent* response = NULL; // free
653 jsonObject* userObj = NULL; // free
654 int card_active = 1; // boolean; assume active until proven otherwise
655 int using_card = 0; // true if this is a barcode login
657 char* cache_key = va_list_to_string(
658 "%s%s%s", OILS_AUTH_CACHE_PRFX, identifier, nonce);
659 jsonObject* cacheObj = osrfCacheGetObject(cache_key); // free
662 return osrfAppRequestRespondException(ctx->session,
663 ctx->request, "No authentication seed found. "
664 "open-ils.auth.authenticate.init must be called first "
665 " (check that memcached is running and can be connected to) "
669 int user_id = jsonObjectGetNumber(
670 jsonObjectGetKeyConst(cacheObj, "user_id"));
672 jsonObject* param = jsonNewNumberObject(user_id); // free
673 userObj = oilsUtilsCStoreReq(
674 "open-ils.cstore.direct.actor.user.retrieve", param);
675 jsonObjectFree(param);
677 using_card = (jsonObjectGetKeyConst(cacheObj, "barcode") != NULL);
680 // see if the card is inactive
682 jsonObject* params = jsonParseFmt("{\"barcode\":\"%s\"}", identifier);
683 jsonObject* card = oilsUtilsCStoreReq(
684 "open-ils.cstore.direct.actor.card.search", params);
685 jsonObjectFree(params);
688 if (card->type != JSON_NULL) {
689 char* card_active_str = oilsFMGetString(card, "active");
690 card_active = oilsUtilsIsDBTrue(card_active_str);
691 free(card_active_str);
693 jsonObjectFree(card);
697 int barred = 0, deleted = 0;
698 char *barred_str, *deleted_str;
701 barred_str = oilsFMGetString(userObj, "barred");
702 barred = oilsUtilsIsDBTrue(barred_str);
705 deleted_str = oilsFMGetString(userObj, "deleted");
706 deleted = oilsUtilsIsDBTrue(deleted_str);
710 if(!userObj || barred || deleted) {
711 response = oilsNewEvent( __FILE__, harmless_line_number, OILS_EVENT_AUTH_FAILED );
712 osrfLogInfo(OSRF_LOG_MARK, "failed login: username=%s, barcode=%s, workstation=%s",
713 uname, (barcode ? barcode : "(none)"), ws );
714 osrfAppRespondComplete( ctx, oilsEventToJSON(response) );
715 oilsEventFree(response);
716 return 0; // No such user
719 // Such a user exists and isn't barred or deleted.
720 // Now see if he or she has the right credentials.
721 int passOK = oilsAuthVerifyPassword(
722 ctx, user_id, identifier, password, nonce);
725 jsonObjectFree(userObj);
729 // See if the account is active
730 char* active = oilsFMGetString(userObj, "active");
731 if( !oilsUtilsIsDBTrue(active) ) {
733 response = oilsNewEvent( OSRF_LOG_MARK, "PATRON_INACTIVE" );
735 response = oilsNewEvent( __FILE__, harmless_line_number, OILS_EVENT_AUTH_FAILED );
737 osrfAppRespondComplete( ctx, oilsEventToJSON(response) );
738 oilsEventFree(response);
739 jsonObjectFree(userObj);
746 osrfLogInfo( OSRF_LOG_MARK, "barcode %s is not active, returning event", barcode );
747 response = oilsNewEvent( OSRF_LOG_MARK, "PATRON_CARD_INACTIVE" );
748 osrfAppRespondComplete( ctx, oilsEventToJSON( response ) );
749 oilsEventFree( response );
750 jsonObjectFree( userObj );
755 // See if the user is even allowed to log in
756 if( oilsAuthCheckLoginPerm( ctx, userObj, type ) == -1 ) {
757 jsonObjectFree(userObj);
761 char* freeable_uname = NULL;
763 uname = freeable_uname = oilsFMGetString( userObj, "usrname" );
766 if( passOK ) { // login successful
768 char* ewhat = "login";
770 if (0 == strcmp(ctx->method->name, "open-ils.auth.authenticate.verify")) {
771 response = oilsNewEvent( OSRF_LOG_MARK, OILS_EVENT_SUCCESS );
775 response = oilsAuthHandleLoginOK( userObj, uname, type, orgloc, workstation );
778 oilsUtilsTrackUserActivity(
779 oilsFMGetObjectId(userObj),
781 osrfAppSessionGetIngress()
785 response = oilsNewEvent( __FILE__, harmless_line_number, OILS_EVENT_AUTH_FAILED );
786 osrfLogInfo(OSRF_LOG_MARK, "failed login: username=%s, barcode=%s, workstation=%s",
787 uname, (barcode ? barcode : "(none)"), ws );
790 jsonObjectFree(userObj);
791 osrfAppRespondComplete( ctx, oilsEventToJSON(response) );
792 oilsEventFree(response);
795 free(freeable_uname);
802 int oilsAuthSessionDelete( osrfMethodContext* ctx ) {
803 OSRF_METHOD_VERIFY_CONTEXT(ctx);
805 const char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0) );
806 jsonObject* resp = NULL;
809 osrfLogDebug(OSRF_LOG_MARK, "Removing auth session: %s", authToken );
810 char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken ); /**/
811 osrfCacheRemove(key);
812 resp = jsonNewObject(authToken); /**/
816 osrfAppRespondComplete( ctx, resp );
817 jsonObjectFree(resp);
822 * Fetches the user object from the database and updates the user object in
823 * the cache object, which then has to be re-inserted into the cache.
824 * User object is retrieved inside a transaction to avoid replication issues.
826 static int _oilsAuthReloadUser(jsonObject* cacheObj) {
828 osrfAppSession* session;
830 jsonObject *param, *userObj, *newUserObj = NULL;
832 userObj = jsonObjectGetKey( cacheObj, "userobj" );
833 userId = oilsFMGetObjectId( userObj );
835 session = osrfAppSessionClientInit( "open-ils.cstore" );
836 osrfAppSessionConnect(session);
838 reqid = osrfAppSessionSendRequest(session, NULL, "open-ils.cstore.transaction.begin", 1);
839 omsg = osrfAppSessionRequestRecv(session, reqid, 60);
843 osrfMessageFree(omsg);
844 param = jsonNewNumberObject(userId);
845 reqid = osrfAppSessionSendRequest(session, param, "open-ils.cstore.direct.actor.user.retrieve", 1);
846 omsg = osrfAppSessionRequestRecv(session, reqid, 60);
847 jsonObjectFree(param);
850 newUserObj = jsonObjectClone( osrfMessageGetResult(omsg) );
851 osrfMessageFree(omsg);
852 reqid = osrfAppSessionSendRequest(session, NULL, "open-ils.cstore.transaction.rollback", 1);
853 omsg = osrfAppSessionRequestRecv(session, reqid, 60);
854 osrfMessageFree(omsg);
858 osrfAppSessionFree(session); // calls disconnect internally
862 // ws_ou and wsid are ephemeral and need to be manually propagated
863 // oilsFMSetString dupe()'s internally, no need to clone the string
864 oilsFMSetString(newUserObj, "wsid", oilsFMGetStringConst(userObj, "wsid"));
865 oilsFMSetString(newUserObj, "ws_ou", oilsFMGetStringConst(userObj, "ws_ou"));
867 jsonObjectRemoveKey(cacheObj, "userobj"); // this also frees the old user object
868 jsonObjectSetKey(cacheObj, "userobj", newUserObj);
872 osrfLogError(OSRF_LOG_MARK, "Error retrieving user %d from database", userId);
877 Resets the auth login timeout
878 @return The event object, OILS_EVENT_SUCCESS, or OILS_EVENT_NO_SESSION
880 static oilsEvent* _oilsAuthResetTimeout( const char* authToken, int reloadUser ) {
881 if(!authToken) return NULL;
883 oilsEvent* evt = NULL;
886 osrfLogDebug(OSRF_LOG_MARK, "Resetting auth timeout for session %s", authToken);
887 char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken );
888 jsonObject* cacheObj = osrfCacheGetObject( key );
891 osrfLogInfo(OSRF_LOG_MARK, "No user in the cache exists with key %s", key);
892 evt = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_NO_SESSION);
897 _oilsAuthReloadUser(cacheObj);
900 // Determine a new timeout value
901 jsonObject* endtime_obj = jsonObjectGetKey( cacheObj, "endtime" );
903 // Extend the current endtime by a fixed amount
904 time_t endtime = (time_t) jsonObjectGetNumber( endtime_obj );
905 int reset_interval = DEFAULT_RESET_INTERVAL;
906 const jsonObject* reset_interval_obj = jsonObjectGetKeyConst(
907 cacheObj, "reset_interval" );
908 if( reset_interval_obj ) {
909 reset_interval = (int) jsonObjectGetNumber( reset_interval_obj );
910 if( reset_interval <= 0 )
911 reset_interval = DEFAULT_RESET_INTERVAL;
914 time_t now = time( NULL );
915 time_t new_endtime = now + reset_interval;
916 if( new_endtime > endtime ) {
917 // Keep the session alive a little longer
918 jsonObjectSetNumber( endtime_obj, (double) new_endtime );
919 timeout = reset_interval;
920 osrfCachePutObject( key, cacheObj, timeout );
922 // The session isn't close to expiring, so don't reset anything.
923 // Just report the time remaining.
924 timeout = endtime - now;
927 // Reapply the existing timeout from the current time
928 timeout = (time_t) jsonObjectGetNumber( jsonObjectGetKeyConst( cacheObj, "authtime"));
929 osrfCachePutObject( key, cacheObj, timeout );
932 jsonObject* payload = jsonNewNumberObject( (double) timeout );
933 evt = oilsNewEvent2(OSRF_LOG_MARK, OILS_EVENT_SUCCESS, payload);
934 jsonObjectFree(payload);
935 jsonObjectFree(cacheObj);
942 int oilsAuthResetTimeout( osrfMethodContext* ctx ) {
943 OSRF_METHOD_VERIFY_CONTEXT(ctx);
944 const char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0));
945 double reloadUser = jsonObjectGetNumber( jsonObjectGetIndex(ctx->params, 1));
946 oilsEvent* evt = _oilsAuthResetTimeout(authToken, (int) reloadUser);
947 osrfAppRespondComplete( ctx, oilsEventToJSON(evt) );
953 int oilsAuthSessionRetrieve( osrfMethodContext* ctx ) {
954 OSRF_METHOD_VERIFY_CONTEXT(ctx);
955 bool returnFull = false;
957 const char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0));
959 if(ctx->params->size > 1) {
960 // caller wants full cached object, with authtime, etc.
961 const char* rt = jsonObjectGetString(jsonObjectGetIndex(ctx->params, 1));
962 if(rt && strcmp(rt, "0") != 0)
966 jsonObject* cacheObj = NULL;
967 oilsEvent* evt = NULL;
971 // Reset the timeout to keep the session alive
972 evt = _oilsAuthResetTimeout(authToken, 0);
974 if( evt && strcmp(evt->event, OILS_EVENT_SUCCESS) ) {
975 osrfAppRespondComplete( ctx, oilsEventToJSON( evt )); // can't reset timeout
979 // Retrieve the cached session object
980 osrfLogDebug(OSRF_LOG_MARK, "Retrieving auth session: %s", authToken);
981 char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken );
982 cacheObj = osrfCacheGetObject( key );
984 // Return a copy of the cached user object
986 osrfAppRespondComplete( ctx, cacheObj);
988 osrfAppRespondComplete( ctx, jsonObjectGetKeyConst( cacheObj, "userobj"));
989 jsonObjectFree(cacheObj);
991 // Auth token is invalid or expired
992 oilsEvent* evt2 = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_NO_SESSION);
993 osrfAppRespondComplete( ctx, oilsEventToJSON(evt2) ); /* should be event.. */
1002 evt = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_NO_SESSION);
1003 osrfAppRespondComplete( ctx, oilsEventToJSON(evt) );