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.authenticate.verify",
69 "Verifies the user provided a valid username and password."
70 "Params and are the same as open-ils.auth.authenticate.complete."
71 "Returns SUCCESS event on success, failure event on failure", 1, 0);
74 osrfAppRegisterMethod(
76 "open-ils.auth.session.retrieve",
77 "oilsAuthSessionRetrieve",
78 "Pass in the auth token and this retrieves the user object. The auth "
79 "timeout is reset when this call is made "
80 "Returns the user object (password blanked) for the given login session "
81 "PARAMS( authToken )", 1, 0 );
83 osrfAppRegisterMethod(
85 "open-ils.auth.session.delete",
86 "oilsAuthSessionDelete",
87 "Destroys the given login session "
88 "PARAMS( authToken )", 1, 0 );
90 osrfAppRegisterMethod(
92 "open-ils.auth.session.reset_timeout",
93 "oilsAuthResetTimeout",
94 "Resets the login timeout for the given session "
95 "Returns an ILS Event with payload = session_timeout of session "
96 "if found, otherwise returns the NO_SESSION event"
97 "PARAMS( authToken )", 1, 0 );
99 if(!_oilsAuthSeedTimeout) { /* Load the default timeouts */
101 jsonObject* value_obj;
103 value_obj = osrf_settings_host_value_object(
104 "/apps/open-ils.auth/app_settings/auth_limits/seed" );
105 _oilsAuthSeedTimeout = oilsUtilsIntervalToSeconds( jsonObjectGetString( value_obj ));
106 jsonObjectFree(value_obj);
107 if( -1 == _oilsAuthSeedTimeout ) {
108 osrfLogWarning( OSRF_LOG_MARK, "Invalid timeout for Auth Seeds - Using 30 seconds" );
109 _oilsAuthSeedTimeout = 30;
112 value_obj = osrf_settings_host_value_object(
113 "/apps/open-ils.auth/app_settings/auth_limits/block_time" );
114 _oilsAuthBlockTimeout = oilsUtilsIntervalToSeconds( jsonObjectGetString( value_obj ));
115 jsonObjectFree(value_obj);
116 if( -1 == _oilsAuthBlockTimeout ) {
117 osrfLogWarning( OSRF_LOG_MARK, "Invalid timeout for Blocking Timeout - Using 3x Seed" );
118 _oilsAuthBlockTimeout = _oilsAuthSeedTimeout * 3;
121 value_obj = osrf_settings_host_value_object(
122 "/apps/open-ils.auth/app_settings/auth_limits/block_count" );
123 _oilsAuthBlockCount = oilsUtilsIntervalToSeconds( jsonObjectGetString( value_obj ));
124 jsonObjectFree(value_obj);
125 if( -1 == _oilsAuthBlockCount ) {
126 osrfLogWarning( OSRF_LOG_MARK, "Invalid count for Blocking - Using 10" );
127 _oilsAuthBlockCount = 10;
130 osrfLogInfo(OSRF_LOG_MARK, "Set auth limits: "
131 "seed => %ld : block_timeout => %ld : block_count => %ld",
132 _oilsAuthSeedTimeout, _oilsAuthBlockTimeout, _oilsAuthBlockCount );
139 @brief Dummy placeholder for initializing a server drone.
141 There is nothing to do, so do nothing.
143 int osrfAppChildInit() {
148 @brief Implement the "init" method.
149 @param ctx The method context.
150 @return Zero if successful, or -1 if not.
155 Return to client: Intermediate authentication seed.
157 Combine the username with a timestamp and process ID, and take an md5 hash of the result.
158 Store the hash in memcache, with a key based on the username. Then return the hash to
161 However: if the username includes one or more embedded blank spaces, return a dummy
162 hash without storing anything in memcache. The dummy will never match a stored hash, so
163 any attempt to authenticate with it will fail.
165 int oilsAuthInit( osrfMethodContext* ctx ) {
166 OSRF_METHOD_VERIFY_CONTEXT(ctx);
168 char* username = jsonObjectToSimpleString( jsonObjectGetIndex(ctx->params, 0) );
173 if( strchr( username, ' ' ) ) {
175 // Embedded spaces are not allowed in a username. Use "x" as a dummy
176 // seed. It will never be a valid seed because 'x' is not a hex digit.
177 resp = jsonNewObject( "x" );
181 // Build a key and a seed; store them in memcache.
182 char* key = va_list_to_string( "%s%s", OILS_AUTH_CACHE_PRFX, username );
183 char* countkey = va_list_to_string( "%s%s%s", OILS_AUTH_CACHE_PRFX, username, OILS_AUTH_COUNT_SFFX );
184 char* seed = md5sum( "%d.%ld.%s", (int) time(NULL), (long) getpid(), username );
185 jsonObject* countobject = osrfCacheGetObject( countkey );
187 countobject = jsonNewNumberObject( (double) 0 );
189 osrfCachePutString( key, seed, _oilsAuthSeedTimeout );
190 osrfCachePutObject( countkey, countobject, _oilsAuthBlockTimeout );
192 osrfLogDebug( OSRF_LOG_MARK, "oilsAuthInit(): has seed %s and key %s", seed, key );
194 // Build a returnable object containing the seed.
195 resp = jsonNewObject( seed );
200 jsonObjectFree( countobject );
203 // Return the seed to the client.
204 osrfAppRespondComplete( ctx, resp );
206 jsonObjectFree(resp);
211 return -1; // Error: no username parameter
215 Verifies that the user has permission to login with the
216 given type. If the permission fails, an oilsEvent is returned
218 @return -1 if the permission check failed, 0 if the permission
221 static int oilsAuthCheckLoginPerm(
222 osrfMethodContext* ctx, const jsonObject* userObj, const char* type ) {
224 if(!(userObj && type)) return -1;
225 oilsEvent* perm = NULL;
227 if(!strcasecmp(type, OILS_AUTH_OPAC)) {
228 char* permissions[] = { "OPAC_LOGIN" };
229 perm = oilsUtilsCheckPerms( oilsFMGetObjectId( userObj ), -1, permissions, 1 );
231 } else if(!strcasecmp(type, OILS_AUTH_STAFF)) {
232 char* permissions[] = { "STAFF_LOGIN" };
233 perm = oilsUtilsCheckPerms( oilsFMGetObjectId( userObj ), -1, permissions, 1 );
235 } else if(!strcasecmp(type, OILS_AUTH_TEMP)) {
236 char* permissions[] = { "STAFF_LOGIN" };
237 perm = oilsUtilsCheckPerms( oilsFMGetObjectId( userObj ), -1, permissions, 1 );
238 } else if(!strcasecmp(type, OILS_AUTH_PERSIST)) {
239 char* permissions[] = { "PERSISTENT_LOGIN" };
240 perm = oilsUtilsCheckPerms( oilsFMGetObjectId( userObj ), -1, permissions, 1 );
244 osrfAppRespondComplete( ctx, oilsEventToJSON(perm) );
253 Returns 1 if the password provided matches the user's real password
258 @brief Verify the password received from the client.
259 @param ctx The method context.
260 @param userObj An object from the database, representing the user.
261 @param password An obfuscated password received from the client.
262 @return 1 if the password is valid; 0 if it isn't; or -1 upon error.
264 (None of the so-called "passwords" used here are in plaintext. All have been passed
265 through at least one layer of hashing to obfuscate them.)
267 Take the password from the user object. Append it to the username seed from memcache,
268 as stored previously by a call to the init method. Take an md5 hash of the result.
269 Then compare this hash to the password received from the client.
271 In order for the two to match, other than by dumb luck, the client had to construct
272 the password it passed in the same way. That means it neded to know not only the
273 original password (either hashed or plaintext), but also the seed. The latter requirement
274 means that the client process needs either to be the same process that called the init
275 method or to receive the seed from the process that did so.
277 static int oilsAuthVerifyPassword( const osrfMethodContext* ctx,
278 const jsonObject* userObj, const char* uname, const char* password ) {
280 // Get the username seed, as stored previously in memcache by the init method
281 char* seed = osrfCacheGetString( "%s%s", OILS_AUTH_CACHE_PRFX, uname );
283 return osrfAppRequestRespondException( ctx->session,
284 ctx->request, "No authentication seed found. "
285 "open-ils.auth.authenticate.init must be called first "
286 " (check that memcached is running and can be connected to) "
290 // We won't be needing the seed again, remove it
291 osrfCacheRemove( "%s%s", OILS_AUTH_CACHE_PRFX, uname );
293 // Get the hashed password from the user object
294 char* realPassword = oilsFMGetString( userObj, "passwd" );
296 osrfLogInternal(OSRF_LOG_MARK, "oilsAuth retrieved real password: [%s]", realPassword);
297 osrfLogDebug(OSRF_LOG_MARK, "oilsAuth retrieved seed from cache: %s", seed );
299 // Concatenate them and take an MD5 hash of the result
300 char* maskedPw = md5sum( "%s%s", seed, realPassword );
306 // This happens only if md5sum() runs out of memory
308 return -1; // md5sum() ran out of memory
311 osrfLogDebug(OSRF_LOG_MARK, "oilsAuth generated masked password %s. "
312 "Testing against provided password %s", maskedPw, password );
315 if( !strcmp( maskedPw, password ) )
320 char* countkey = va_list_to_string( "%s%s%s", OILS_AUTH_CACHE_PRFX, uname, OILS_AUTH_COUNT_SFFX );
321 jsonObject* countobject = osrfCacheGetObject( countkey );
323 long failcount = (long) jsonObjectGetNumber( countobject );
324 if(failcount >= _oilsAuthBlockCount) {
326 osrfLogInfo(OSRF_LOG_MARK, "oilsAuth found too many recent failures for '%s' : %i, forcing failure state.", uname, failcount);
331 jsonObjectSetNumber( countobject, failcount );
332 osrfCachePutObject( countkey, countobject, _oilsAuthBlockTimeout );
333 jsonObjectFree(countobject);
341 @brief Determine the login timeout.
342 @param userObj Pointer to an object describing the user.
343 @param type Pointer to one of four possible character strings identifying the login type.
344 @param orgloc Org unit to use for settings lookups (negative or zero means unspecified)
345 @return The length of the timeout, in seconds.
347 The default timeout value comes from the configuration file, and depends on the
350 The default may be overridden by a corresponding org unit setting. The @a orgloc
351 parameter says what org unit to use for the lookup. If @a orgloc <= 0, or if the
352 lookup for @a orgloc yields no result, we look up the setting for the user's home org unit
353 instead (except that if it's the same as @a orgloc we don't bother repeating the lookup).
355 Whether defined in the config file or in an org unit setting, a timeout value may be
356 expressed as a raw number (i.e. all digits, possibly with leading and/or trailing white
357 space) or as an interval string to be translated into seconds by PostgreSQL.
359 static long oilsAuthGetTimeout( const jsonObject* userObj, const char* type, int orgloc ) {
361 if(!_oilsAuthOPACTimeout) { /* Load the default timeouts */
363 jsonObject* value_obj;
365 value_obj = osrf_settings_host_value_object(
366 "/apps/open-ils.auth/app_settings/default_timeout/opac" );
367 _oilsAuthOPACTimeout = oilsUtilsIntervalToSeconds( jsonObjectGetString( value_obj ));
368 jsonObjectFree(value_obj);
369 if( -1 == _oilsAuthOPACTimeout ) {
370 osrfLogWarning( OSRF_LOG_MARK, "Invalid default timeout for OPAC logins" );
371 _oilsAuthOPACTimeout = 0;
374 value_obj = osrf_settings_host_value_object(
375 "/apps/open-ils.auth/app_settings/default_timeout/staff" );
376 _oilsAuthStaffTimeout = oilsUtilsIntervalToSeconds( jsonObjectGetString( value_obj ));
377 jsonObjectFree(value_obj);
378 if( -1 == _oilsAuthStaffTimeout ) {
379 osrfLogWarning( OSRF_LOG_MARK, "Invalid default timeout for staff logins" );
380 _oilsAuthStaffTimeout = 0;
383 value_obj = osrf_settings_host_value_object(
384 "/apps/open-ils.auth/app_settings/default_timeout/temp" );
385 _oilsAuthOverrideTimeout = oilsUtilsIntervalToSeconds( jsonObjectGetString( value_obj ));
386 jsonObjectFree(value_obj);
387 if( -1 == _oilsAuthOverrideTimeout ) {
388 osrfLogWarning( OSRF_LOG_MARK, "Invalid default timeout for temp logins" );
389 _oilsAuthOverrideTimeout = 0;
392 value_obj = osrf_settings_host_value_object(
393 "/apps/open-ils.auth/app_settings/default_timeout/persist" );
394 _oilsAuthPersistTimeout = oilsUtilsIntervalToSeconds( jsonObjectGetString( value_obj ));
395 jsonObjectFree(value_obj);
396 if( -1 == _oilsAuthPersistTimeout ) {
397 osrfLogWarning( OSRF_LOG_MARK, "Invalid default timeout for persist logins" );
398 _oilsAuthPersistTimeout = 0;
401 osrfLogInfo(OSRF_LOG_MARK, "Set default auth timeouts: "
402 "opac => %ld : staff => %ld : temp => %ld : persist => %ld",
403 _oilsAuthOPACTimeout, _oilsAuthStaffTimeout,
404 _oilsAuthOverrideTimeout, _oilsAuthPersistTimeout );
407 int home_ou = (int) jsonObjectGetNumber( oilsFMGetObject( userObj, "home_ou" ));
411 char* setting = NULL;
412 long default_timeout = 0;
414 if( !strcmp( type, OILS_AUTH_OPAC )) {
415 setting = OILS_ORG_SETTING_OPAC_TIMEOUT;
416 default_timeout = _oilsAuthOPACTimeout;
417 } else if( !strcmp( type, OILS_AUTH_STAFF )) {
418 setting = OILS_ORG_SETTING_STAFF_TIMEOUT;
419 default_timeout = _oilsAuthStaffTimeout;
420 } else if( !strcmp( type, OILS_AUTH_TEMP )) {
421 setting = OILS_ORG_SETTING_TEMP_TIMEOUT;
422 default_timeout = _oilsAuthOverrideTimeout;
423 } else if( !strcmp( type, OILS_AUTH_PERSIST )) {
424 setting = OILS_ORG_SETTING_PERSIST_TIMEOUT;
425 default_timeout = _oilsAuthPersistTimeout;
428 // Get the org unit setting, if there is one.
429 char* timeout = oilsUtilsFetchOrgSetting( orgloc, setting );
431 if( orgloc != home_ou ) {
432 osrfLogDebug(OSRF_LOG_MARK, "Auth timeout not defined for org %d, "
433 "trying home_ou %d", orgloc, home_ou );
434 timeout = oilsUtilsFetchOrgSetting( home_ou, setting );
439 return default_timeout; // No override from org unit setting
441 // Translate the org unit setting to a number
444 osrfLogWarning( OSRF_LOG_MARK,
445 "Timeout org unit setting is an empty string for %s login; using default",
449 // Treat timeout string as an interval, and convert it to seconds
450 t = oilsUtilsIntervalToSeconds( timeout );
452 // Unable to convert; possibly an invalid interval string
453 osrfLogError( OSRF_LOG_MARK,
454 "Unable to convert timeout interval \"%s\" for %s login; using default",
465 Adds the authentication token to the user cache. The timeout for the
466 auth token is based on the type of login as well as (if type=='opac')
468 Returns the event that should be returned to the user.
471 static oilsEvent* oilsAuthHandleLoginOK( jsonObject* userObj, const char* uname,
472 const char* type, int orgloc, const char* workstation ) {
477 char* wsorg = jsonObjectToSimpleString(oilsFMGetObject(userObj, "ws_ou"));
478 if(wsorg) { /* if there is a workstation, use it for the timeout */
479 osrfLogDebug( OSRF_LOG_MARK,
480 "Auth session trying workstation id %d for auth timeout", atoi(wsorg));
481 timeout = oilsAuthGetTimeout( userObj, type, atoi(wsorg) );
484 osrfLogDebug( OSRF_LOG_MARK,
485 "Auth session trying org from param [%d] for auth timeout", orgloc );
486 timeout = oilsAuthGetTimeout( userObj, type, orgloc );
488 osrfLogDebug(OSRF_LOG_MARK, "Auth session timeout for %s: %ld", uname, timeout );
490 char* string = va_list_to_string(
491 "%d.%ld.%s", (long) getpid(), time(NULL), uname );
492 char* authToken = md5sum(string);
493 char* authKey = va_list_to_string(
494 "%s%s", OILS_AUTH_CACHE_PRFX, authToken );
496 const char* ws = (workstation) ? workstation : "";
497 osrfLogActivity(OSRF_LOG_MARK,
498 "successful login: username=%s, authtoken=%s, workstation=%s", uname, authToken, ws );
500 oilsFMSetString( userObj, "passwd", "" );
501 jsonObject* cacheObj = jsonParseFmt( "{\"authtime\": %ld}", timeout );
502 jsonObjectSetKey( cacheObj, "userobj", jsonObjectClone(userObj));
504 if( !strcmp( type, OILS_AUTH_PERSIST )) {
505 // Add entries for endtime and reset_interval, so that we can gracefully
506 // extend the session a bit if the user is active toward the end of the
507 // timeout originally specified.
508 time_t endtime = time( NULL ) + timeout;
509 jsonObjectSetKey( cacheObj, "endtime", jsonNewNumberObject( (double) endtime ) );
511 // Reset interval is hard-coded for now, but if we ever want to make it
512 // configurable, this is the place to do it:
513 jsonObjectSetKey( cacheObj, "reset_interval",
514 jsonNewNumberObject( (double) DEFAULT_RESET_INTERVAL ));
517 osrfCachePutObject( authKey, cacheObj, (time_t) timeout );
518 jsonObjectFree(cacheObj);
519 osrfLogInternal(OSRF_LOG_MARK, "oilsAuthHandleLoginOK(): Placed user object into cache");
520 jsonObject* payload = jsonParseFmt(
521 "{ \"authtoken\": \"%s\", \"authtime\": %ld }", authToken, timeout );
523 response = oilsNewEvent2( OSRF_LOG_MARK, OILS_EVENT_SUCCESS, payload );
524 free(string); free(authToken); free(authKey);
525 jsonObjectFree(payload);
530 static oilsEvent* oilsAuthVerifyWorkstation(
531 const osrfMethodContext* ctx, jsonObject* userObj, const char* ws ) {
532 osrfLogInfo(OSRF_LOG_MARK, "Attaching workstation to user at login: %s", ws);
533 jsonObject* workstation = oilsUtilsFetchWorkstationByName(ws);
534 if(!workstation || workstation->type == JSON_NULL) {
535 jsonObjectFree(workstation);
536 return oilsNewEvent(OSRF_LOG_MARK, "WORKSTATION_NOT_FOUND");
538 long wsid = oilsFMGetObjectId(workstation);
539 LONG_TO_STRING(wsid);
540 char* orgid = oilsFMGetString(workstation, "owning_lib");
541 oilsFMSetString(userObj, "wsid", LONGSTR);
542 oilsFMSetString(userObj, "ws_ou", orgid);
544 jsonObjectFree(workstation);
551 @brief Implement the "complete" method.
552 @param ctx The method context.
553 @return -1 upon error; zero if successful, and if a STATUS message has been sent to the
554 client to indicate completion; a positive integer if successful but no such STATUS
555 message has been sent.
558 - a hash with some combination of the following elements:
561 - "password" (hashed with the cached seed; not plaintext)
565 - "agent" (what software/interface/3rd-party is making the request)
567 The password is required. Either a username or a barcode must also be present.
569 Return to client: Intermediate authentication seed.
571 Validate the password, using the username if available, or the barcode if not. The
572 user must be active, and not barred from logging on. The barcode, if used for
573 authentication, must be active as well. The workstation, if specified, must be valid.
575 Upon deciding whether to allow the logon, return a corresponding event to the client.
577 int oilsAuthComplete( osrfMethodContext* ctx ) {
578 OSRF_METHOD_VERIFY_CONTEXT(ctx);
580 const jsonObject* args = jsonObjectGetIndex(ctx->params, 0);
582 const char* uname = jsonObjectGetString(jsonObjectGetKeyConst(args, "username"));
583 const char* password = jsonObjectGetString(jsonObjectGetKeyConst(args, "password"));
584 const char* type = jsonObjectGetString(jsonObjectGetKeyConst(args, "type"));
585 int orgloc = (int) jsonObjectGetNumber(jsonObjectGetKeyConst(args, "org"));
586 const char* workstation = jsonObjectGetString(jsonObjectGetKeyConst(args, "workstation"));
587 const char* barcode = jsonObjectGetString(jsonObjectGetKeyConst(args, "barcode"));
588 const char* ewho = jsonObjectGetString(jsonObjectGetKeyConst(args, "agent"));
590 const char* ws = (workstation) ? workstation : "";
593 type = OILS_AUTH_STAFF;
595 if( !( (uname || barcode) && password) ) {
596 return osrfAppRequestRespondException( ctx->session, ctx->request,
597 "username/barcode and password required for method: %s", ctx->method->name );
600 oilsEvent* response = NULL;
601 jsonObject* userObj = NULL;
602 int card_active = 1; // boolean; assume active until proven otherwise
604 // Fetch a row from the actor.usr table, by username if available,
605 // or by barcode if not.
607 userObj = oilsUtilsFetchUserByUsername( uname );
608 if( userObj && JSON_NULL == userObj->type ) {
609 jsonObjectFree( userObj );
610 userObj = NULL; // username not found
614 // Read from actor.card by barcode
616 osrfLogInfo( OSRF_LOG_MARK, "Fetching user by barcode %s", barcode );
618 jsonObject* params = jsonParseFmt("{\"barcode\":\"%s\"}", barcode);
619 jsonObject* card = oilsUtilsQuickReq(
620 "open-ils.cstore", "open-ils.cstore.direct.actor.card.search", params );
621 jsonObjectFree( params );
623 if( card && card->type != JSON_NULL ) {
624 // Determine whether the card is active
625 char* card_active_str = oilsFMGetString( card, "active" );
626 card_active = oilsUtilsIsDBTrue( card_active_str );
627 free( card_active_str );
629 // Look up the user who owns the card
630 char* userid = oilsFMGetString( card, "usr" );
631 jsonObjectFree( card );
632 params = jsonParseFmt( "[%s]", userid );
634 userObj = oilsUtilsQuickReq(
635 "open-ils.cstore", "open-ils.cstore.direct.actor.user.retrieve", params );
636 jsonObjectFree( params );
637 if( userObj && JSON_NULL == userObj->type ) {
638 // user not found (shouldn't happen, due to foreign key)
639 jsonObjectFree( userObj );
646 response = oilsNewEvent( OSRF_LOG_MARK, OILS_EVENT_AUTH_FAILED );
647 osrfLogInfo(OSRF_LOG_MARK, "failed login: username=%s, barcode=%s, workstation=%s",
648 uname, (barcode ? barcode : "(none)"), ws );
649 osrfAppRespondComplete( ctx, oilsEventToJSON(response) );
650 oilsEventFree(response);
651 return 0; // No such user
654 // Such a user exists. Now see if he or she has the right credentials.
657 passOK = oilsAuthVerifyPassword( ctx, userObj, uname, password );
659 passOK = oilsAuthVerifyPassword( ctx, userObj, barcode, password );
662 jsonObjectFree(userObj);
666 // See if the account is active
667 char* active = oilsFMGetString(userObj, "active");
668 if( !oilsUtilsIsDBTrue(active) ) {
670 response = oilsNewEvent( OSRF_LOG_MARK, "PATRON_INACTIVE" );
672 response = oilsNewEvent( OSRF_LOG_MARK, OILS_EVENT_AUTH_FAILED );
674 osrfAppRespondComplete( ctx, oilsEventToJSON(response) );
675 oilsEventFree(response);
676 jsonObjectFree(userObj);
682 osrfLogInfo( OSRF_LOG_MARK, "Fetching card by barcode %s", barcode );
685 osrfLogInfo( OSRF_LOG_MARK, "barcode %s is not active, returning event", barcode );
686 response = oilsNewEvent( OSRF_LOG_MARK, "PATRON_CARD_INACTIVE" );
687 osrfAppRespondComplete( ctx, oilsEventToJSON( response ) );
688 oilsEventFree( response );
689 jsonObjectFree( userObj );
694 // See if the user is even allowed to log in
695 if( oilsAuthCheckLoginPerm( ctx, userObj, type ) == -1 ) {
696 jsonObjectFree(userObj);
700 // If a workstation is defined, add the workstation info
701 if( workstation != NULL ) {
702 osrfLogDebug(OSRF_LOG_MARK, "Workstation is %s", workstation);
703 response = oilsAuthVerifyWorkstation( ctx, userObj, workstation );
705 jsonObjectFree(userObj);
706 osrfAppRespondComplete( ctx, oilsEventToJSON(response) );
707 oilsEventFree(response);
712 // Otherwise, use the home org as the workstation org on the user
713 char* orgid = oilsFMGetString(userObj, "home_ou");
714 oilsFMSetString(userObj, "ws_ou", orgid);
718 char* freeable_uname = NULL;
720 uname = freeable_uname = oilsFMGetString( userObj, "usrname" );
723 if( passOK ) { // login successful
725 char* ewhat = "login";
727 if (0 == strcmp(ctx->method->name, "open-ils.auth.authenticate.verify")) {
728 response = oilsNewEvent( OSRF_LOG_MARK, OILS_EVENT_SUCCESS );
732 response = oilsAuthHandleLoginOK( userObj, uname, type, orgloc, workstation );
735 oilsUtilsTrackUserActivity(
736 oilsFMGetObjectId(userObj),
738 osrfAppSessionGetIngress()
742 response = oilsNewEvent( OSRF_LOG_MARK, OILS_EVENT_AUTH_FAILED );
743 osrfLogInfo(OSRF_LOG_MARK, "failed login: username=%s, barcode=%s, workstation=%s",
744 uname, (barcode ? barcode : "(none)"), ws );
747 jsonObjectFree(userObj);
748 osrfAppRespondComplete( ctx, oilsEventToJSON(response) );
749 oilsEventFree(response);
752 free(freeable_uname);
759 int oilsAuthSessionDelete( osrfMethodContext* ctx ) {
760 OSRF_METHOD_VERIFY_CONTEXT(ctx);
762 const char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0) );
763 jsonObject* resp = NULL;
766 osrfLogDebug(OSRF_LOG_MARK, "Removing auth session: %s", authToken );
767 char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken ); /**/
768 osrfCacheRemove(key);
769 resp = jsonNewObject(authToken); /**/
773 osrfAppRespondComplete( ctx, resp );
774 jsonObjectFree(resp);
779 * Fetches the user object from the database and updates the user object in
780 * the cache object, which then has to be re-inserted into the cache.
781 * User object is retrieved inside a transaction to avoid replication issues.
783 static int _oilsAuthReloadUser(jsonObject* cacheObj) {
785 osrfAppSession* session;
787 jsonObject *param, *userObj, *newUserObj;
789 userObj = jsonObjectGetKey( cacheObj, "userobj" );
790 userId = oilsFMGetObjectId( userObj );
792 session = osrfAppSessionClientInit( "open-ils.cstore" );
793 osrfAppSessionConnect(session);
795 reqid = osrfAppSessionSendRequest(session, NULL, "open-ils.cstore.transaction.begin", 1);
796 omsg = osrfAppSessionRequestRecv(session, reqid, 60);
800 osrfMessageFree(omsg);
801 param = jsonNewNumberObject(userId);
802 reqid = osrfAppSessionSendRequest(session, param, "open-ils.cstore.direct.actor.user.retrieve", 1);
803 omsg = osrfAppSessionRequestRecv(session, reqid, 60);
804 jsonObjectFree(param);
807 newUserObj = jsonObjectClone( osrfMessageGetResult(omsg) );
808 osrfMessageFree(omsg);
809 reqid = osrfAppSessionSendRequest(session, NULL, "open-ils.cstore.transaction.rollback", 1);
810 omsg = osrfAppSessionRequestRecv(session, reqid, 60);
811 osrfMessageFree(omsg);
815 osrfAppSessionFree(session); // calls disconnect internally
819 // ws_ou and wsid are ephemeral and need to be manually propagated
820 // oilsFMSetString dupe()'s internally, no need to clone the string
821 oilsFMSetString(newUserObj, "wsid", oilsFMGetStringConst(userObj, "wsid"));
822 oilsFMSetString(newUserObj, "ws_ou", oilsFMGetStringConst(userObj, "ws_ou"));
824 jsonObjectRemoveKey(cacheObj, "userobj"); // this also frees the old user object
825 jsonObjectSetKey(cacheObj, "userobj", newUserObj);
829 osrfLogError(OSRF_LOG_MARK, "Error retrieving user %d from database", userId);
834 Resets the auth login timeout
835 @return The event object, OILS_EVENT_SUCCESS, or OILS_EVENT_NO_SESSION
837 static oilsEvent* _oilsAuthResetTimeout( const char* authToken, int reloadUser ) {
838 if(!authToken) return NULL;
840 oilsEvent* evt = NULL;
843 osrfLogDebug(OSRF_LOG_MARK, "Resetting auth timeout for session %s", authToken);
844 char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken );
845 jsonObject* cacheObj = osrfCacheGetObject( key );
848 osrfLogInfo(OSRF_LOG_MARK, "No user in the cache exists with key %s", key);
849 evt = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_NO_SESSION);
854 _oilsAuthReloadUser(cacheObj);
857 // Determine a new timeout value
858 jsonObject* endtime_obj = jsonObjectGetKey( cacheObj, "endtime" );
860 // Extend the current endtime by a fixed amount
861 time_t endtime = (time_t) jsonObjectGetNumber( endtime_obj );
862 int reset_interval = DEFAULT_RESET_INTERVAL;
863 const jsonObject* reset_interval_obj = jsonObjectGetKeyConst(
864 cacheObj, "reset_interval" );
865 if( reset_interval_obj ) {
866 reset_interval = (int) jsonObjectGetNumber( reset_interval_obj );
867 if( reset_interval <= 0 )
868 reset_interval = DEFAULT_RESET_INTERVAL;
871 time_t now = time( NULL );
872 time_t new_endtime = now + reset_interval;
873 if( new_endtime > endtime ) {
874 // Keep the session alive a little longer
875 jsonObjectSetNumber( endtime_obj, (double) new_endtime );
876 timeout = reset_interval;
877 osrfCachePutObject( key, cacheObj, timeout );
879 // The session isn't close to expiring, so don't reset anything.
880 // Just report the time remaining.
881 timeout = endtime - now;
884 // Reapply the existing timeout from the current time
885 timeout = (time_t) jsonObjectGetNumber( jsonObjectGetKeyConst( cacheObj, "authtime"));
886 osrfCachePutObject( key, cacheObj, timeout );
889 jsonObject* payload = jsonNewNumberObject( (double) timeout );
890 evt = oilsNewEvent2(OSRF_LOG_MARK, OILS_EVENT_SUCCESS, payload);
891 jsonObjectFree(payload);
892 jsonObjectFree(cacheObj);
899 int oilsAuthResetTimeout( osrfMethodContext* ctx ) {
900 OSRF_METHOD_VERIFY_CONTEXT(ctx);
901 const char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0));
902 double reloadUser = jsonObjectGetNumber( jsonObjectGetIndex(ctx->params, 1));
903 oilsEvent* evt = _oilsAuthResetTimeout(authToken, (int) reloadUser);
904 osrfAppRespondComplete( ctx, oilsEventToJSON(evt) );
910 int oilsAuthSessionRetrieve( osrfMethodContext* ctx ) {
911 OSRF_METHOD_VERIFY_CONTEXT(ctx);
912 bool returnFull = false;
914 const char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0));
916 if(ctx->params->size > 1) {
917 // caller wants full cached object, with authtime, etc.
918 const char* rt = jsonObjectGetString(jsonObjectGetIndex(ctx->params, 1));
919 if(rt && strcmp(rt, "0") != 0)
923 jsonObject* cacheObj = NULL;
924 oilsEvent* evt = NULL;
928 // Reset the timeout to keep the session alive
929 evt = _oilsAuthResetTimeout(authToken, 0);
931 if( evt && strcmp(evt->event, OILS_EVENT_SUCCESS) ) {
932 osrfAppRespondComplete( ctx, oilsEventToJSON( evt )); // can't reset timeout
936 // Retrieve the cached session object
937 osrfLogDebug(OSRF_LOG_MARK, "Retrieving auth session: %s", authToken);
938 char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken );
939 cacheObj = osrfCacheGetObject( key );
941 // Return a copy of the cached user object
943 osrfAppRespondComplete( ctx, cacheObj);
945 osrfAppRespondComplete( ctx, jsonObjectGetKeyConst( cacheObj, "userobj"));
946 jsonObjectFree(cacheObj);
948 // Auth token is invalid or expired
949 oilsEvent* evt2 = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_NO_SESSION);
950 osrfAppRespondComplete( ctx, oilsEventToJSON(evt2) ); /* should be event.. */
959 evt = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_NO_SESSION);
960 osrfAppRespondComplete( ctx, oilsEventToJSON(evt) );