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"
10 #define OILS_AUTH_CACHE_PRFX "oils_auth_"
12 #define MODULENAME "open-ils.auth"
14 #define OILS_AUTH_OPAC "opac"
15 #define OILS_AUTH_STAFF "staff"
16 #define OILS_AUTH_TEMP "temp"
17 #define OILS_AUTH_PERSIST "persist"
19 // Default time for extending a persistent session: ten minutes
20 #define DEFAULT_RESET_INTERVAL 10 * 60
22 int osrfAppInitialize();
23 int osrfAppChildInit();
25 static long _oilsAuthOPACTimeout = 0;
26 static long _oilsAuthStaffTimeout = 0;
27 static long _oilsAuthOverrideTimeout = 0;
28 static long _oilsAuthPersistTimeout = 0;
32 @brief Initialize the application by registering functions for method calls.
33 @return Zero in all cases.
35 int osrfAppInitialize() {
37 osrfLogInfo(OSRF_LOG_MARK, "Initializing Auth Server...");
39 /* load and parse the IDL */
40 if (!oilsInitIDL(NULL)) return 1; /* return non-zero to indicate error */
42 osrfAppRegisterMethod(
44 "open-ils.auth.authenticate.init",
46 "Start the authentication process and returns the intermediate authentication seed"
47 " PARAMS( username )", 1, 0 );
49 osrfAppRegisterMethod(
51 "open-ils.auth.authenticate.complete",
53 "Completes the authentication process. Returns an object like so: "
54 "{authtoken : <token>, authtime:<time>}, where authtoken is the login "
55 "token and authtime is the number of seconds the session will be active"
56 "PARAMS(username, md5sum( seed + md5sum( password ) ), type, org_id ) "
57 "type can be one of 'opac','staff', or 'temp' and it defaults to 'staff' "
58 "org_id is the location at which the login should be considered "
59 "active for login timeout purposes", 1, 0 );
61 osrfAppRegisterMethod(
63 "open-ils.auth.session.retrieve",
64 "oilsAuthSessionRetrieve",
65 "Pass in the auth token and this retrieves the user object. The auth "
66 "timeout is reset when this call is made "
67 "Returns the user object (password blanked) for the given login session "
68 "PARAMS( authToken )", 1, 0 );
70 osrfAppRegisterMethod(
72 "open-ils.auth.session.delete",
73 "oilsAuthSessionDelete",
74 "Destroys the given login session "
75 "PARAMS( authToken )", 1, 0 );
77 osrfAppRegisterMethod(
79 "open-ils.auth.session.reset_timeout",
80 "oilsAuthResetTimeout",
81 "Resets the login timeout for the given session "
82 "Returns an ILS Event with payload = session_timeout of session "
83 "if found, otherwise returns the NO_SESSION event"
84 "PARAMS( authToken )", 1, 0 );
90 @brief Dummy placeholder for initializing a server drone.
92 There is nothing to do, so do nothing.
94 int osrfAppChildInit() {
99 @brief Implement the "init" method.
100 @param ctx The method context.
101 @return Zero if successful, or -1 if not.
106 Return to client: Intermediate authentication seed.
108 Combine the username with a timestamp and process ID, and take an md5 hash of the result.
109 Store the hash in memcache, with a key based on the username. Then return the hash to
112 However: if the username includes one or more embedded blank spaces, return a dummy
113 hash without storing anything in memcache. The dummy will never match a stored hash, so
114 any attempt to authenticate with it will fail.
116 int oilsAuthInit( osrfMethodContext* ctx ) {
117 OSRF_METHOD_VERIFY_CONTEXT(ctx);
119 char* username = jsonObjectToSimpleString( jsonObjectGetIndex(ctx->params, 0) );
124 if( strchr( username, ' ' ) ) {
126 // Embedded spaces are not allowed in a username. Use "x" as a dummy
127 // seed. It will never be a valid seed because 'x' is not a hex digit.
128 resp = jsonNewObject( "x" );
132 // Build a key and a seed; store them in memcache.
133 char* key = va_list_to_string( "%s%s", OILS_AUTH_CACHE_PRFX, username );
134 char* seed = md5sum( "%d.%ld.%s", (int) time(NULL), (long) getpid(), username );
135 osrfCachePutString( key, seed, 30 );
137 osrfLogDebug( OSRF_LOG_MARK, "oilsAuthInit(): has seed %s and key %s", seed, key );
139 // Build a returnable object containing the seed.
140 resp = jsonNewObject( seed );
146 // Return the seed to the client.
147 osrfAppRespondComplete( ctx, resp );
149 jsonObjectFree(resp);
154 return -1; // Error: no username parameter
158 Verifies that the user has permission to login with the
159 given type. If the permission fails, an oilsEvent is returned
161 @return -1 if the permission check failed, 0 if the permission
164 static int oilsAuthCheckLoginPerm(
165 osrfMethodContext* ctx, const jsonObject* userObj, const char* type ) {
167 if(!(userObj && type)) return -1;
168 oilsEvent* perm = NULL;
170 if(!strcasecmp(type, OILS_AUTH_OPAC)) {
171 char* permissions[] = { "OPAC_LOGIN" };
172 perm = oilsUtilsCheckPerms( oilsFMGetObjectId( userObj ), -1, permissions, 1 );
174 } else if(!strcasecmp(type, OILS_AUTH_STAFF)) {
175 char* permissions[] = { "STAFF_LOGIN" };
176 perm = oilsUtilsCheckPerms( oilsFMGetObjectId( userObj ), -1, permissions, 1 );
178 } else if(!strcasecmp(type, OILS_AUTH_TEMP)) {
179 char* permissions[] = { "STAFF_LOGIN" };
180 perm = oilsUtilsCheckPerms( oilsFMGetObjectId( userObj ), -1, permissions, 1 );
181 } else if(!strcasecmp(type, OILS_AUTH_PERSIST)) {
182 char* permissions[] = { "PERSISTENT_LOGIN" };
183 perm = oilsUtilsCheckPerms( oilsFMGetObjectId( userObj ), -1, permissions, 1 );
187 osrfAppRespondComplete( ctx, oilsEventToJSON(perm) );
196 Returns 1 if the password provided matches the user's real password
201 @brief Verify the password received from the client.
202 @param ctx The method context.
203 @param userObj An object from the database, representing the user.
204 @param password An obfuscated password received from the client.
205 @return 1 if the password is valid; 0 if it isn't; or -1 upon error.
207 (None of the so-called "passwords" used here are in plaintext. All have been passed
208 through at least one layer of hashing to obfuscate them.)
210 Take the password from the user object. Append it to the username seed from memcache,
211 as stored previously by a call to the init method. Take an md5 hash of the result.
212 Then compare this hash to the password received from the client.
214 In order for the two to match, other than by dumb luck, the client had to construct
215 the password it passed in the same way. That means it neded to know not only the
216 original password (either hashed or plaintext), but also the seed. The latter requirement
217 means that the client process needs either to be the same process that called the init
218 method or to receive the seed from the process that did so.
220 static int oilsAuthVerifyPassword( const osrfMethodContext* ctx,
221 const jsonObject* userObj, const char* uname, const char* password ) {
223 // Get the username seed, as stored previously in memcache by the init method
224 char* seed = osrfCacheGetString( "%s%s", OILS_AUTH_CACHE_PRFX, uname );
226 return osrfAppRequestRespondException( ctx->session,
227 ctx->request, "No authentication seed found. "
228 "open-ils.auth.authenticate.init must be called first "
229 " (check that memcached is running and can be connected to) "
233 // Get the hashed password from the user object
234 char* realPassword = oilsFMGetString( userObj, "passwd" );
236 osrfLogInternal(OSRF_LOG_MARK, "oilsAuth retrieved real password: [%s]", realPassword);
237 osrfLogDebug(OSRF_LOG_MARK, "oilsAuth retrieved seed from cache: %s", seed );
239 // Concatenate them and take an MD5 hash of the result
240 char* maskedPw = md5sum( "%s%s", seed, realPassword );
246 // This happens only if md5sum() runs out of memory
248 return -1; // md5sum() ran out of memory
251 osrfLogDebug(OSRF_LOG_MARK, "oilsAuth generated masked password %s. "
252 "Testing against provided password %s", maskedPw, password );
255 if( !strcmp( maskedPw, password ) )
264 @brief Determine the login timeout.
265 @param userObj Pointer to an object describing the user.
266 @param type Pointer to one of four possible character strings identifying the login type.
267 @param orgloc Org unit to use for settings lookups (negative or zero means unspecified)
268 @return The length of the timeout, in seconds.
270 The default timeout value comes from the configuration file, and depends on the
273 The default may be overridden by a corresponding org unit setting. The @a orgloc
274 parameter says what org unit to use for the lookup. If @a orgloc <= 0, or if the
275 lookup for @a orgloc yields no result, we look up the setting for the user's home org unit
276 instead (except that if it's the same as @a orgloc we don't bother repeating the lookup).
278 Whether defined in the config file or in an org unit setting, a timeout value may be
279 expressed as a raw number (i.e. all digits, possibly with leading and/or trailing white
280 space) or as an interval string to be translated into seconds by PostgreSQL.
282 static long oilsAuthGetTimeout( const jsonObject* userObj, const char* type, int orgloc ) {
284 if(!_oilsAuthOPACTimeout) { /* Load the default timeouts */
286 jsonObject* value_obj;
288 value_obj = osrf_settings_host_value_object(
289 "/apps/open-ils.auth/app_settings/default_timeout/opac" );
290 _oilsAuthOPACTimeout = oilsUtilsIntervalToSeconds( jsonObjectGetString( value_obj ));
291 jsonObjectFree(value_obj);
292 if( -1 == _oilsAuthOPACTimeout ) {
293 osrfLogWarning( OSRF_LOG_MARK, "Invalid default timeout for OPAC logins" );
294 _oilsAuthOPACTimeout = 0;
297 value_obj = osrf_settings_host_value_object(
298 "/apps/open-ils.auth/app_settings/default_timeout/staff" );
299 _oilsAuthStaffTimeout = oilsUtilsIntervalToSeconds( jsonObjectGetString( value_obj ));
300 jsonObjectFree(value_obj);
301 if( -1 == _oilsAuthStaffTimeout ) {
302 osrfLogWarning( OSRF_LOG_MARK, "Invalid default timeout for staff logins" );
303 _oilsAuthStaffTimeout = 0;
306 value_obj = osrf_settings_host_value_object(
307 "/apps/open-ils.auth/app_settings/default_timeout/temp" );
308 _oilsAuthOverrideTimeout = oilsUtilsIntervalToSeconds( jsonObjectGetString( value_obj ));
309 jsonObjectFree(value_obj);
310 if( -1 == _oilsAuthOverrideTimeout ) {
311 osrfLogWarning( OSRF_LOG_MARK, "Invalid default timeout for temp logins" );
312 _oilsAuthOverrideTimeout = 0;
315 value_obj = osrf_settings_host_value_object(
316 "/apps/open-ils.auth/app_settings/default_timeout/persist" );
317 _oilsAuthPersistTimeout = oilsUtilsIntervalToSeconds( jsonObjectGetString( value_obj ));
318 jsonObjectFree(value_obj);
319 if( -1 == _oilsAuthPersistTimeout ) {
320 osrfLogWarning( OSRF_LOG_MARK, "Invalid default timeout for persist logins" );
321 _oilsAuthPersistTimeout = 0;
324 osrfLogInfo(OSRF_LOG_MARK, "Set default auth timeouts: "
325 "opac => %ld : staff => %ld : temp => %ld : persist => %ld",
326 _oilsAuthOPACTimeout, _oilsAuthStaffTimeout,
327 _oilsAuthOverrideTimeout, _oilsAuthPersistTimeout );
330 int home_ou = (int) jsonObjectGetNumber( oilsFMGetObject( userObj, "home_ou" ));
334 char* setting = NULL;
335 long default_timeout = 0;
337 if( !strcmp( type, OILS_AUTH_OPAC )) {
338 setting = OILS_ORG_SETTING_OPAC_TIMEOUT;
339 default_timeout = _oilsAuthOPACTimeout;
340 } else if( !strcmp( type, OILS_AUTH_STAFF )) {
341 setting = OILS_ORG_SETTING_STAFF_TIMEOUT;
342 default_timeout = _oilsAuthStaffTimeout;
343 } else if( !strcmp( type, OILS_AUTH_TEMP )) {
344 setting = OILS_ORG_SETTING_TEMP_TIMEOUT;
345 default_timeout = _oilsAuthOverrideTimeout;
346 } else if( !strcmp( type, OILS_AUTH_PERSIST )) {
347 setting = OILS_ORG_SETTING_PERSIST_TIMEOUT;
348 default_timeout = _oilsAuthPersistTimeout;
351 // Get the org unit setting, if there is one.
352 char* timeout = oilsUtilsFetchOrgSetting( orgloc, setting );
354 if( orgloc != home_ou ) {
355 osrfLogDebug(OSRF_LOG_MARK, "Auth timeout not defined for org %d, "
356 "trying home_ou %d", orgloc, home_ou );
357 timeout = oilsUtilsFetchOrgSetting( home_ou, setting );
362 return default_timeout; // No override from org unit setting
364 // Translate the org unit setting to a number
367 osrfLogWarning( OSRF_LOG_MARK,
368 "Timeout org unit setting is an empty string for %s login; using default",
372 // Treat timeout string as an interval, and convert it to seconds
373 t = oilsUtilsIntervalToSeconds( timeout );
375 // Unable to convert; possibly an invalid interval string
376 osrfLogError( OSRF_LOG_MARK,
377 "Unable to convert timeout interval \"%s\" for %s login; using default",
388 Adds the authentication token to the user cache. The timeout for the
389 auth token is based on the type of login as well as (if type=='opac')
391 Returns the event that should be returned to the user.
394 static oilsEvent* oilsAuthHandleLoginOK( jsonObject* userObj, const char* uname,
395 const char* type, int orgloc, const char* workstation ) {
400 char* wsorg = jsonObjectToSimpleString(oilsFMGetObject(userObj, "ws_ou"));
401 if(wsorg) { /* if there is a workstation, use it for the timeout */
402 osrfLogDebug( OSRF_LOG_MARK,
403 "Auth session trying workstation id %d for auth timeout", atoi(wsorg));
404 timeout = oilsAuthGetTimeout( userObj, type, atoi(wsorg) );
407 osrfLogDebug( OSRF_LOG_MARK,
408 "Auth session trying org from param [%d] for auth timeout", orgloc );
409 timeout = oilsAuthGetTimeout( userObj, type, orgloc );
411 osrfLogDebug(OSRF_LOG_MARK, "Auth session timeout for %s: %ld", uname, timeout );
413 char* string = va_list_to_string(
414 "%d.%ld.%s", (long) getpid(), time(NULL), uname );
415 char* authToken = md5sum(string);
416 char* authKey = va_list_to_string(
417 "%s%s", OILS_AUTH_CACHE_PRFX, authToken );
419 const char* ws = (workstation) ? workstation : "";
420 osrfLogActivity(OSRF_LOG_MARK,
421 "successful login: username=%s, authtoken=%s, workstation=%s", uname, authToken, ws );
423 oilsFMSetString( userObj, "passwd", "" );
424 jsonObject* cacheObj = jsonParseFmt( "{\"authtime\": %ld}", timeout );
425 jsonObjectSetKey( cacheObj, "userobj", jsonObjectClone(userObj));
427 if( !strcmp( type, OILS_AUTH_PERSIST )) {
428 // Add entries for endtime and reset_interval, so that we can gracefully
429 // extend the session a bit if the user is active toward the end of the
430 // timeout originally specified.
431 time_t endtime = time( NULL ) + timeout;
432 jsonObjectSetKey( cacheObj, "endtime", jsonNewNumberObject( (double) endtime ) );
434 // Reset interval is hard-coded for now, but if we ever want to make it
435 // configurable, this is the place to do it:
436 jsonObjectSetKey( cacheObj, "reset_interval",
437 jsonNewNumberObject( (double) DEFAULT_RESET_INTERVAL ));
440 osrfCachePutObject( authKey, cacheObj, (time_t) timeout );
441 jsonObjectFree(cacheObj);
442 osrfLogInternal(OSRF_LOG_MARK, "oilsAuthHandleLoginOK(): Placed user object into cache");
443 jsonObject* payload = jsonParseFmt(
444 "{ \"authtoken\": \"%s\", \"authtime\": %ld }", authToken, timeout );
446 response = oilsNewEvent2( OSRF_LOG_MARK, OILS_EVENT_SUCCESS, payload );
447 free(string); free(authToken); free(authKey);
448 jsonObjectFree(payload);
453 static oilsEvent* oilsAuthVerifyWorkstation(
454 const osrfMethodContext* ctx, jsonObject* userObj, const char* ws ) {
455 osrfLogInfo(OSRF_LOG_MARK, "Attaching workstation to user at login: %s", ws);
456 jsonObject* workstation = oilsUtilsFetchWorkstationByName(ws);
457 if(!workstation || workstation->type == JSON_NULL) {
458 jsonObjectFree(workstation);
459 return oilsNewEvent(OSRF_LOG_MARK, "WORKSTATION_NOT_FOUND");
461 long wsid = oilsFMGetObjectId(workstation);
462 LONG_TO_STRING(wsid);
463 char* orgid = oilsFMGetString(workstation, "owning_lib");
464 oilsFMSetString(userObj, "wsid", LONGSTR);
465 oilsFMSetString(userObj, "ws_ou", orgid);
467 jsonObjectFree(workstation);
474 @brief Implement the "complete" method.
475 @param ctx The method context.
476 @return -1 upon error; zero if successful, and if a STATUS message has been sent to the
477 client to indicate completion; a positive integer if successful but no such STATUS
478 message has been sent.
481 - a hash with some combination of the following elements:
484 - "password" (hashed with the cached seed; not plaintext)
489 The password is required. Either a username or a barcode must also be present.
491 Return to client: Intermediate authentication seed.
493 Validate the password, using the username if available, or the barcode if not. The
494 user must be active, and not barred from logging on. The barcode, if used for
495 authentication, must be active as well. The workstation, if specified, must be valid.
497 Upon deciding whether to allow the logon, return a corresponding event to the client.
499 int oilsAuthComplete( osrfMethodContext* ctx ) {
500 OSRF_METHOD_VERIFY_CONTEXT(ctx);
502 const jsonObject* args = jsonObjectGetIndex(ctx->params, 0);
504 const char* uname = jsonObjectGetString(jsonObjectGetKeyConst(args, "username"));
505 const char* password = jsonObjectGetString(jsonObjectGetKeyConst(args, "password"));
506 const char* type = jsonObjectGetString(jsonObjectGetKeyConst(args, "type"));
507 int orgloc = (int) jsonObjectGetNumber(jsonObjectGetKeyConst(args, "org"));
508 const char* workstation = jsonObjectGetString(jsonObjectGetKeyConst(args, "workstation"));
509 const char* barcode = jsonObjectGetString(jsonObjectGetKeyConst(args, "barcode"));
511 const char* ws = (workstation) ? workstation : "";
514 type = OILS_AUTH_STAFF;
516 if( !( (uname || barcode) && password) ) {
517 return osrfAppRequestRespondException( ctx->session, ctx->request,
518 "username/barcode and password required for method: %s", ctx->method->name );
521 oilsEvent* response = NULL;
522 jsonObject* userObj = NULL;
523 int card_active = 1; // boolean; assume active until proven otherwise
525 // Fetch a row from the actor.usr table, by username if available,
526 // or by barcode if not.
528 userObj = oilsUtilsFetchUserByUsername( uname );
529 if( userObj && JSON_NULL == userObj->type ) {
530 jsonObjectFree( userObj );
531 userObj = NULL; // username not found
535 // Read from actor.card by barcode
537 osrfLogInfo( OSRF_LOG_MARK, "Fetching user by barcode %s", barcode );
539 jsonObject* params = jsonParseFmt("{\"barcode\":\"%s\"}", barcode);
540 jsonObject* card = oilsUtilsQuickReq(
541 "open-ils.cstore", "open-ils.cstore.direct.actor.card.search", params );
542 jsonObjectFree( params );
544 if( card && card->type != JSON_NULL ) {
545 // Determine whether the card is active
546 char* card_active_str = oilsFMGetString( card, "active" );
547 card_active = oilsUtilsIsDBTrue( card_active_str );
548 free( card_active_str );
550 // Look up the user who owns the card
551 char* userid = oilsFMGetString( card, "usr" );
552 jsonObjectFree( card );
553 params = jsonParseFmt( "[%s]", userid );
555 userObj = oilsUtilsQuickReq(
556 "open-ils.cstore", "open-ils.cstore.direct.actor.user.retrieve", params );
557 jsonObjectFree( params );
558 if( userObj && JSON_NULL == userObj->type ) {
559 // user not found (shouldn't happen, due to foreign key)
560 jsonObjectFree( userObj );
567 response = oilsNewEvent( OSRF_LOG_MARK, OILS_EVENT_AUTH_FAILED );
568 osrfLogInfo(OSRF_LOG_MARK, "failed login: username=%s, barcode=%s, workstation=%s",
569 uname, (barcode ? barcode : "(none)"), ws );
570 osrfAppRespondComplete( ctx, oilsEventToJSON(response) );
571 oilsEventFree(response);
572 return 0; // No such user
575 // Such a user exists. Now see if he or she has the right credentials.
578 passOK = oilsAuthVerifyPassword( ctx, userObj, uname, password );
580 passOK = oilsAuthVerifyPassword( ctx, userObj, barcode, password );
583 jsonObjectFree(userObj);
587 // See if the account is active
588 char* active = oilsFMGetString(userObj, "active");
589 if( !oilsUtilsIsDBTrue(active) ) {
591 response = oilsNewEvent( OSRF_LOG_MARK, "PATRON_INACTIVE" );
593 response = oilsNewEvent( OSRF_LOG_MARK, OILS_EVENT_AUTH_FAILED );
595 osrfAppRespondComplete( ctx, oilsEventToJSON(response) );
596 oilsEventFree(response);
597 jsonObjectFree(userObj);
603 osrfLogInfo( OSRF_LOG_MARK, "Fetching card by barcode %s", barcode );
606 osrfLogInfo( OSRF_LOG_MARK, "barcode %s is not active, returning event", barcode );
607 response = oilsNewEvent( OSRF_LOG_MARK, "PATRON_CARD_INACTIVE" );
608 osrfAppRespondComplete( ctx, oilsEventToJSON( response ) );
609 oilsEventFree( response );
610 jsonObjectFree( userObj );
615 // See if the user is even allowed to log in
616 if( oilsAuthCheckLoginPerm( ctx, userObj, type ) == -1 ) {
617 jsonObjectFree(userObj);
621 // If a workstation is defined, add the workstation info
622 if( workstation != NULL ) {
623 osrfLogDebug(OSRF_LOG_MARK, "Workstation is %s", workstation);
624 response = oilsAuthVerifyWorkstation( ctx, userObj, workstation );
626 jsonObjectFree(userObj);
627 osrfAppRespondComplete( ctx, oilsEventToJSON(response) );
628 oilsEventFree(response);
633 // Otherwise, use the home org as the workstation org on the user
634 char* orgid = oilsFMGetString(userObj, "home_ou");
635 oilsFMSetString(userObj, "ws_ou", orgid);
639 char* freeable_uname = NULL;
641 uname = freeable_uname = oilsFMGetString( userObj, "usrname" );
645 response = oilsAuthHandleLoginOK( userObj, uname, type, orgloc, workstation );
648 response = oilsNewEvent( OSRF_LOG_MARK, OILS_EVENT_AUTH_FAILED );
649 osrfLogInfo(OSRF_LOG_MARK, "failed login: username=%s, barcode=%s, workstation=%s",
650 uname, (barcode ? barcode : "(none)"), ws );
653 jsonObjectFree(userObj);
654 osrfAppRespondComplete( ctx, oilsEventToJSON(response) );
655 oilsEventFree(response);
658 free(freeable_uname);
665 int oilsAuthSessionDelete( osrfMethodContext* ctx ) {
666 OSRF_METHOD_VERIFY_CONTEXT(ctx);
668 const char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0) );
669 jsonObject* resp = NULL;
672 osrfLogDebug(OSRF_LOG_MARK, "Removing auth session: %s", authToken );
673 char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken ); /**/
674 osrfCacheRemove(key);
675 resp = jsonNewObject(authToken); /**/
679 osrfAppRespondComplete( ctx, resp );
680 jsonObjectFree(resp);
685 * Fetches the user object from the database and updates the user object in
686 * the cache object, which then has to be re-inserted into the cache.
687 * User object is retrieved inside a transaction to avoid replication issues.
689 static int _oilsAuthReloadUser(jsonObject* cacheObj) {
691 osrfAppSession* session;
693 jsonObject *param, *userObj, *newUserObj;
695 userObj = jsonObjectGetKey( cacheObj, "userobj" );
696 userId = oilsFMGetObjectId( userObj );
698 session = osrfAppSessionClientInit( "open-ils.cstore" );
699 osrfAppSessionConnect(session);
701 reqid = osrfAppSessionSendRequest(session, NULL, "open-ils.cstore.transaction.begin", 1);
702 omsg = osrfAppSessionRequestRecv(session, reqid, 60);
706 osrfMessageFree(omsg);
707 param = jsonNewNumberObject(userId);
708 reqid = osrfAppSessionSendRequest(session, param, "open-ils.cstore.direct.actor.user.retrieve", 1);
709 omsg = osrfAppSessionRequestRecv(session, reqid, 60);
710 jsonObjectFree(param);
713 newUserObj = jsonObjectClone( osrfMessageGetResult(omsg) );
714 osrfMessageFree(omsg);
715 reqid = osrfAppSessionSendRequest(session, NULL, "open-ils.cstore.transaction.rollback", 1);
716 omsg = osrfAppSessionRequestRecv(session, reqid, 60);
717 osrfMessageFree(omsg);
721 osrfAppSessionFree(session); // calls disconnect internally
725 // ws_ou and wsid are ephemeral and need to be manually propagated
726 // oilsFMSetString dupe()'s internally, no need to clone the string
727 oilsFMSetString(newUserObj, "wsid", oilsFMGetStringConst(userObj, "wsid"));
728 oilsFMSetString(newUserObj, "ws_ou", oilsFMGetStringConst(userObj, "ws_ou"));
730 jsonObjectRemoveKey(cacheObj, "userobj"); // this also frees the old user object
731 jsonObjectSetKey(cacheObj, "userobj", newUserObj);
735 osrfLogError(OSRF_LOG_MARK, "Error retrieving user %d from database", userId);
740 Resets the auth login timeout
741 @return The event object, OILS_EVENT_SUCCESS, or OILS_EVENT_NO_SESSION
743 static oilsEvent* _oilsAuthResetTimeout( const char* authToken, int reloadUser ) {
744 if(!authToken) return NULL;
746 oilsEvent* evt = NULL;
749 osrfLogDebug(OSRF_LOG_MARK, "Resetting auth timeout for session %s", authToken);
750 char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken );
751 jsonObject* cacheObj = osrfCacheGetObject( key );
754 osrfLogInfo(OSRF_LOG_MARK, "No user in the cache exists with key %s", key);
755 evt = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_NO_SESSION);
760 _oilsAuthReloadUser(cacheObj);
763 // Determine a new timeout value
764 jsonObject* endtime_obj = jsonObjectGetKey( cacheObj, "endtime" );
766 // Extend the current endtime by a fixed amount
767 time_t endtime = (time_t) jsonObjectGetNumber( endtime_obj );
768 int reset_interval = DEFAULT_RESET_INTERVAL;
769 const jsonObject* reset_interval_obj = jsonObjectGetKeyConst(
770 cacheObj, "reset_interval" );
771 if( reset_interval_obj ) {
772 reset_interval = (int) jsonObjectGetNumber( reset_interval_obj );
773 if( reset_interval <= 0 )
774 reset_interval = DEFAULT_RESET_INTERVAL;
777 time_t now = time( NULL );
778 time_t new_endtime = now + reset_interval;
779 if( new_endtime > endtime ) {
780 // Keep the session alive a little longer
781 jsonObjectSetNumber( endtime_obj, (double) new_endtime );
782 timeout = reset_interval;
783 osrfCachePutObject( key, cacheObj, timeout );
785 // The session isn't close to expiring, so don't reset anything.
786 // Just report the time remaining.
787 timeout = endtime - now;
790 // Reapply the existing timeout from the current time
791 timeout = (time_t) jsonObjectGetNumber( jsonObjectGetKeyConst( cacheObj, "authtime"));
792 osrfCachePutObject( key, cacheObj, timeout );
795 jsonObject* payload = jsonNewNumberObject( (double) timeout );
796 evt = oilsNewEvent2(OSRF_LOG_MARK, OILS_EVENT_SUCCESS, payload);
797 jsonObjectFree(payload);
798 jsonObjectFree(cacheObj);
805 int oilsAuthResetTimeout( osrfMethodContext* ctx ) {
806 OSRF_METHOD_VERIFY_CONTEXT(ctx);
807 const char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0));
808 double reloadUser = jsonObjectGetNumber( jsonObjectGetIndex(ctx->params, 1));
809 oilsEvent* evt = _oilsAuthResetTimeout(authToken, (int) reloadUser);
810 osrfAppRespondComplete( ctx, oilsEventToJSON(evt) );
816 int oilsAuthSessionRetrieve( osrfMethodContext* ctx ) {
817 OSRF_METHOD_VERIFY_CONTEXT(ctx);
818 bool returnFull = false;
820 const char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0));
822 if(ctx->params->size > 1) {
823 // caller wants full cached object, with authtime, etc.
824 const char* rt = jsonObjectGetString(jsonObjectGetIndex(ctx->params, 1));
825 if(rt && strcmp(rt, "0") != 0)
829 jsonObject* cacheObj = NULL;
830 oilsEvent* evt = NULL;
834 // Reset the timeout to keep the session alive
835 evt = _oilsAuthResetTimeout(authToken, 0);
837 if( evt && strcmp(evt->event, OILS_EVENT_SUCCESS) ) {
838 osrfAppRespondComplete( ctx, oilsEventToJSON( evt )); // can't reset timeout
842 // Retrieve the cached session object
843 osrfLogDebug(OSRF_LOG_MARK, "Retrieving auth session: %s", authToken);
844 char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken );
845 cacheObj = osrfCacheGetObject( key );
847 // Return a copy of the cached user object
849 osrfAppRespondComplete( ctx, cacheObj);
851 osrfAppRespondComplete( ctx, jsonObjectGetKeyConst( cacheObj, "userobj"));
852 jsonObjectFree(cacheObj);
854 // Auth token is invalid or expired
855 oilsEvent* evt2 = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_NO_SESSION);
856 osrfAppRespondComplete( ctx, oilsEventToJSON(evt2) ); /* should be event.. */
865 evt = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_NO_SESSION);
866 osrfAppRespondComplete( ctx, oilsEventToJSON(evt) );