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 int osrfAppInitialize();
24 int osrfAppChildInit();
26 static long _oilsAuthOPACTimeout = 0;
27 static long _oilsAuthStaffTimeout = 0;
28 static long _oilsAuthOverrideTimeout = 0;
29 static long _oilsAuthPersistTimeout = 0;
30 static long _oilsAuthSeedTimeout = 0;
31 static long _oilsAuthBlockTimeout = 0;
32 static long _oilsAuthBlockCount = 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 );
90 if(!_oilsAuthSeedTimeout) { /* Load the default timeouts */
92 jsonObject* value_obj;
94 value_obj = osrf_settings_host_value_object(
95 "/apps/open-ils.auth/app_settings/auth_limits/seed" );
96 _oilsAuthSeedTimeout = oilsUtilsIntervalToSeconds( jsonObjectGetString( value_obj ));
97 jsonObjectFree(value_obj);
98 if( -1 == _oilsAuthSeedTimeout ) {
99 osrfLogWarning( OSRF_LOG_MARK, "Invalid timeout for Auth Seeds - Using 30 seconds" );
100 _oilsAuthSeedTimeout = 30;
103 value_obj = osrf_settings_host_value_object(
104 "/apps/open-ils.auth/app_settings/auth_limits/block_time" );
105 _oilsAuthBlockTimeout = oilsUtilsIntervalToSeconds( jsonObjectGetString( value_obj ));
106 jsonObjectFree(value_obj);
107 if( -1 == _oilsAuthBlockTimeout ) {
108 osrfLogWarning( OSRF_LOG_MARK, "Invalid timeout for Blocking Timeout - Using 3x Seed" );
109 _oilsAuthBlockTimeout = _oilsAuthSeedTimeout * 3;
112 value_obj = osrf_settings_host_value_object(
113 "/apps/open-ils.auth/app_settings/auth_limits/block_count" );
114 _oilsAuthBlockCount = oilsUtilsIntervalToSeconds( jsonObjectGetString( value_obj ));
115 jsonObjectFree(value_obj);
116 if( -1 == _oilsAuthBlockCount ) {
117 osrfLogWarning( OSRF_LOG_MARK, "Invalid count for Blocking - Using 10" );
118 _oilsAuthBlockCount = 10;
121 osrfLogInfo(OSRF_LOG_MARK, "Set auth limits: "
122 "seed => %ld : block_timeout => %ld : block_count => %ld",
123 _oilsAuthSeedTimeout, _oilsAuthBlockTimeout, _oilsAuthBlockCount );
130 @brief Dummy placeholder for initializing a server drone.
132 There is nothing to do, so do nothing.
134 int osrfAppChildInit() {
139 @brief Implement the "init" method.
140 @param ctx The method context.
141 @return Zero if successful, or -1 if not.
146 Return to client: Intermediate authentication seed.
148 Combine the username with a timestamp and process ID, and take an md5 hash of the result.
149 Store the hash in memcache, with a key based on the username. Then return the hash to
152 However: if the username includes one or more embedded blank spaces, return a dummy
153 hash without storing anything in memcache. The dummy will never match a stored hash, so
154 any attempt to authenticate with it will fail.
156 int oilsAuthInit( osrfMethodContext* ctx ) {
157 OSRF_METHOD_VERIFY_CONTEXT(ctx);
159 char* username = jsonObjectToSimpleString( jsonObjectGetIndex(ctx->params, 0) );
164 if( strchr( username, ' ' ) ) {
166 // Embedded spaces are not allowed in a username. Use "x" as a dummy
167 // seed. It will never be a valid seed because 'x' is not a hex digit.
168 resp = jsonNewObject( "x" );
172 // Build a key and a seed; store them in memcache.
173 char* key = va_list_to_string( "%s%s", OILS_AUTH_CACHE_PRFX, username );
174 char* countkey = va_list_to_string( "%s%s%s", OILS_AUTH_CACHE_PRFX, username, OILS_AUTH_COUNT_SFFX );
175 char* seed = md5sum( "%d.%ld.%s", (int) time(NULL), (long) getpid(), username );
176 jsonObject* countobject = osrfCacheGetObject( countkey );
178 countobject = jsonNewNumberObject( (double) 0 );
180 osrfCachePutString( key, seed, _oilsAuthSeedTimeout );
181 osrfCachePutObject( countkey, countobject, _oilsAuthBlockTimeout );
183 osrfLogDebug( OSRF_LOG_MARK, "oilsAuthInit(): has seed %s and key %s", seed, key );
185 // Build a returnable object containing the seed.
186 resp = jsonNewObject( seed );
191 jsonObjectFree( countobject );
194 // Return the seed to the client.
195 osrfAppRespondComplete( ctx, resp );
197 jsonObjectFree(resp);
202 return -1; // Error: no username parameter
206 Verifies that the user has permission to login with the
207 given type. If the permission fails, an oilsEvent is returned
209 @return -1 if the permission check failed, 0 if the permission
212 static int oilsAuthCheckLoginPerm(
213 osrfMethodContext* ctx, const jsonObject* userObj, const char* type ) {
215 if(!(userObj && type)) return -1;
216 oilsEvent* perm = NULL;
218 if(!strcasecmp(type, OILS_AUTH_OPAC)) {
219 char* permissions[] = { "OPAC_LOGIN" };
220 perm = oilsUtilsCheckPerms( oilsFMGetObjectId( userObj ), -1, permissions, 1 );
222 } else if(!strcasecmp(type, OILS_AUTH_STAFF)) {
223 char* permissions[] = { "STAFF_LOGIN" };
224 perm = oilsUtilsCheckPerms( oilsFMGetObjectId( userObj ), -1, permissions, 1 );
226 } else if(!strcasecmp(type, OILS_AUTH_TEMP)) {
227 char* permissions[] = { "STAFF_LOGIN" };
228 perm = oilsUtilsCheckPerms( oilsFMGetObjectId( userObj ), -1, permissions, 1 );
229 } else if(!strcasecmp(type, OILS_AUTH_PERSIST)) {
230 char* permissions[] = { "PERSISTENT_LOGIN" };
231 perm = oilsUtilsCheckPerms( oilsFMGetObjectId( userObj ), -1, permissions, 1 );
235 osrfAppRespondComplete( ctx, oilsEventToJSON(perm) );
244 Returns 1 if the password provided matches the user's real password
249 @brief Verify the password received from the client.
250 @param ctx The method context.
251 @param userObj An object from the database, representing the user.
252 @param password An obfuscated password received from the client.
253 @return 1 if the password is valid; 0 if it isn't; or -1 upon error.
255 (None of the so-called "passwords" used here are in plaintext. All have been passed
256 through at least one layer of hashing to obfuscate them.)
258 Take the password from the user object. Append it to the username seed from memcache,
259 as stored previously by a call to the init method. Take an md5 hash of the result.
260 Then compare this hash to the password received from the client.
262 In order for the two to match, other than by dumb luck, the client had to construct
263 the password it passed in the same way. That means it neded to know not only the
264 original password (either hashed or plaintext), but also the seed. The latter requirement
265 means that the client process needs either to be the same process that called the init
266 method or to receive the seed from the process that did so.
268 static int oilsAuthVerifyPassword( const osrfMethodContext* ctx,
269 const jsonObject* userObj, const char* uname, const char* password ) {
271 // Get the username seed, as stored previously in memcache by the init method
272 char* seed = osrfCacheGetString( "%s%s", OILS_AUTH_CACHE_PRFX, uname );
274 return osrfAppRequestRespondException( ctx->session,
275 ctx->request, "No authentication seed found. "
276 "open-ils.auth.authenticate.init must be called first "
277 " (check that memcached is running and can be connected to) "
281 // We won't be needing the seed again, remove it
282 osrfCacheRemove( "%s%s", OILS_AUTH_CACHE_PRFX, uname );
284 // Get the hashed password from the user object
285 char* realPassword = oilsFMGetString( userObj, "passwd" );
287 osrfLogInternal(OSRF_LOG_MARK, "oilsAuth retrieved real password: [%s]", realPassword);
288 osrfLogDebug(OSRF_LOG_MARK, "oilsAuth retrieved seed from cache: %s", seed );
290 // Concatenate them and take an MD5 hash of the result
291 char* maskedPw = md5sum( "%s%s", seed, realPassword );
297 // This happens only if md5sum() runs out of memory
299 return -1; // md5sum() ran out of memory
302 osrfLogDebug(OSRF_LOG_MARK, "oilsAuth generated masked password %s. "
303 "Testing against provided password %s", maskedPw, password );
306 if( !strcmp( maskedPw, password ) )
311 char* countkey = va_list_to_string( "%s%s%s", OILS_AUTH_CACHE_PRFX, uname, OILS_AUTH_COUNT_SFFX );
312 jsonObject* countobject = osrfCacheGetObject( countkey );
314 long failcount = (long) jsonObjectGetNumber( countobject );
315 if(failcount >= _oilsAuthBlockCount) {
317 osrfLogInternal(OSRF_LOG_MARK, "oilsAuth found too many recent failures: %i, forcing failure state.", failcount);
322 jsonObjectSetNumber( countobject, failcount );
323 osrfCachePutObject( countkey, countobject, _oilsAuthBlockTimeout );
324 jsonObjectFree(countobject);
332 @brief Determine the login timeout.
333 @param userObj Pointer to an object describing the user.
334 @param type Pointer to one of four possible character strings identifying the login type.
335 @param orgloc Org unit to use for settings lookups (negative or zero means unspecified)
336 @return The length of the timeout, in seconds.
338 The default timeout value comes from the configuration file, and depends on the
341 The default may be overridden by a corresponding org unit setting. The @a orgloc
342 parameter says what org unit to use for the lookup. If @a orgloc <= 0, or if the
343 lookup for @a orgloc yields no result, we look up the setting for the user's home org unit
344 instead (except that if it's the same as @a orgloc we don't bother repeating the lookup).
346 Whether defined in the config file or in an org unit setting, a timeout value may be
347 expressed as a raw number (i.e. all digits, possibly with leading and/or trailing white
348 space) or as an interval string to be translated into seconds by PostgreSQL.
350 static long oilsAuthGetTimeout( const jsonObject* userObj, const char* type, int orgloc ) {
352 if(!_oilsAuthOPACTimeout) { /* Load the default timeouts */
354 jsonObject* value_obj;
356 value_obj = osrf_settings_host_value_object(
357 "/apps/open-ils.auth/app_settings/default_timeout/opac" );
358 _oilsAuthOPACTimeout = oilsUtilsIntervalToSeconds( jsonObjectGetString( value_obj ));
359 jsonObjectFree(value_obj);
360 if( -1 == _oilsAuthOPACTimeout ) {
361 osrfLogWarning( OSRF_LOG_MARK, "Invalid default timeout for OPAC logins" );
362 _oilsAuthOPACTimeout = 0;
365 value_obj = osrf_settings_host_value_object(
366 "/apps/open-ils.auth/app_settings/default_timeout/staff" );
367 _oilsAuthStaffTimeout = oilsUtilsIntervalToSeconds( jsonObjectGetString( value_obj ));
368 jsonObjectFree(value_obj);
369 if( -1 == _oilsAuthStaffTimeout ) {
370 osrfLogWarning( OSRF_LOG_MARK, "Invalid default timeout for staff logins" );
371 _oilsAuthStaffTimeout = 0;
374 value_obj = osrf_settings_host_value_object(
375 "/apps/open-ils.auth/app_settings/default_timeout/temp" );
376 _oilsAuthOverrideTimeout = oilsUtilsIntervalToSeconds( jsonObjectGetString( value_obj ));
377 jsonObjectFree(value_obj);
378 if( -1 == _oilsAuthOverrideTimeout ) {
379 osrfLogWarning( OSRF_LOG_MARK, "Invalid default timeout for temp logins" );
380 _oilsAuthOverrideTimeout = 0;
383 value_obj = osrf_settings_host_value_object(
384 "/apps/open-ils.auth/app_settings/default_timeout/persist" );
385 _oilsAuthPersistTimeout = oilsUtilsIntervalToSeconds( jsonObjectGetString( value_obj ));
386 jsonObjectFree(value_obj);
387 if( -1 == _oilsAuthPersistTimeout ) {
388 osrfLogWarning( OSRF_LOG_MARK, "Invalid default timeout for persist logins" );
389 _oilsAuthPersistTimeout = 0;
392 osrfLogInfo(OSRF_LOG_MARK, "Set default auth timeouts: "
393 "opac => %ld : staff => %ld : temp => %ld : persist => %ld",
394 _oilsAuthOPACTimeout, _oilsAuthStaffTimeout,
395 _oilsAuthOverrideTimeout, _oilsAuthPersistTimeout );
398 int home_ou = (int) jsonObjectGetNumber( oilsFMGetObject( userObj, "home_ou" ));
402 char* setting = NULL;
403 long default_timeout = 0;
405 if( !strcmp( type, OILS_AUTH_OPAC )) {
406 setting = OILS_ORG_SETTING_OPAC_TIMEOUT;
407 default_timeout = _oilsAuthOPACTimeout;
408 } else if( !strcmp( type, OILS_AUTH_STAFF )) {
409 setting = OILS_ORG_SETTING_STAFF_TIMEOUT;
410 default_timeout = _oilsAuthStaffTimeout;
411 } else if( !strcmp( type, OILS_AUTH_TEMP )) {
412 setting = OILS_ORG_SETTING_TEMP_TIMEOUT;
413 default_timeout = _oilsAuthOverrideTimeout;
414 } else if( !strcmp( type, OILS_AUTH_PERSIST )) {
415 setting = OILS_ORG_SETTING_PERSIST_TIMEOUT;
416 default_timeout = _oilsAuthPersistTimeout;
419 // Get the org unit setting, if there is one.
420 char* timeout = oilsUtilsFetchOrgSetting( orgloc, setting );
422 if( orgloc != home_ou ) {
423 osrfLogDebug(OSRF_LOG_MARK, "Auth timeout not defined for org %d, "
424 "trying home_ou %d", orgloc, home_ou );
425 timeout = oilsUtilsFetchOrgSetting( home_ou, setting );
430 return default_timeout; // No override from org unit setting
432 // Translate the org unit setting to a number
435 osrfLogWarning( OSRF_LOG_MARK,
436 "Timeout org unit setting is an empty string for %s login; using default",
440 // Treat timeout string as an interval, and convert it to seconds
441 t = oilsUtilsIntervalToSeconds( timeout );
443 // Unable to convert; possibly an invalid interval string
444 osrfLogError( OSRF_LOG_MARK,
445 "Unable to convert timeout interval \"%s\" for %s login; using default",
456 Adds the authentication token to the user cache. The timeout for the
457 auth token is based on the type of login as well as (if type=='opac')
459 Returns the event that should be returned to the user.
462 static oilsEvent* oilsAuthHandleLoginOK( jsonObject* userObj, const char* uname,
463 const char* type, int orgloc, const char* workstation ) {
468 char* wsorg = jsonObjectToSimpleString(oilsFMGetObject(userObj, "ws_ou"));
469 if(wsorg) { /* if there is a workstation, use it for the timeout */
470 osrfLogDebug( OSRF_LOG_MARK,
471 "Auth session trying workstation id %d for auth timeout", atoi(wsorg));
472 timeout = oilsAuthGetTimeout( userObj, type, atoi(wsorg) );
475 osrfLogDebug( OSRF_LOG_MARK,
476 "Auth session trying org from param [%d] for auth timeout", orgloc );
477 timeout = oilsAuthGetTimeout( userObj, type, orgloc );
479 osrfLogDebug(OSRF_LOG_MARK, "Auth session timeout for %s: %ld", uname, timeout );
481 char* string = va_list_to_string(
482 "%d.%ld.%s", (long) getpid(), time(NULL), uname );
483 char* authToken = md5sum(string);
484 char* authKey = va_list_to_string(
485 "%s%s", OILS_AUTH_CACHE_PRFX, authToken );
487 const char* ws = (workstation) ? workstation : "";
488 osrfLogActivity(OSRF_LOG_MARK,
489 "successful login: username=%s, authtoken=%s, workstation=%s", uname, authToken, ws );
491 oilsFMSetString( userObj, "passwd", "" );
492 jsonObject* cacheObj = jsonParseFmt( "{\"authtime\": %ld}", timeout );
493 jsonObjectSetKey( cacheObj, "userobj", jsonObjectClone(userObj));
495 if( !strcmp( type, OILS_AUTH_PERSIST )) {
496 // Add entries for endtime and reset_interval, so that we can gracefully
497 // extend the session a bit if the user is active toward the end of the
498 // timeout originally specified.
499 time_t endtime = time( NULL ) + timeout;
500 jsonObjectSetKey( cacheObj, "endtime", jsonNewNumberObject( (double) endtime ) );
502 // Reset interval is hard-coded for now, but if we ever want to make it
503 // configurable, this is the place to do it:
504 jsonObjectSetKey( cacheObj, "reset_interval",
505 jsonNewNumberObject( (double) DEFAULT_RESET_INTERVAL ));
508 osrfCachePutObject( authKey, cacheObj, (time_t) timeout );
509 jsonObjectFree(cacheObj);
510 osrfLogInternal(OSRF_LOG_MARK, "oilsAuthHandleLoginOK(): Placed user object into cache");
511 jsonObject* payload = jsonParseFmt(
512 "{ \"authtoken\": \"%s\", \"authtime\": %ld }", authToken, timeout );
514 response = oilsNewEvent2( OSRF_LOG_MARK, OILS_EVENT_SUCCESS, payload );
515 free(string); free(authToken); free(authKey);
516 jsonObjectFree(payload);
521 static oilsEvent* oilsAuthVerifyWorkstation(
522 const osrfMethodContext* ctx, jsonObject* userObj, const char* ws ) {
523 osrfLogInfo(OSRF_LOG_MARK, "Attaching workstation to user at login: %s", ws);
524 jsonObject* workstation = oilsUtilsFetchWorkstationByName(ws);
525 if(!workstation || workstation->type == JSON_NULL) {
526 jsonObjectFree(workstation);
527 return oilsNewEvent(OSRF_LOG_MARK, "WORKSTATION_NOT_FOUND");
529 long wsid = oilsFMGetObjectId(workstation);
530 LONG_TO_STRING(wsid);
531 char* orgid = oilsFMGetString(workstation, "owning_lib");
532 oilsFMSetString(userObj, "wsid", LONGSTR);
533 oilsFMSetString(userObj, "ws_ou", orgid);
535 jsonObjectFree(workstation);
542 @brief Implement the "complete" method.
543 @param ctx The method context.
544 @return -1 upon error; zero if successful, and if a STATUS message has been sent to the
545 client to indicate completion; a positive integer if successful but no such STATUS
546 message has been sent.
549 - a hash with some combination of the following elements:
552 - "password" (hashed with the cached seed; not plaintext)
557 The password is required. Either a username or a barcode must also be present.
559 Return to client: Intermediate authentication seed.
561 Validate the password, using the username if available, or the barcode if not. The
562 user must be active, and not barred from logging on. The barcode, if used for
563 authentication, must be active as well. The workstation, if specified, must be valid.
565 Upon deciding whether to allow the logon, return a corresponding event to the client.
567 int oilsAuthComplete( osrfMethodContext* ctx ) {
568 OSRF_METHOD_VERIFY_CONTEXT(ctx);
570 const jsonObject* args = jsonObjectGetIndex(ctx->params, 0);
572 const char* uname = jsonObjectGetString(jsonObjectGetKeyConst(args, "username"));
573 const char* password = jsonObjectGetString(jsonObjectGetKeyConst(args, "password"));
574 const char* type = jsonObjectGetString(jsonObjectGetKeyConst(args, "type"));
575 int orgloc = (int) jsonObjectGetNumber(jsonObjectGetKeyConst(args, "org"));
576 const char* workstation = jsonObjectGetString(jsonObjectGetKeyConst(args, "workstation"));
577 const char* barcode = jsonObjectGetString(jsonObjectGetKeyConst(args, "barcode"));
579 const char* ws = (workstation) ? workstation : "";
582 type = OILS_AUTH_STAFF;
584 if( !( (uname || barcode) && password) ) {
585 return osrfAppRequestRespondException( ctx->session, ctx->request,
586 "username/barcode and password required for method: %s", ctx->method->name );
589 oilsEvent* response = NULL;
590 jsonObject* userObj = NULL;
591 int card_active = 1; // boolean; assume active until proven otherwise
593 // Fetch a row from the actor.usr table, by username if available,
594 // or by barcode if not.
596 userObj = oilsUtilsFetchUserByUsername( uname );
597 if( userObj && JSON_NULL == userObj->type ) {
598 jsonObjectFree( userObj );
599 userObj = NULL; // username not found
603 // Read from actor.card by barcode
605 osrfLogInfo( OSRF_LOG_MARK, "Fetching user by barcode %s", barcode );
607 jsonObject* params = jsonParseFmt("{\"barcode\":\"%s\"}", barcode);
608 jsonObject* card = oilsUtilsQuickReq(
609 "open-ils.cstore", "open-ils.cstore.direct.actor.card.search", params );
610 jsonObjectFree( params );
612 if( card && card->type != JSON_NULL ) {
613 // Determine whether the card is active
614 char* card_active_str = oilsFMGetString( card, "active" );
615 card_active = oilsUtilsIsDBTrue( card_active_str );
616 free( card_active_str );
618 // Look up the user who owns the card
619 char* userid = oilsFMGetString( card, "usr" );
620 jsonObjectFree( card );
621 params = jsonParseFmt( "[%s]", userid );
623 userObj = oilsUtilsQuickReq(
624 "open-ils.cstore", "open-ils.cstore.direct.actor.user.retrieve", params );
625 jsonObjectFree( params );
626 if( userObj && JSON_NULL == userObj->type ) {
627 // user not found (shouldn't happen, due to foreign key)
628 jsonObjectFree( userObj );
635 response = oilsNewEvent( OSRF_LOG_MARK, OILS_EVENT_AUTH_FAILED );
636 osrfLogInfo(OSRF_LOG_MARK, "failed login: username=%s, barcode=%s, workstation=%s",
637 uname, (barcode ? barcode : "(none)"), ws );
638 osrfAppRespondComplete( ctx, oilsEventToJSON(response) );
639 oilsEventFree(response);
640 return 0; // No such user
643 // Such a user exists. Now see if he or she has the right credentials.
646 passOK = oilsAuthVerifyPassword( ctx, userObj, uname, password );
648 passOK = oilsAuthVerifyPassword( ctx, userObj, barcode, password );
651 jsonObjectFree(userObj);
655 // See if the account is active
656 char* active = oilsFMGetString(userObj, "active");
657 if( !oilsUtilsIsDBTrue(active) ) {
659 response = oilsNewEvent( OSRF_LOG_MARK, "PATRON_INACTIVE" );
661 response = oilsNewEvent( OSRF_LOG_MARK, OILS_EVENT_AUTH_FAILED );
663 osrfAppRespondComplete( ctx, oilsEventToJSON(response) );
664 oilsEventFree(response);
665 jsonObjectFree(userObj);
671 osrfLogInfo( OSRF_LOG_MARK, "Fetching card by barcode %s", barcode );
674 osrfLogInfo( OSRF_LOG_MARK, "barcode %s is not active, returning event", barcode );
675 response = oilsNewEvent( OSRF_LOG_MARK, "PATRON_CARD_INACTIVE" );
676 osrfAppRespondComplete( ctx, oilsEventToJSON( response ) );
677 oilsEventFree( response );
678 jsonObjectFree( userObj );
683 // See if the user is even allowed to log in
684 if( oilsAuthCheckLoginPerm( ctx, userObj, type ) == -1 ) {
685 jsonObjectFree(userObj);
689 // If a workstation is defined, add the workstation info
690 if( workstation != NULL ) {
691 osrfLogDebug(OSRF_LOG_MARK, "Workstation is %s", workstation);
692 response = oilsAuthVerifyWorkstation( ctx, userObj, workstation );
694 jsonObjectFree(userObj);
695 osrfAppRespondComplete( ctx, oilsEventToJSON(response) );
696 oilsEventFree(response);
701 // Otherwise, use the home org as the workstation org on the user
702 char* orgid = oilsFMGetString(userObj, "home_ou");
703 oilsFMSetString(userObj, "ws_ou", orgid);
707 char* freeable_uname = NULL;
709 uname = freeable_uname = oilsFMGetString( userObj, "usrname" );
713 response = oilsAuthHandleLoginOK( userObj, uname, type, orgloc, workstation );
716 response = oilsNewEvent( OSRF_LOG_MARK, OILS_EVENT_AUTH_FAILED );
717 osrfLogInfo(OSRF_LOG_MARK, "failed login: username=%s, barcode=%s, workstation=%s",
718 uname, (barcode ? barcode : "(none)"), ws );
721 jsonObjectFree(userObj);
722 osrfAppRespondComplete( ctx, oilsEventToJSON(response) );
723 oilsEventFree(response);
726 free(freeable_uname);
733 int oilsAuthSessionDelete( osrfMethodContext* ctx ) {
734 OSRF_METHOD_VERIFY_CONTEXT(ctx);
736 const char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0) );
737 jsonObject* resp = NULL;
740 osrfLogDebug(OSRF_LOG_MARK, "Removing auth session: %s", authToken );
741 char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken ); /**/
742 osrfCacheRemove(key);
743 resp = jsonNewObject(authToken); /**/
747 osrfAppRespondComplete( ctx, resp );
748 jsonObjectFree(resp);
753 * Fetches the user object from the database and updates the user object in
754 * the cache object, which then has to be re-inserted into the cache.
755 * User object is retrieved inside a transaction to avoid replication issues.
757 static int _oilsAuthReloadUser(jsonObject* cacheObj) {
759 osrfAppSession* session;
761 jsonObject *param, *userObj, *newUserObj;
763 userObj = jsonObjectGetKey( cacheObj, "userobj" );
764 userId = oilsFMGetObjectId( userObj );
766 session = osrfAppSessionClientInit( "open-ils.cstore" );
767 osrfAppSessionConnect(session);
769 reqid = osrfAppSessionSendRequest(session, NULL, "open-ils.cstore.transaction.begin", 1);
770 omsg = osrfAppSessionRequestRecv(session, reqid, 60);
774 osrfMessageFree(omsg);
775 param = jsonNewNumberObject(userId);
776 reqid = osrfAppSessionSendRequest(session, param, "open-ils.cstore.direct.actor.user.retrieve", 1);
777 omsg = osrfAppSessionRequestRecv(session, reqid, 60);
778 jsonObjectFree(param);
781 newUserObj = jsonObjectClone( osrfMessageGetResult(omsg) );
782 osrfMessageFree(omsg);
783 reqid = osrfAppSessionSendRequest(session, NULL, "open-ils.cstore.transaction.rollback", 1);
784 omsg = osrfAppSessionRequestRecv(session, reqid, 60);
785 osrfMessageFree(omsg);
789 osrfAppSessionFree(session); // calls disconnect internally
793 // ws_ou and wsid are ephemeral and need to be manually propagated
794 // oilsFMSetString dupe()'s internally, no need to clone the string
795 oilsFMSetString(newUserObj, "wsid", oilsFMGetStringConst(userObj, "wsid"));
796 oilsFMSetString(newUserObj, "ws_ou", oilsFMGetStringConst(userObj, "ws_ou"));
798 jsonObjectRemoveKey(cacheObj, "userobj"); // this also frees the old user object
799 jsonObjectSetKey(cacheObj, "userobj", newUserObj);
803 osrfLogError(OSRF_LOG_MARK, "Error retrieving user %d from database", userId);
808 Resets the auth login timeout
809 @return The event object, OILS_EVENT_SUCCESS, or OILS_EVENT_NO_SESSION
811 static oilsEvent* _oilsAuthResetTimeout( const char* authToken, int reloadUser ) {
812 if(!authToken) return NULL;
814 oilsEvent* evt = NULL;
817 osrfLogDebug(OSRF_LOG_MARK, "Resetting auth timeout for session %s", authToken);
818 char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken );
819 jsonObject* cacheObj = osrfCacheGetObject( key );
822 osrfLogInfo(OSRF_LOG_MARK, "No user in the cache exists with key %s", key);
823 evt = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_NO_SESSION);
828 _oilsAuthReloadUser(cacheObj);
831 // Determine a new timeout value
832 jsonObject* endtime_obj = jsonObjectGetKey( cacheObj, "endtime" );
834 // Extend the current endtime by a fixed amount
835 time_t endtime = (time_t) jsonObjectGetNumber( endtime_obj );
836 int reset_interval = DEFAULT_RESET_INTERVAL;
837 const jsonObject* reset_interval_obj = jsonObjectGetKeyConst(
838 cacheObj, "reset_interval" );
839 if( reset_interval_obj ) {
840 reset_interval = (int) jsonObjectGetNumber( reset_interval_obj );
841 if( reset_interval <= 0 )
842 reset_interval = DEFAULT_RESET_INTERVAL;
845 time_t now = time( NULL );
846 time_t new_endtime = now + reset_interval;
847 if( new_endtime > endtime ) {
848 // Keep the session alive a little longer
849 jsonObjectSetNumber( endtime_obj, (double) new_endtime );
850 timeout = reset_interval;
851 osrfCachePutObject( key, cacheObj, timeout );
853 // The session isn't close to expiring, so don't reset anything.
854 // Just report the time remaining.
855 timeout = endtime - now;
858 // Reapply the existing timeout from the current time
859 timeout = (time_t) jsonObjectGetNumber( jsonObjectGetKeyConst( cacheObj, "authtime"));
860 osrfCachePutObject( key, cacheObj, timeout );
863 jsonObject* payload = jsonNewNumberObject( (double) timeout );
864 evt = oilsNewEvent2(OSRF_LOG_MARK, OILS_EVENT_SUCCESS, payload);
865 jsonObjectFree(payload);
866 jsonObjectFree(cacheObj);
873 int oilsAuthResetTimeout( osrfMethodContext* ctx ) {
874 OSRF_METHOD_VERIFY_CONTEXT(ctx);
875 const char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0));
876 double reloadUser = jsonObjectGetNumber( jsonObjectGetIndex(ctx->params, 1));
877 oilsEvent* evt = _oilsAuthResetTimeout(authToken, (int) reloadUser);
878 osrfAppRespondComplete( ctx, oilsEventToJSON(evt) );
884 int oilsAuthSessionRetrieve( osrfMethodContext* ctx ) {
885 OSRF_METHOD_VERIFY_CONTEXT(ctx);
886 bool returnFull = false;
888 const char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0));
890 if(ctx->params->size > 1) {
891 // caller wants full cached object, with authtime, etc.
892 const char* rt = jsonObjectGetString(jsonObjectGetIndex(ctx->params, 1));
893 if(rt && strcmp(rt, "0") != 0)
897 jsonObject* cacheObj = NULL;
898 oilsEvent* evt = NULL;
902 // Reset the timeout to keep the session alive
903 evt = _oilsAuthResetTimeout(authToken, 0);
905 if( evt && strcmp(evt->event, OILS_EVENT_SUCCESS) ) {
906 osrfAppRespondComplete( ctx, oilsEventToJSON( evt )); // can't reset timeout
910 // Retrieve the cached session object
911 osrfLogDebug(OSRF_LOG_MARK, "Retrieving auth session: %s", authToken);
912 char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken );
913 cacheObj = osrfCacheGetObject( key );
915 // Return a copy of the cached user object
917 osrfAppRespondComplete( ctx, cacheObj);
919 osrfAppRespondComplete( ctx, jsonObjectGetKeyConst( cacheObj, "userobj"));
920 jsonObjectFree(cacheObj);
922 // Auth token is invalid or expired
923 oilsEvent* evt2 = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_NO_SESSION);
924 osrfAppRespondComplete( ctx, oilsEventToJSON(evt2) ); /* should be event.. */
933 evt = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_NO_SESSION);
934 osrfAppRespondComplete( ctx, oilsEventToJSON(evt) );