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 );
645 int barred = 0, deleted = 0;
646 char *barred_str, *deleted_str;
649 barred_str = oilsFMGetString( userObj, "barred" );
650 barred = oilsUtilsIsDBTrue( barred_str );
653 deleted_str = oilsFMGetString( userObj, "deleted" );
654 deleted = oilsUtilsIsDBTrue( deleted_str );
658 if(!userObj || barred || deleted) {
659 response = oilsNewEvent( OSRF_LOG_MARK, OILS_EVENT_AUTH_FAILED );
660 osrfLogInfo(OSRF_LOG_MARK, "failed login: username=%s, barcode=%s, workstation=%s",
661 uname, (barcode ? barcode : "(none)"), ws );
662 osrfAppRespondComplete( ctx, oilsEventToJSON(response) );
663 oilsEventFree(response);
664 return 0; // No such user
667 // Such a user exists and isn't barred or deleted.
668 // Now see if he or she has the right credentials.
671 passOK = oilsAuthVerifyPassword( ctx, userObj, uname, password );
673 passOK = oilsAuthVerifyPassword( ctx, userObj, barcode, password );
676 jsonObjectFree(userObj);
680 // See if the account is active
681 char* active = oilsFMGetString(userObj, "active");
682 if( !oilsUtilsIsDBTrue(active) ) {
684 response = oilsNewEvent( OSRF_LOG_MARK, "PATRON_INACTIVE" );
686 response = oilsNewEvent( OSRF_LOG_MARK, OILS_EVENT_AUTH_FAILED );
688 osrfAppRespondComplete( ctx, oilsEventToJSON(response) );
689 oilsEventFree(response);
690 jsonObjectFree(userObj);
696 osrfLogInfo( OSRF_LOG_MARK, "Fetching card by barcode %s", barcode );
699 osrfLogInfo( OSRF_LOG_MARK, "barcode %s is not active, returning event", barcode );
700 response = oilsNewEvent( OSRF_LOG_MARK, "PATRON_CARD_INACTIVE" );
701 osrfAppRespondComplete( ctx, oilsEventToJSON( response ) );
702 oilsEventFree( response );
703 jsonObjectFree( userObj );
708 // See if the user is even allowed to log in
709 if( oilsAuthCheckLoginPerm( ctx, userObj, type ) == -1 ) {
710 jsonObjectFree(userObj);
714 // If a workstation is defined, add the workstation info
715 if( workstation != NULL ) {
716 osrfLogDebug(OSRF_LOG_MARK, "Workstation is %s", workstation);
717 response = oilsAuthVerifyWorkstation( ctx, userObj, workstation );
719 jsonObjectFree(userObj);
720 osrfAppRespondComplete( ctx, oilsEventToJSON(response) );
721 oilsEventFree(response);
726 // Otherwise, use the home org as the workstation org on the user
727 char* orgid = oilsFMGetString(userObj, "home_ou");
728 oilsFMSetString(userObj, "ws_ou", orgid);
732 char* freeable_uname = NULL;
734 uname = freeable_uname = oilsFMGetString( userObj, "usrname" );
737 if( passOK ) { // login successful
739 char* ewhat = "login";
741 if (0 == strcmp(ctx->method->name, "open-ils.auth.authenticate.verify")) {
742 response = oilsNewEvent( OSRF_LOG_MARK, OILS_EVENT_SUCCESS );
746 response = oilsAuthHandleLoginOK( userObj, uname, type, orgloc, workstation );
749 oilsUtilsTrackUserActivity(
750 oilsFMGetObjectId(userObj),
752 osrfAppSessionGetIngress()
756 response = oilsNewEvent( OSRF_LOG_MARK, OILS_EVENT_AUTH_FAILED );
757 osrfLogInfo(OSRF_LOG_MARK, "failed login: username=%s, barcode=%s, workstation=%s",
758 uname, (barcode ? barcode : "(none)"), ws );
761 jsonObjectFree(userObj);
762 osrfAppRespondComplete( ctx, oilsEventToJSON(response) );
763 oilsEventFree(response);
766 free(freeable_uname);
773 int oilsAuthSessionDelete( osrfMethodContext* ctx ) {
774 OSRF_METHOD_VERIFY_CONTEXT(ctx);
776 const char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0) );
777 jsonObject* resp = NULL;
780 osrfLogDebug(OSRF_LOG_MARK, "Removing auth session: %s", authToken );
781 char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken ); /**/
782 osrfCacheRemove(key);
783 resp = jsonNewObject(authToken); /**/
787 osrfAppRespondComplete( ctx, resp );
788 jsonObjectFree(resp);
793 * Fetches the user object from the database and updates the user object in
794 * the cache object, which then has to be re-inserted into the cache.
795 * User object is retrieved inside a transaction to avoid replication issues.
797 static int _oilsAuthReloadUser(jsonObject* cacheObj) {
799 osrfAppSession* session;
801 jsonObject *param, *userObj, *newUserObj;
803 userObj = jsonObjectGetKey( cacheObj, "userobj" );
804 userId = oilsFMGetObjectId( userObj );
806 session = osrfAppSessionClientInit( "open-ils.cstore" );
807 osrfAppSessionConnect(session);
809 reqid = osrfAppSessionSendRequest(session, NULL, "open-ils.cstore.transaction.begin", 1);
810 omsg = osrfAppSessionRequestRecv(session, reqid, 60);
814 osrfMessageFree(omsg);
815 param = jsonNewNumberObject(userId);
816 reqid = osrfAppSessionSendRequest(session, param, "open-ils.cstore.direct.actor.user.retrieve", 1);
817 omsg = osrfAppSessionRequestRecv(session, reqid, 60);
818 jsonObjectFree(param);
821 newUserObj = jsonObjectClone( osrfMessageGetResult(omsg) );
822 osrfMessageFree(omsg);
823 reqid = osrfAppSessionSendRequest(session, NULL, "open-ils.cstore.transaction.rollback", 1);
824 omsg = osrfAppSessionRequestRecv(session, reqid, 60);
825 osrfMessageFree(omsg);
829 osrfAppSessionFree(session); // calls disconnect internally
833 // ws_ou and wsid are ephemeral and need to be manually propagated
834 // oilsFMSetString dupe()'s internally, no need to clone the string
835 oilsFMSetString(newUserObj, "wsid", oilsFMGetStringConst(userObj, "wsid"));
836 oilsFMSetString(newUserObj, "ws_ou", oilsFMGetStringConst(userObj, "ws_ou"));
838 jsonObjectRemoveKey(cacheObj, "userobj"); // this also frees the old user object
839 jsonObjectSetKey(cacheObj, "userobj", newUserObj);
843 osrfLogError(OSRF_LOG_MARK, "Error retrieving user %d from database", userId);
848 Resets the auth login timeout
849 @return The event object, OILS_EVENT_SUCCESS, or OILS_EVENT_NO_SESSION
851 static oilsEvent* _oilsAuthResetTimeout( const char* authToken, int reloadUser ) {
852 if(!authToken) return NULL;
854 oilsEvent* evt = NULL;
857 osrfLogDebug(OSRF_LOG_MARK, "Resetting auth timeout for session %s", authToken);
858 char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken );
859 jsonObject* cacheObj = osrfCacheGetObject( key );
862 osrfLogInfo(OSRF_LOG_MARK, "No user in the cache exists with key %s", key);
863 evt = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_NO_SESSION);
868 _oilsAuthReloadUser(cacheObj);
871 // Determine a new timeout value
872 jsonObject* endtime_obj = jsonObjectGetKey( cacheObj, "endtime" );
874 // Extend the current endtime by a fixed amount
875 time_t endtime = (time_t) jsonObjectGetNumber( endtime_obj );
876 int reset_interval = DEFAULT_RESET_INTERVAL;
877 const jsonObject* reset_interval_obj = jsonObjectGetKeyConst(
878 cacheObj, "reset_interval" );
879 if( reset_interval_obj ) {
880 reset_interval = (int) jsonObjectGetNumber( reset_interval_obj );
881 if( reset_interval <= 0 )
882 reset_interval = DEFAULT_RESET_INTERVAL;
885 time_t now = time( NULL );
886 time_t new_endtime = now + reset_interval;
887 if( new_endtime > endtime ) {
888 // Keep the session alive a little longer
889 jsonObjectSetNumber( endtime_obj, (double) new_endtime );
890 timeout = reset_interval;
891 osrfCachePutObject( key, cacheObj, timeout );
893 // The session isn't close to expiring, so don't reset anything.
894 // Just report the time remaining.
895 timeout = endtime - now;
898 // Reapply the existing timeout from the current time
899 timeout = (time_t) jsonObjectGetNumber( jsonObjectGetKeyConst( cacheObj, "authtime"));
900 osrfCachePutObject( key, cacheObj, timeout );
903 jsonObject* payload = jsonNewNumberObject( (double) timeout );
904 evt = oilsNewEvent2(OSRF_LOG_MARK, OILS_EVENT_SUCCESS, payload);
905 jsonObjectFree(payload);
906 jsonObjectFree(cacheObj);
913 int oilsAuthResetTimeout( osrfMethodContext* ctx ) {
914 OSRF_METHOD_VERIFY_CONTEXT(ctx);
915 const char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0));
916 double reloadUser = jsonObjectGetNumber( jsonObjectGetIndex(ctx->params, 1));
917 oilsEvent* evt = _oilsAuthResetTimeout(authToken, (int) reloadUser);
918 osrfAppRespondComplete( ctx, oilsEventToJSON(evt) );
924 int oilsAuthSessionRetrieve( osrfMethodContext* ctx ) {
925 OSRF_METHOD_VERIFY_CONTEXT(ctx);
926 bool returnFull = false;
928 const char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0));
930 if(ctx->params->size > 1) {
931 // caller wants full cached object, with authtime, etc.
932 const char* rt = jsonObjectGetString(jsonObjectGetIndex(ctx->params, 1));
933 if(rt && strcmp(rt, "0") != 0)
937 jsonObject* cacheObj = NULL;
938 oilsEvent* evt = NULL;
942 // Reset the timeout to keep the session alive
943 evt = _oilsAuthResetTimeout(authToken, 0);
945 if( evt && strcmp(evt->event, OILS_EVENT_SUCCESS) ) {
946 osrfAppRespondComplete( ctx, oilsEventToJSON( evt )); // can't reset timeout
950 // Retrieve the cached session object
951 osrfLogDebug(OSRF_LOG_MARK, "Retrieving auth session: %s", authToken);
952 char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken );
953 cacheObj = osrfCacheGetObject( key );
955 // Return a copy of the cached user object
957 osrfAppRespondComplete( ctx, cacheObj);
959 osrfAppRespondComplete( ctx, jsonObjectGetKeyConst( cacheObj, "userobj"));
960 jsonObjectFree(cacheObj);
962 // Auth token is invalid or expired
963 oilsEvent* evt2 = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_NO_SESSION);
964 osrfAppRespondComplete( ctx, oilsEventToJSON(evt2) ); /* should be event.. */
973 evt = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_NO_SESSION);
974 osrfAppRespondComplete( ctx, oilsEventToJSON(evt) );