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_"
11 #define OILS_AUTH_COUNT_SFFX "_count"
13 #define MODULENAME "open-ils.auth"
15 #define OILS_AUTH_OPAC "opac"
16 #define OILS_AUTH_STAFF "staff"
17 #define OILS_AUTH_TEMP "temp"
18 #define OILS_AUTH_PERSIST "persist"
20 // Default time for extending a persistent session: ten minutes
21 #define DEFAULT_RESET_INTERVAL 10 * 60
23 #define OILS_AUTH_COUNT_MAX 10
24 #define OILS_AUTH_COUNT_INTERVAL 90
26 int osrfAppInitialize();
27 int osrfAppChildInit();
29 static long _oilsAuthOPACTimeout = 0;
30 static long _oilsAuthStaffTimeout = 0;
31 static long _oilsAuthOverrideTimeout = 0;
32 static long _oilsAuthPersistTimeout = 0;
36 @brief Initialize the application by registering functions for method calls.
37 @return Zero in all cases.
39 int osrfAppInitialize() {
41 osrfLogInfo(OSRF_LOG_MARK, "Initializing Auth Server...");
43 /* load and parse the IDL */
44 if (!oilsInitIDL(NULL)) return 1; /* return non-zero to indicate error */
46 osrfAppRegisterMethod(
48 "open-ils.auth.authenticate.init",
50 "Start the authentication process and returns the intermediate authentication seed"
51 " PARAMS( username )", 1, 0 );
53 osrfAppRegisterMethod(
55 "open-ils.auth.authenticate.complete",
57 "Completes the authentication process. Returns an object like so: "
58 "{authtoken : <token>, authtime:<time>}, where authtoken is the login "
59 "token and authtime is the number of seconds the session will be active"
60 "PARAMS(username, md5sum( seed + md5sum( password ) ), type, org_id ) "
61 "type can be one of 'opac','staff', or 'temp' and it defaults to 'staff' "
62 "org_id is the location at which the login should be considered "
63 "active for login timeout purposes", 1, 0 );
65 osrfAppRegisterMethod(
67 "open-ils.auth.session.retrieve",
68 "oilsAuthSessionRetrieve",
69 "Pass in the auth token and this retrieves the user object. The auth "
70 "timeout is reset when this call is made "
71 "Returns the user object (password blanked) for the given login session "
72 "PARAMS( authToken )", 1, 0 );
74 osrfAppRegisterMethod(
76 "open-ils.auth.session.delete",
77 "oilsAuthSessionDelete",
78 "Destroys the given login session "
79 "PARAMS( authToken )", 1, 0 );
81 osrfAppRegisterMethod(
83 "open-ils.auth.session.reset_timeout",
84 "oilsAuthResetTimeout",
85 "Resets the login timeout for the given session "
86 "Returns an ILS Event with payload = session_timeout of session "
87 "if found, otherwise returns the NO_SESSION event"
88 "PARAMS( authToken )", 1, 0 );
94 @brief Dummy placeholder for initializing a server drone.
96 There is nothing to do, so do nothing.
98 int osrfAppChildInit() {
103 @brief Implement the "init" method.
104 @param ctx The method context.
105 @return Zero if successful, or -1 if not.
110 Return to client: Intermediate authentication seed.
112 Combine the username with a timestamp and process ID, and take an md5 hash of the result.
113 Store the hash in memcache, with a key based on the username. Then return the hash to
116 However: if the username includes one or more embedded blank spaces, return a dummy
117 hash without storing anything in memcache. The dummy will never match a stored hash, so
118 any attempt to authenticate with it will fail.
120 int oilsAuthInit( osrfMethodContext* ctx ) {
121 OSRF_METHOD_VERIFY_CONTEXT(ctx);
123 char* username = jsonObjectToSimpleString( jsonObjectGetIndex(ctx->params, 0) );
128 if( strchr( username, ' ' ) ) {
130 // Embedded spaces are not allowed in a username. Use "x" as a dummy
131 // seed. It will never be a valid seed because 'x' is not a hex digit.
132 resp = jsonNewObject( "x" );
136 // Build a key and a seed; store them in memcache.
137 char* key = va_list_to_string( "%s%s", OILS_AUTH_CACHE_PRFX, username );
138 char* countkey = va_list_to_string( "%s%s%s", OILS_AUTH_CACHE_PRFX, username, OILS_AUTH_COUNT_SFFX );
139 char* seed = md5sum( "%d.%ld.%s", (int) time(NULL), (long) getpid(), username );
140 jsonObject* countobject = osrfCacheGetObject( countkey );
142 countobject = jsonNewNumberObject( (double) 0 );
144 osrfCachePutString( key, seed, 30 );
145 osrfCachePutObject( countkey, countobject, OILS_AUTH_COUNT_INTERVAL );
147 osrfLogDebug( OSRF_LOG_MARK, "oilsAuthInit(): has seed %s and key %s", seed, key );
149 // Build a returnable object containing the seed.
150 resp = jsonNewObject( seed );
155 jsonObjectFree( countobject );
158 // Return the seed to the client.
159 osrfAppRespondComplete( ctx, resp );
161 jsonObjectFree(resp);
166 return -1; // Error: no username parameter
170 Verifies that the user has permission to login with the
171 given type. If the permission fails, an oilsEvent is returned
173 @return -1 if the permission check failed, 0 if the permission
176 static int oilsAuthCheckLoginPerm(
177 osrfMethodContext* ctx, const jsonObject* userObj, const char* type ) {
179 if(!(userObj && type)) return -1;
180 oilsEvent* perm = NULL;
182 if(!strcasecmp(type, OILS_AUTH_OPAC)) {
183 char* permissions[] = { "OPAC_LOGIN" };
184 perm = oilsUtilsCheckPerms( oilsFMGetObjectId( userObj ), -1, permissions, 1 );
186 } else if(!strcasecmp(type, OILS_AUTH_STAFF)) {
187 char* permissions[] = { "STAFF_LOGIN" };
188 perm = oilsUtilsCheckPerms( oilsFMGetObjectId( userObj ), -1, permissions, 1 );
190 } else if(!strcasecmp(type, OILS_AUTH_TEMP)) {
191 char* permissions[] = { "STAFF_LOGIN" };
192 perm = oilsUtilsCheckPerms( oilsFMGetObjectId( userObj ), -1, permissions, 1 );
193 } else if(!strcasecmp(type, OILS_AUTH_PERSIST)) {
194 char* permissions[] = { "PERSISTENT_LOGIN" };
195 perm = oilsUtilsCheckPerms( oilsFMGetObjectId( userObj ), -1, permissions, 1 );
199 osrfAppRespondComplete( ctx, oilsEventToJSON(perm) );
208 Returns 1 if the password provided matches the user's real password
213 @brief Verify the password received from the client.
214 @param ctx The method context.
215 @param userObj An object from the database, representing the user.
216 @param password An obfuscated password received from the client.
217 @return 1 if the password is valid; 0 if it isn't; or -1 upon error.
219 (None of the so-called "passwords" used here are in plaintext. All have been passed
220 through at least one layer of hashing to obfuscate them.)
222 Take the password from the user object. Append it to the username seed from memcache,
223 as stored previously by a call to the init method. Take an md5 hash of the result.
224 Then compare this hash to the password received from the client.
226 In order for the two to match, other than by dumb luck, the client had to construct
227 the password it passed in the same way. That means it neded to know not only the
228 original password (either hashed or plaintext), but also the seed. The latter requirement
229 means that the client process needs either to be the same process that called the init
230 method or to receive the seed from the process that did so.
232 static int oilsAuthVerifyPassword( const osrfMethodContext* ctx,
233 const jsonObject* userObj, const char* uname, const char* password ) {
235 // Get the username seed, as stored previously in memcache by the init method
236 char* seed = osrfCacheGetString( "%s%s", OILS_AUTH_CACHE_PRFX, uname );
238 return osrfAppRequestRespondException( ctx->session,
239 ctx->request, "No authentication seed found. "
240 "open-ils.auth.authenticate.init must be called first "
241 " (check that memcached is running and can be connected to) "
245 // Get the hashed password from the user object
246 char* realPassword = oilsFMGetString( userObj, "passwd" );
248 osrfLogInternal(OSRF_LOG_MARK, "oilsAuth retrieved real password: [%s]", realPassword);
249 osrfLogDebug(OSRF_LOG_MARK, "oilsAuth retrieved seed from cache: %s", seed );
251 // Concatenate them and take an MD5 hash of the result
252 char* maskedPw = md5sum( "%s%s", seed, realPassword );
258 // This happens only if md5sum() runs out of memory
260 return -1; // md5sum() ran out of memory
263 osrfLogDebug(OSRF_LOG_MARK, "oilsAuth generated masked password %s. "
264 "Testing against provided password %s", maskedPw, password );
267 if( !strcmp( maskedPw, password ) )
272 char* countkey = va_list_to_string( "%s%s%s", OILS_AUTH_CACHE_PRFX, uname, OILS_AUTH_COUNT_SFFX );
273 jsonObject* countobject = osrfCacheGetObject( countkey );
275 double failcount = jsonObjectGetNumber( countobject );
276 if(failcount >= OILS_AUTH_COUNT_MAX) {
278 osrfLogInternal(OSRF_LOG_MARK, "oilsAuth found too many recent failures: %d, forcing failure state.", failcount);
283 jsonObjectSetNumber( countobject, failcount );
284 osrfCachePutObject( countkey, countobject, OILS_AUTH_COUNT_INTERVAL );
285 jsonObjectFree(countobject);
293 @brief Determine the login timeout.
294 @param userObj Pointer to an object describing the user.
295 @param type Pointer to one of four possible character strings identifying the login type.
296 @param orgloc Org unit to use for settings lookups (negative or zero means unspecified)
297 @return The length of the timeout, in seconds.
299 The default timeout value comes from the configuration file, and depends on the
302 The default may be overridden by a corresponding org unit setting. The @a orgloc
303 parameter says what org unit to use for the lookup. If @a orgloc <= 0, or if the
304 lookup for @a orgloc yields no result, we look up the setting for the user's home org unit
305 instead (except that if it's the same as @a orgloc we don't bother repeating the lookup).
307 Whether defined in the config file or in an org unit setting, a timeout value may be
308 expressed as a raw number (i.e. all digits, possibly with leading and/or trailing white
309 space) or as an interval string to be translated into seconds by PostgreSQL.
311 static long oilsAuthGetTimeout( const jsonObject* userObj, const char* type, int orgloc ) {
313 if(!_oilsAuthOPACTimeout) { /* Load the default timeouts */
315 jsonObject* value_obj;
317 value_obj = osrf_settings_host_value_object(
318 "/apps/open-ils.auth/app_settings/default_timeout/opac" );
319 _oilsAuthOPACTimeout = oilsUtilsIntervalToSeconds( jsonObjectGetString( value_obj ));
320 jsonObjectFree(value_obj);
321 if( -1 == _oilsAuthOPACTimeout ) {
322 osrfLogWarning( OSRF_LOG_MARK, "Invalid default timeout for OPAC logins" );
323 _oilsAuthOPACTimeout = 0;
326 value_obj = osrf_settings_host_value_object(
327 "/apps/open-ils.auth/app_settings/default_timeout/staff" );
328 _oilsAuthStaffTimeout = oilsUtilsIntervalToSeconds( jsonObjectGetString( value_obj ));
329 jsonObjectFree(value_obj);
330 if( -1 == _oilsAuthStaffTimeout ) {
331 osrfLogWarning( OSRF_LOG_MARK, "Invalid default timeout for staff logins" );
332 _oilsAuthStaffTimeout = 0;
335 value_obj = osrf_settings_host_value_object(
336 "/apps/open-ils.auth/app_settings/default_timeout/temp" );
337 _oilsAuthOverrideTimeout = oilsUtilsIntervalToSeconds( jsonObjectGetString( value_obj ));
338 jsonObjectFree(value_obj);
339 if( -1 == _oilsAuthOverrideTimeout ) {
340 osrfLogWarning( OSRF_LOG_MARK, "Invalid default timeout for temp logins" );
341 _oilsAuthOverrideTimeout = 0;
344 value_obj = osrf_settings_host_value_object(
345 "/apps/open-ils.auth/app_settings/default_timeout/persist" );
346 _oilsAuthPersistTimeout = oilsUtilsIntervalToSeconds( jsonObjectGetString( value_obj ));
347 jsonObjectFree(value_obj);
348 if( -1 == _oilsAuthPersistTimeout ) {
349 osrfLogWarning( OSRF_LOG_MARK, "Invalid default timeout for persist logins" );
350 _oilsAuthPersistTimeout = 0;
353 osrfLogInfo(OSRF_LOG_MARK, "Set default auth timeouts: "
354 "opac => %ld : staff => %ld : temp => %ld : persist => %ld",
355 _oilsAuthOPACTimeout, _oilsAuthStaffTimeout,
356 _oilsAuthOverrideTimeout, _oilsAuthPersistTimeout );
359 int home_ou = (int) jsonObjectGetNumber( oilsFMGetObject( userObj, "home_ou" ));
363 char* setting = NULL;
364 long default_timeout = 0;
366 if( !strcmp( type, OILS_AUTH_OPAC )) {
367 setting = OILS_ORG_SETTING_OPAC_TIMEOUT;
368 default_timeout = _oilsAuthOPACTimeout;
369 } else if( !strcmp( type, OILS_AUTH_STAFF )) {
370 setting = OILS_ORG_SETTING_STAFF_TIMEOUT;
371 default_timeout = _oilsAuthStaffTimeout;
372 } else if( !strcmp( type, OILS_AUTH_TEMP )) {
373 setting = OILS_ORG_SETTING_TEMP_TIMEOUT;
374 default_timeout = _oilsAuthOverrideTimeout;
375 } else if( !strcmp( type, OILS_AUTH_PERSIST )) {
376 setting = OILS_ORG_SETTING_PERSIST_TIMEOUT;
377 default_timeout = _oilsAuthPersistTimeout;
380 // Get the org unit setting, if there is one.
381 char* timeout = oilsUtilsFetchOrgSetting( orgloc, setting );
383 if( orgloc != home_ou ) {
384 osrfLogDebug(OSRF_LOG_MARK, "Auth timeout not defined for org %d, "
385 "trying home_ou %d", orgloc, home_ou );
386 timeout = oilsUtilsFetchOrgSetting( home_ou, setting );
391 return default_timeout; // No override from org unit setting
393 // Translate the org unit setting to a number
396 osrfLogWarning( OSRF_LOG_MARK,
397 "Timeout org unit setting is an empty string for %s login; using default",
401 // Treat timeout string as an interval, and convert it to seconds
402 t = oilsUtilsIntervalToSeconds( timeout );
404 // Unable to convert; possibly an invalid interval string
405 osrfLogError( OSRF_LOG_MARK,
406 "Unable to convert timeout interval \"%s\" for %s login; using default",
417 Adds the authentication token to the user cache. The timeout for the
418 auth token is based on the type of login as well as (if type=='opac')
420 Returns the event that should be returned to the user.
423 static oilsEvent* oilsAuthHandleLoginOK( jsonObject* userObj, const char* uname,
424 const char* type, int orgloc, const char* workstation ) {
429 char* wsorg = jsonObjectToSimpleString(oilsFMGetObject(userObj, "ws_ou"));
430 if(wsorg) { /* if there is a workstation, use it for the timeout */
431 osrfLogDebug( OSRF_LOG_MARK,
432 "Auth session trying workstation id %d for auth timeout", atoi(wsorg));
433 timeout = oilsAuthGetTimeout( userObj, type, atoi(wsorg) );
436 osrfLogDebug( OSRF_LOG_MARK,
437 "Auth session trying org from param [%d] for auth timeout", orgloc );
438 timeout = oilsAuthGetTimeout( userObj, type, orgloc );
440 osrfLogDebug(OSRF_LOG_MARK, "Auth session timeout for %s: %ld", uname, timeout );
442 char* string = va_list_to_string(
443 "%d.%ld.%s", (long) getpid(), time(NULL), uname );
444 char* authToken = md5sum(string);
445 char* authKey = va_list_to_string(
446 "%s%s", OILS_AUTH_CACHE_PRFX, authToken );
448 const char* ws = (workstation) ? workstation : "";
449 osrfLogActivity(OSRF_LOG_MARK,
450 "successful login: username=%s, authtoken=%s, workstation=%s", uname, authToken, ws );
452 oilsFMSetString( userObj, "passwd", "" );
453 jsonObject* cacheObj = jsonParseFmt( "{\"authtime\": %ld}", timeout );
454 jsonObjectSetKey( cacheObj, "userobj", jsonObjectClone(userObj));
456 if( !strcmp( type, OILS_AUTH_PERSIST )) {
457 // Add entries for endtime and reset_interval, so that we can gracefully
458 // extend the session a bit if the user is active toward the end of the
459 // timeout originally specified.
460 time_t endtime = time( NULL ) + timeout;
461 jsonObjectSetKey( cacheObj, "endtime", jsonNewNumberObject( (double) endtime ) );
463 // Reset interval is hard-coded for now, but if we ever want to make it
464 // configurable, this is the place to do it:
465 jsonObjectSetKey( cacheObj, "reset_interval",
466 jsonNewNumberObject( (double) DEFAULT_RESET_INTERVAL ));
469 osrfCachePutObject( authKey, cacheObj, (time_t) timeout );
470 jsonObjectFree(cacheObj);
471 osrfLogInternal(OSRF_LOG_MARK, "oilsAuthHandleLoginOK(): Placed user object into cache");
472 jsonObject* payload = jsonParseFmt(
473 "{ \"authtoken\": \"%s\", \"authtime\": %ld }", authToken, timeout );
475 response = oilsNewEvent2( OSRF_LOG_MARK, OILS_EVENT_SUCCESS, payload );
476 free(string); free(authToken); free(authKey);
477 jsonObjectFree(payload);
482 static oilsEvent* oilsAuthVerifyWorkstation(
483 const osrfMethodContext* ctx, jsonObject* userObj, const char* ws ) {
484 osrfLogInfo(OSRF_LOG_MARK, "Attaching workstation to user at login: %s", ws);
485 jsonObject* workstation = oilsUtilsFetchWorkstationByName(ws);
486 if(!workstation || workstation->type == JSON_NULL) {
487 jsonObjectFree(workstation);
488 return oilsNewEvent(OSRF_LOG_MARK, "WORKSTATION_NOT_FOUND");
490 long wsid = oilsFMGetObjectId(workstation);
491 LONG_TO_STRING(wsid);
492 char* orgid = oilsFMGetString(workstation, "owning_lib");
493 oilsFMSetString(userObj, "wsid", LONGSTR);
494 oilsFMSetString(userObj, "ws_ou", orgid);
496 jsonObjectFree(workstation);
503 @brief Implement the "complete" method.
504 @param ctx The method context.
505 @return -1 upon error; zero if successful, and if a STATUS message has been sent to the
506 client to indicate completion; a positive integer if successful but no such STATUS
507 message has been sent.
510 - a hash with some combination of the following elements:
513 - "password" (hashed with the cached seed; not plaintext)
518 The password is required. Either a username or a barcode must also be present.
520 Return to client: Intermediate authentication seed.
522 Validate the password, using the username if available, or the barcode if not. The
523 user must be active, and not barred from logging on. The barcode, if used for
524 authentication, must be active as well. The workstation, if specified, must be valid.
526 Upon deciding whether to allow the logon, return a corresponding event to the client.
528 int oilsAuthComplete( osrfMethodContext* ctx ) {
529 OSRF_METHOD_VERIFY_CONTEXT(ctx);
531 const jsonObject* args = jsonObjectGetIndex(ctx->params, 0);
533 const char* uname = jsonObjectGetString(jsonObjectGetKeyConst(args, "username"));
534 const char* password = jsonObjectGetString(jsonObjectGetKeyConst(args, "password"));
535 const char* type = jsonObjectGetString(jsonObjectGetKeyConst(args, "type"));
536 int orgloc = (int) jsonObjectGetNumber(jsonObjectGetKeyConst(args, "org"));
537 const char* workstation = jsonObjectGetString(jsonObjectGetKeyConst(args, "workstation"));
538 const char* barcode = jsonObjectGetString(jsonObjectGetKeyConst(args, "barcode"));
540 const char* ws = (workstation) ? workstation : "";
543 type = OILS_AUTH_STAFF;
545 if( !( (uname || barcode) && password) ) {
546 return osrfAppRequestRespondException( ctx->session, ctx->request,
547 "username/barcode and password required for method: %s", ctx->method->name );
550 oilsEvent* response = NULL;
551 jsonObject* userObj = NULL;
552 int card_active = 1; // boolean; assume active until proven otherwise
554 // Fetch a row from the actor.usr table, by username if available,
555 // or by barcode if not.
557 userObj = oilsUtilsFetchUserByUsername( uname );
558 if( userObj && JSON_NULL == userObj->type ) {
559 jsonObjectFree( userObj );
560 userObj = NULL; // username not found
564 // Read from actor.card by barcode
566 osrfLogInfo( OSRF_LOG_MARK, "Fetching user by barcode %s", barcode );
568 jsonObject* params = jsonParseFmt("{\"barcode\":\"%s\"}", barcode);
569 jsonObject* card = oilsUtilsQuickReq(
570 "open-ils.cstore", "open-ils.cstore.direct.actor.card.search", params );
571 jsonObjectFree( params );
573 if( card && card->type != JSON_NULL ) {
574 // Determine whether the card is active
575 char* card_active_str = oilsFMGetString( card, "active" );
576 card_active = oilsUtilsIsDBTrue( card_active_str );
577 free( card_active_str );
579 // Look up the user who owns the card
580 char* userid = oilsFMGetString( card, "usr" );
581 jsonObjectFree( card );
582 params = jsonParseFmt( "[%s]", userid );
584 userObj = oilsUtilsQuickReq(
585 "open-ils.cstore", "open-ils.cstore.direct.actor.user.retrieve", params );
586 jsonObjectFree( params );
587 if( userObj && JSON_NULL == userObj->type ) {
588 // user not found (shouldn't happen, due to foreign key)
589 jsonObjectFree( userObj );
596 response = oilsNewEvent( OSRF_LOG_MARK, OILS_EVENT_AUTH_FAILED );
597 osrfLogInfo(OSRF_LOG_MARK, "failed login: username=%s, barcode=%s, workstation=%s",
598 uname, (barcode ? barcode : "(none)"), ws );
599 osrfAppRespondComplete( ctx, oilsEventToJSON(response) );
600 oilsEventFree(response);
601 return 0; // No such user
604 // Such a user exists. Now see if he or she has the right credentials.
607 passOK = oilsAuthVerifyPassword( ctx, userObj, uname, password );
609 passOK = oilsAuthVerifyPassword( ctx, userObj, barcode, password );
612 jsonObjectFree(userObj);
616 // See if the account is active
617 char* active = oilsFMGetString(userObj, "active");
618 if( !oilsUtilsIsDBTrue(active) ) {
620 response = oilsNewEvent( OSRF_LOG_MARK, "PATRON_INACTIVE" );
622 response = oilsNewEvent( OSRF_LOG_MARK, OILS_EVENT_AUTH_FAILED );
624 osrfAppRespondComplete( ctx, oilsEventToJSON(response) );
625 oilsEventFree(response);
626 jsonObjectFree(userObj);
632 osrfLogInfo( OSRF_LOG_MARK, "Fetching card by barcode %s", barcode );
635 osrfLogInfo( OSRF_LOG_MARK, "barcode %s is not active, returning event", barcode );
636 response = oilsNewEvent( OSRF_LOG_MARK, "PATRON_CARD_INACTIVE" );
637 osrfAppRespondComplete( ctx, oilsEventToJSON( response ) );
638 oilsEventFree( response );
639 jsonObjectFree( userObj );
644 // See if the user is even allowed to log in
645 if( oilsAuthCheckLoginPerm( ctx, userObj, type ) == -1 ) {
646 jsonObjectFree(userObj);
650 // If a workstation is defined, add the workstation info
651 if( workstation != NULL ) {
652 osrfLogDebug(OSRF_LOG_MARK, "Workstation is %s", workstation);
653 response = oilsAuthVerifyWorkstation( ctx, userObj, workstation );
655 jsonObjectFree(userObj);
656 osrfAppRespondComplete( ctx, oilsEventToJSON(response) );
657 oilsEventFree(response);
662 // Otherwise, use the home org as the workstation org on the user
663 char* orgid = oilsFMGetString(userObj, "home_ou");
664 oilsFMSetString(userObj, "ws_ou", orgid);
668 char* freeable_uname = NULL;
670 uname = freeable_uname = oilsFMGetString( userObj, "usrname" );
674 response = oilsAuthHandleLoginOK( userObj, uname, type, orgloc, workstation );
677 response = oilsNewEvent( OSRF_LOG_MARK, OILS_EVENT_AUTH_FAILED );
678 osrfLogInfo(OSRF_LOG_MARK, "failed login: username=%s, barcode=%s, workstation=%s",
679 uname, (barcode ? barcode : "(none)"), ws );
682 jsonObjectFree(userObj);
683 osrfAppRespondComplete( ctx, oilsEventToJSON(response) );
684 oilsEventFree(response);
687 free(freeable_uname);
694 int oilsAuthSessionDelete( osrfMethodContext* ctx ) {
695 OSRF_METHOD_VERIFY_CONTEXT(ctx);
697 const char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0) );
698 jsonObject* resp = NULL;
701 osrfLogDebug(OSRF_LOG_MARK, "Removing auth session: %s", authToken );
702 char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken ); /**/
703 osrfCacheRemove(key);
704 resp = jsonNewObject(authToken); /**/
708 osrfAppRespondComplete( ctx, resp );
709 jsonObjectFree(resp);
714 * Fetches the user object from the database and updates the user object in
715 * the cache object, which then has to be re-inserted into the cache.
716 * User object is retrieved inside a transaction to avoid replication issues.
718 static int _oilsAuthReloadUser(jsonObject* cacheObj) {
720 osrfAppSession* session;
722 jsonObject *param, *userObj, *newUserObj;
724 userObj = jsonObjectGetKey( cacheObj, "userobj" );
725 userId = oilsFMGetObjectId( userObj );
727 session = osrfAppSessionClientInit( "open-ils.cstore" );
728 osrfAppSessionConnect(session);
730 reqid = osrfAppSessionSendRequest(session, NULL, "open-ils.cstore.transaction.begin", 1);
731 omsg = osrfAppSessionRequestRecv(session, reqid, 60);
735 osrfMessageFree(omsg);
736 param = jsonNewNumberObject(userId);
737 reqid = osrfAppSessionSendRequest(session, param, "open-ils.cstore.direct.actor.user.retrieve", 1);
738 omsg = osrfAppSessionRequestRecv(session, reqid, 60);
739 jsonObjectFree(param);
742 newUserObj = jsonObjectClone( osrfMessageGetResult(omsg) );
743 osrfMessageFree(omsg);
744 reqid = osrfAppSessionSendRequest(session, NULL, "open-ils.cstore.transaction.rollback", 1);
745 omsg = osrfAppSessionRequestRecv(session, reqid, 60);
746 osrfMessageFree(omsg);
750 osrfAppSessionFree(session); // calls disconnect internally
754 // ws_ou and wsid are ephemeral and need to be manually propagated
755 // oilsFMSetString dupe()'s internally, no need to clone the string
756 oilsFMSetString(newUserObj, "wsid", oilsFMGetStringConst(userObj, "wsid"));
757 oilsFMSetString(newUserObj, "ws_ou", oilsFMGetStringConst(userObj, "ws_ou"));
759 jsonObjectRemoveKey(cacheObj, "userobj"); // this also frees the old user object
760 jsonObjectSetKey(cacheObj, "userobj", newUserObj);
764 osrfLogError(OSRF_LOG_MARK, "Error retrieving user %d from database", userId);
769 Resets the auth login timeout
770 @return The event object, OILS_EVENT_SUCCESS, or OILS_EVENT_NO_SESSION
772 static oilsEvent* _oilsAuthResetTimeout( const char* authToken, int reloadUser ) {
773 if(!authToken) return NULL;
775 oilsEvent* evt = NULL;
778 osrfLogDebug(OSRF_LOG_MARK, "Resetting auth timeout for session %s", authToken);
779 char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken );
780 jsonObject* cacheObj = osrfCacheGetObject( key );
783 osrfLogInfo(OSRF_LOG_MARK, "No user in the cache exists with key %s", key);
784 evt = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_NO_SESSION);
789 _oilsAuthReloadUser(cacheObj);
792 // Determine a new timeout value
793 jsonObject* endtime_obj = jsonObjectGetKey( cacheObj, "endtime" );
795 // Extend the current endtime by a fixed amount
796 time_t endtime = (time_t) jsonObjectGetNumber( endtime_obj );
797 int reset_interval = DEFAULT_RESET_INTERVAL;
798 const jsonObject* reset_interval_obj = jsonObjectGetKeyConst(
799 cacheObj, "reset_interval" );
800 if( reset_interval_obj ) {
801 reset_interval = (int) jsonObjectGetNumber( reset_interval_obj );
802 if( reset_interval <= 0 )
803 reset_interval = DEFAULT_RESET_INTERVAL;
806 time_t now = time( NULL );
807 time_t new_endtime = now + reset_interval;
808 if( new_endtime > endtime ) {
809 // Keep the session alive a little longer
810 jsonObjectSetNumber( endtime_obj, (double) new_endtime );
811 timeout = reset_interval;
812 osrfCachePutObject( key, cacheObj, timeout );
814 // The session isn't close to expiring, so don't reset anything.
815 // Just report the time remaining.
816 timeout = endtime - now;
819 // Reapply the existing timeout from the current time
820 timeout = (time_t) jsonObjectGetNumber( jsonObjectGetKeyConst( cacheObj, "authtime"));
821 osrfCachePutObject( key, cacheObj, timeout );
824 jsonObject* payload = jsonNewNumberObject( (double) timeout );
825 evt = oilsNewEvent2(OSRF_LOG_MARK, OILS_EVENT_SUCCESS, payload);
826 jsonObjectFree(payload);
827 jsonObjectFree(cacheObj);
834 int oilsAuthResetTimeout( osrfMethodContext* ctx ) {
835 OSRF_METHOD_VERIFY_CONTEXT(ctx);
836 const char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0));
837 double reloadUser = jsonObjectGetNumber( jsonObjectGetIndex(ctx->params, 1));
838 oilsEvent* evt = _oilsAuthResetTimeout(authToken, (int) reloadUser);
839 osrfAppRespondComplete( ctx, oilsEventToJSON(evt) );
845 int oilsAuthSessionRetrieve( osrfMethodContext* ctx ) {
846 OSRF_METHOD_VERIFY_CONTEXT(ctx);
847 bool returnFull = false;
849 const char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0));
851 if(ctx->params->size > 1) {
852 // caller wants full cached object, with authtime, etc.
853 const char* rt = jsonObjectGetString(jsonObjectGetIndex(ctx->params, 1));
854 if(rt && strcmp(rt, "0") != 0)
858 jsonObject* cacheObj = NULL;
859 oilsEvent* evt = NULL;
863 // Reset the timeout to keep the session alive
864 evt = _oilsAuthResetTimeout(authToken, 0);
866 if( evt && strcmp(evt->event, OILS_EVENT_SUCCESS) ) {
867 osrfAppRespondComplete( ctx, oilsEventToJSON( evt )); // can't reset timeout
871 // Retrieve the cached session object
872 osrfLogDebug(OSRF_LOG_MARK, "Retrieving auth session: %s", authToken);
873 char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken );
874 cacheObj = osrfCacheGetObject( key );
876 // Return a copy of the cached user object
878 osrfAppRespondComplete( ctx, cacheObj);
880 osrfAppRespondComplete( ctx, jsonObjectGetKeyConst( cacheObj, "userobj"));
881 jsonObjectFree(cacheObj);
883 // Auth token is invalid or expired
884 oilsEvent* evt2 = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_NO_SESSION);
885 osrfAppRespondComplete( ctx, oilsEventToJSON(evt2) ); /* should be event.. */
894 evt = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_NO_SESSION);
895 osrfAppRespondComplete( ctx, oilsEventToJSON(evt) );