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.
154 - nonce : optional login seed (string) provided by the caller which
155 is added to the auth init cache to differentiate between logins
156 using the same username and thus avoiding cache collisions for
157 near-simultaneous logins.
159 Return to client: Intermediate authentication seed.
161 Combine the username with a timestamp and process ID, and take an md5 hash of the result.
162 Store the hash in memcache, with a key based on the username. Then return the hash to
165 However: if the username includes one or more embedded blank spaces, return a dummy
166 hash without storing anything in memcache. The dummy will never match a stored hash, so
167 any attempt to authenticate with it will fail.
169 int oilsAuthInit( osrfMethodContext* ctx ) {
170 OSRF_METHOD_VERIFY_CONTEXT(ctx);
172 char* username = jsonObjectToSimpleString( jsonObjectGetIndex(ctx->params, 0) );
173 const char* nonce = jsonObjectGetString(jsonObjectGetIndex(ctx->params, 1));
174 if (!nonce) nonce = "";
180 if( strchr( username, ' ' ) ) {
182 // Embedded spaces are not allowed in a username. Use "x" as a dummy
183 // seed. It will never be a valid seed because 'x' is not a hex digit.
184 resp = jsonNewObject( "x" );
188 // Build a key and a seed; store them in memcache.
189 char* key = va_list_to_string( "%s%s%s", OILS_AUTH_CACHE_PRFX, username, nonce );
190 char* countkey = va_list_to_string( "%s%s%s", OILS_AUTH_CACHE_PRFX, username, OILS_AUTH_COUNT_SFFX );
191 char* seed = md5sum( "%d.%ld.%s.%s", (int) time(NULL), (long) getpid(), username, nonce );
192 jsonObject* countobject = osrfCacheGetObject( countkey );
194 countobject = jsonNewNumberObject( (double) 0 );
196 osrfCachePutString( key, seed, _oilsAuthSeedTimeout );
197 osrfCachePutObject( countkey, countobject, _oilsAuthBlockTimeout );
199 osrfLogDebug( OSRF_LOG_MARK, "oilsAuthInit(): has seed %s and key %s", seed, key );
201 // Build a returnable object containing the seed.
202 resp = jsonNewObject( seed );
207 jsonObjectFree( countobject );
210 // Return the seed to the client.
211 osrfAppRespondComplete( ctx, resp );
213 jsonObjectFree(resp);
218 return -1; // Error: no username parameter
222 Verifies that the user has permission to login with the
223 given type. If the permission fails, an oilsEvent is returned
225 @return -1 if the permission check failed, 0 if the permission
228 static int oilsAuthCheckLoginPerm(
229 osrfMethodContext* ctx, const jsonObject* userObj, const char* type ) {
231 if(!(userObj && type)) return -1;
232 oilsEvent* perm = NULL;
234 if(!strcasecmp(type, OILS_AUTH_OPAC)) {
235 char* permissions[] = { "OPAC_LOGIN" };
236 perm = oilsUtilsCheckPerms( oilsFMGetObjectId( userObj ), -1, permissions, 1 );
238 } else if(!strcasecmp(type, OILS_AUTH_STAFF)) {
239 char* permissions[] = { "STAFF_LOGIN" };
240 perm = oilsUtilsCheckPerms( oilsFMGetObjectId( userObj ), -1, permissions, 1 );
242 } else if(!strcasecmp(type, OILS_AUTH_TEMP)) {
243 char* permissions[] = { "STAFF_LOGIN" };
244 perm = oilsUtilsCheckPerms( oilsFMGetObjectId( userObj ), -1, permissions, 1 );
245 } else if(!strcasecmp(type, OILS_AUTH_PERSIST)) {
246 char* permissions[] = { "PERSISTENT_LOGIN" };
247 perm = oilsUtilsCheckPerms( oilsFMGetObjectId( userObj ), -1, permissions, 1 );
251 osrfAppRespondComplete( ctx, oilsEventToJSON(perm) );
260 Returns 1 if the password provided matches the user's real password
265 @brief Verify the password received from the client.
266 @param ctx The method context.
267 @param userObj An object from the database, representing the user.
268 @param password An obfuscated password received from the client.
269 @return 1 if the password is valid; 0 if it isn't; or -1 upon error.
271 (None of the so-called "passwords" used here are in plaintext. All have been passed
272 through at least one layer of hashing to obfuscate them.)
274 Take the password from the user object. Append it to the username seed from memcache,
275 as stored previously by a call to the init method. Take an md5 hash of the result.
276 Then compare this hash to the password received from the client.
278 In order for the two to match, other than by dumb luck, the client had to construct
279 the password it passed in the same way. That means it neded to know not only the
280 original password (either hashed or plaintext), but also the seed. The latter requirement
281 means that the client process needs either to be the same process that called the init
282 method or to receive the seed from the process that did so.
284 static int oilsAuthVerifyPassword( const osrfMethodContext* ctx,
285 const jsonObject* userObj, const char* uname,
286 const char* password, const char* nonce ) {
288 // Get the username seed, as stored previously in memcache by the init method
289 char* seed = osrfCacheGetString( "%s%s%s", OILS_AUTH_CACHE_PRFX, uname, nonce );
291 return osrfAppRequestRespondException( ctx->session,
292 ctx->request, "No authentication seed found. "
293 "open-ils.auth.authenticate.init must be called first "
294 " (check that memcached is running and can be connected to) "
298 // We won't be needing the seed again, remove it
299 osrfCacheRemove( "%s%s%s", OILS_AUTH_CACHE_PRFX, uname, nonce );
301 // Get the hashed password from the user object
302 char* realPassword = oilsFMGetString( userObj, "passwd" );
304 osrfLogInternal(OSRF_LOG_MARK, "oilsAuth retrieved real password: [%s]", realPassword);
305 osrfLogDebug(OSRF_LOG_MARK, "oilsAuth retrieved seed from cache: %s", seed );
307 // Concatenate them and take an MD5 hash of the result
308 char* maskedPw = md5sum( "%s%s", seed, realPassword );
314 // This happens only if md5sum() runs out of memory
316 return -1; // md5sum() ran out of memory
319 osrfLogDebug(OSRF_LOG_MARK, "oilsAuth generated masked password %s. "
320 "Testing against provided password %s", maskedPw, password );
323 if( !strcmp( maskedPw, password ) )
328 char* countkey = va_list_to_string( "%s%s%s", OILS_AUTH_CACHE_PRFX, uname, OILS_AUTH_COUNT_SFFX );
329 jsonObject* countobject = osrfCacheGetObject( countkey );
331 long failcount = (long) jsonObjectGetNumber( countobject );
332 if(failcount >= _oilsAuthBlockCount) {
334 osrfLogInfo(OSRF_LOG_MARK, "oilsAuth found too many recent failures for '%s' : %i, forcing failure state.", uname, failcount);
339 jsonObjectSetNumber( countobject, failcount );
340 osrfCachePutObject( countkey, countobject, _oilsAuthBlockTimeout );
341 jsonObjectFree(countobject);
349 @brief Determine the login timeout.
350 @param userObj Pointer to an object describing the user.
351 @param type Pointer to one of four possible character strings identifying the login type.
352 @param orgloc Org unit to use for settings lookups (negative or zero means unspecified)
353 @return The length of the timeout, in seconds.
355 The default timeout value comes from the configuration file, and depends on the
358 The default may be overridden by a corresponding org unit setting. The @a orgloc
359 parameter says what org unit to use for the lookup. If @a orgloc <= 0, or if the
360 lookup for @a orgloc yields no result, we look up the setting for the user's home org unit
361 instead (except that if it's the same as @a orgloc we don't bother repeating the lookup).
363 Whether defined in the config file or in an org unit setting, a timeout value may be
364 expressed as a raw number (i.e. all digits, possibly with leading and/or trailing white
365 space) or as an interval string to be translated into seconds by PostgreSQL.
367 static long oilsAuthGetTimeout( const jsonObject* userObj, const char* type, int orgloc ) {
369 if(!_oilsAuthOPACTimeout) { /* Load the default timeouts */
371 jsonObject* value_obj;
373 value_obj = osrf_settings_host_value_object(
374 "/apps/open-ils.auth/app_settings/default_timeout/opac" );
375 _oilsAuthOPACTimeout = oilsUtilsIntervalToSeconds( jsonObjectGetString( value_obj ));
376 jsonObjectFree(value_obj);
377 if( -1 == _oilsAuthOPACTimeout ) {
378 osrfLogWarning( OSRF_LOG_MARK, "Invalid default timeout for OPAC logins" );
379 _oilsAuthOPACTimeout = 0;
382 value_obj = osrf_settings_host_value_object(
383 "/apps/open-ils.auth/app_settings/default_timeout/staff" );
384 _oilsAuthStaffTimeout = oilsUtilsIntervalToSeconds( jsonObjectGetString( value_obj ));
385 jsonObjectFree(value_obj);
386 if( -1 == _oilsAuthStaffTimeout ) {
387 osrfLogWarning( OSRF_LOG_MARK, "Invalid default timeout for staff logins" );
388 _oilsAuthStaffTimeout = 0;
391 value_obj = osrf_settings_host_value_object(
392 "/apps/open-ils.auth/app_settings/default_timeout/temp" );
393 _oilsAuthOverrideTimeout = oilsUtilsIntervalToSeconds( jsonObjectGetString( value_obj ));
394 jsonObjectFree(value_obj);
395 if( -1 == _oilsAuthOverrideTimeout ) {
396 osrfLogWarning( OSRF_LOG_MARK, "Invalid default timeout for temp logins" );
397 _oilsAuthOverrideTimeout = 0;
400 value_obj = osrf_settings_host_value_object(
401 "/apps/open-ils.auth/app_settings/default_timeout/persist" );
402 _oilsAuthPersistTimeout = oilsUtilsIntervalToSeconds( jsonObjectGetString( value_obj ));
403 jsonObjectFree(value_obj);
404 if( -1 == _oilsAuthPersistTimeout ) {
405 osrfLogWarning( OSRF_LOG_MARK, "Invalid default timeout for persist logins" );
406 _oilsAuthPersistTimeout = 0;
409 osrfLogInfo(OSRF_LOG_MARK, "Set default auth timeouts: "
410 "opac => %ld : staff => %ld : temp => %ld : persist => %ld",
411 _oilsAuthOPACTimeout, _oilsAuthStaffTimeout,
412 _oilsAuthOverrideTimeout, _oilsAuthPersistTimeout );
415 int home_ou = (int) jsonObjectGetNumber( oilsFMGetObject( userObj, "home_ou" ));
419 char* setting = NULL;
420 long default_timeout = 0;
422 if( !strcmp( type, OILS_AUTH_OPAC )) {
423 setting = OILS_ORG_SETTING_OPAC_TIMEOUT;
424 default_timeout = _oilsAuthOPACTimeout;
425 } else if( !strcmp( type, OILS_AUTH_STAFF )) {
426 setting = OILS_ORG_SETTING_STAFF_TIMEOUT;
427 default_timeout = _oilsAuthStaffTimeout;
428 } else if( !strcmp( type, OILS_AUTH_TEMP )) {
429 setting = OILS_ORG_SETTING_TEMP_TIMEOUT;
430 default_timeout = _oilsAuthOverrideTimeout;
431 } else if( !strcmp( type, OILS_AUTH_PERSIST )) {
432 setting = OILS_ORG_SETTING_PERSIST_TIMEOUT;
433 default_timeout = _oilsAuthPersistTimeout;
436 // Get the org unit setting, if there is one.
437 char* timeout = oilsUtilsFetchOrgSetting( orgloc, setting );
439 if( orgloc != home_ou ) {
440 osrfLogDebug(OSRF_LOG_MARK, "Auth timeout not defined for org %d, "
441 "trying home_ou %d", orgloc, home_ou );
442 timeout = oilsUtilsFetchOrgSetting( home_ou, setting );
447 return default_timeout; // No override from org unit setting
449 // Translate the org unit setting to a number
452 osrfLogWarning( OSRF_LOG_MARK,
453 "Timeout org unit setting is an empty string for %s login; using default",
457 // Treat timeout string as an interval, and convert it to seconds
458 t = oilsUtilsIntervalToSeconds( timeout );
460 // Unable to convert; possibly an invalid interval string
461 osrfLogError( OSRF_LOG_MARK,
462 "Unable to convert timeout interval \"%s\" for %s login; using default",
473 Adds the authentication token to the user cache. The timeout for the
474 auth token is based on the type of login as well as (if type=='opac')
476 Returns the event that should be returned to the user.
479 static oilsEvent* oilsAuthHandleLoginOK( jsonObject* userObj, const char* uname,
480 const char* type, int orgloc, const char* workstation ) {
485 char* wsorg = jsonObjectToSimpleString(oilsFMGetObject(userObj, "ws_ou"));
486 if(wsorg) { /* if there is a workstation, use it for the timeout */
487 osrfLogDebug( OSRF_LOG_MARK,
488 "Auth session trying workstation id %d for auth timeout", atoi(wsorg));
489 timeout = oilsAuthGetTimeout( userObj, type, atoi(wsorg) );
492 osrfLogDebug( OSRF_LOG_MARK,
493 "Auth session trying org from param [%d] for auth timeout", orgloc );
494 timeout = oilsAuthGetTimeout( userObj, type, orgloc );
496 osrfLogDebug(OSRF_LOG_MARK, "Auth session timeout for %s: %ld", uname, timeout );
498 char* string = va_list_to_string(
499 "%d.%ld.%s", (long) getpid(), time(NULL), uname );
500 char* authToken = md5sum(string);
501 char* authKey = va_list_to_string(
502 "%s%s", OILS_AUTH_CACHE_PRFX, authToken );
504 const char* ws = (workstation) ? workstation : "";
505 osrfLogActivity(OSRF_LOG_MARK,
506 "successful login: username=%s, authtoken=%s, workstation=%s", uname, authToken, ws );
508 oilsFMSetString( userObj, "passwd", "" );
509 jsonObject* cacheObj = jsonParseFmt( "{\"authtime\": %ld}", timeout );
510 jsonObjectSetKey( cacheObj, "userobj", jsonObjectClone(userObj));
512 if( !strcmp( type, OILS_AUTH_PERSIST )) {
513 // Add entries for endtime and reset_interval, so that we can gracefully
514 // extend the session a bit if the user is active toward the end of the
515 // timeout originally specified.
516 time_t endtime = time( NULL ) + timeout;
517 jsonObjectSetKey( cacheObj, "endtime", jsonNewNumberObject( (double) endtime ) );
519 // Reset interval is hard-coded for now, but if we ever want to make it
520 // configurable, this is the place to do it:
521 jsonObjectSetKey( cacheObj, "reset_interval",
522 jsonNewNumberObject( (double) DEFAULT_RESET_INTERVAL ));
525 osrfCachePutObject( authKey, cacheObj, (time_t) timeout );
526 jsonObjectFree(cacheObj);
527 osrfLogInternal(OSRF_LOG_MARK, "oilsAuthHandleLoginOK(): Placed user object into cache");
528 jsonObject* payload = jsonParseFmt(
529 "{ \"authtoken\": \"%s\", \"authtime\": %ld }", authToken, timeout );
531 response = oilsNewEvent2( OSRF_LOG_MARK, OILS_EVENT_SUCCESS, payload );
532 free(string); free(authToken); free(authKey);
533 jsonObjectFree(payload);
538 static oilsEvent* oilsAuthVerifyWorkstation(
539 const osrfMethodContext* ctx, jsonObject* userObj, const char* ws ) {
540 osrfLogInfo(OSRF_LOG_MARK, "Attaching workstation to user at login: %s", ws);
541 jsonObject* workstation = oilsUtilsFetchWorkstationByName(ws);
542 if(!workstation || workstation->type == JSON_NULL) {
543 jsonObjectFree(workstation);
544 return oilsNewEvent(OSRF_LOG_MARK, "WORKSTATION_NOT_FOUND");
546 long wsid = oilsFMGetObjectId(workstation);
547 LONG_TO_STRING(wsid);
548 char* orgid = oilsFMGetString(workstation, "owning_lib");
549 oilsFMSetString(userObj, "wsid", LONGSTR);
550 oilsFMSetString(userObj, "ws_ou", orgid);
552 jsonObjectFree(workstation);
559 @brief Implement the "complete" method.
560 @param ctx The method context.
561 @return -1 upon error; zero if successful, and if a STATUS message has been sent to the
562 client to indicate completion; a positive integer if successful but no such STATUS
563 message has been sent.
566 - a hash with some combination of the following elements:
569 - "password" (hashed with the cached seed; not plaintext)
573 - "agent" (what software/interface/3rd-party is making the request)
574 - "nonce" optional login seed to differentiate logins using the same username.
576 The password is required. Either a username or a barcode must also be present.
578 Return to client: Intermediate authentication seed.
580 Validate the password, using the username if available, or the barcode if not. The
581 user must be active, and not barred from logging on. The barcode, if used for
582 authentication, must be active as well. The workstation, if specified, must be valid.
584 Upon deciding whether to allow the logon, return a corresponding event to the client.
586 int oilsAuthComplete( osrfMethodContext* ctx ) {
587 OSRF_METHOD_VERIFY_CONTEXT(ctx);
589 const jsonObject* args = jsonObjectGetIndex(ctx->params, 0);
591 const char* uname = jsonObjectGetString(jsonObjectGetKeyConst(args, "username"));
592 const char* password = jsonObjectGetString(jsonObjectGetKeyConst(args, "password"));
593 const char* type = jsonObjectGetString(jsonObjectGetKeyConst(args, "type"));
594 int orgloc = (int) jsonObjectGetNumber(jsonObjectGetKeyConst(args, "org"));
595 const char* workstation = jsonObjectGetString(jsonObjectGetKeyConst(args, "workstation"));
596 const char* barcode = jsonObjectGetString(jsonObjectGetKeyConst(args, "barcode"));
597 const char* ewho = jsonObjectGetString(jsonObjectGetKeyConst(args, "agent"));
598 const char* nonce = jsonObjectGetString(jsonObjectGetKeyConst(args, "nonce"));
600 const char* ws = (workstation) ? workstation : "";
601 if (!nonce) nonce = "";
603 /* Use __FILE__, harmless_line_number for creating
604 * OILS_EVENT_AUTH_FAILED events (instead of OSRF_LOG_MARK) to avoid
605 * giving away information about why an authentication attempt failed.
607 int harmless_line_number = __LINE__;
610 type = OILS_AUTH_STAFF;
612 if( !( (uname || barcode) && password) ) {
613 return osrfAppRequestRespondException( ctx->session, ctx->request,
614 "username/barcode and password required for method: %s", ctx->method->name );
617 oilsEvent* response = NULL;
618 jsonObject* userObj = NULL;
619 int card_active = 1; // boolean; assume active until proven otherwise
621 // Fetch a row from the actor.usr table, by username if available,
622 // or by barcode if not.
624 userObj = oilsUtilsFetchUserByUsername( uname );
625 if( userObj && JSON_NULL == userObj->type ) {
626 jsonObjectFree( userObj );
627 userObj = NULL; // username not found
631 // Read from actor.card by barcode
633 osrfLogInfo( OSRF_LOG_MARK, "Fetching user by barcode %s", barcode );
635 jsonObject* params = jsonParseFmt("{\"barcode\":\"%s\"}", barcode);
636 jsonObject* card = oilsUtilsQuickReq(
637 "open-ils.cstore", "open-ils.cstore.direct.actor.card.search", params );
638 jsonObjectFree( params );
640 if( card && card->type != JSON_NULL ) {
641 // Determine whether the card is active
642 char* card_active_str = oilsFMGetString( card, "active" );
643 card_active = oilsUtilsIsDBTrue( card_active_str );
644 free( card_active_str );
646 // Look up the user who owns the card
647 char* userid = oilsFMGetString( card, "usr" );
648 jsonObjectFree( card );
649 params = jsonParseFmt( "[%s]", userid );
651 userObj = oilsUtilsQuickReq(
652 "open-ils.cstore", "open-ils.cstore.direct.actor.user.retrieve", params );
653 jsonObjectFree( params );
654 if( userObj && JSON_NULL == userObj->type ) {
655 // user not found (shouldn't happen, due to foreign key)
656 jsonObjectFree( userObj );
662 int barred = 0, deleted = 0;
663 char *barred_str, *deleted_str;
666 barred_str = oilsFMGetString( userObj, "barred" );
667 barred = oilsUtilsIsDBTrue( barred_str );
670 deleted_str = oilsFMGetString( userObj, "deleted" );
671 deleted = oilsUtilsIsDBTrue( deleted_str );
675 if(!userObj || barred || deleted) {
676 response = oilsNewEvent( __FILE__, harmless_line_number, OILS_EVENT_AUTH_FAILED );
677 osrfLogInfo(OSRF_LOG_MARK, "failed login: username=%s, barcode=%s, workstation=%s",
678 uname, (barcode ? barcode : "(none)"), ws );
679 osrfAppRespondComplete( ctx, oilsEventToJSON(response) );
680 oilsEventFree(response);
681 return 0; // No such user
684 // Such a user exists and isn't barred or deleted.
685 // Now see if he or she has the right credentials.
688 passOK = oilsAuthVerifyPassword( ctx, userObj, uname, password, nonce );
690 passOK = oilsAuthVerifyPassword( ctx, userObj, barcode, password, nonce );
693 jsonObjectFree(userObj);
697 // See if the account is active
698 char* active = oilsFMGetString(userObj, "active");
699 if( !oilsUtilsIsDBTrue(active) ) {
701 response = oilsNewEvent( OSRF_LOG_MARK, "PATRON_INACTIVE" );
703 response = oilsNewEvent( __FILE__, harmless_line_number, OILS_EVENT_AUTH_FAILED );
705 osrfAppRespondComplete( ctx, oilsEventToJSON(response) );
706 oilsEventFree(response);
707 jsonObjectFree(userObj);
713 osrfLogInfo( OSRF_LOG_MARK, "Fetching card by barcode %s", barcode );
716 osrfLogInfo( OSRF_LOG_MARK, "barcode %s is not active, returning event", barcode );
717 response = oilsNewEvent( OSRF_LOG_MARK, "PATRON_CARD_INACTIVE" );
718 osrfAppRespondComplete( ctx, oilsEventToJSON( response ) );
719 oilsEventFree( response );
720 jsonObjectFree( userObj );
725 // See if the user is even allowed to log in
726 if( oilsAuthCheckLoginPerm( ctx, userObj, type ) == -1 ) {
727 jsonObjectFree(userObj);
731 // If a workstation is defined, add the workstation info
732 if( workstation != NULL ) {
733 osrfLogDebug(OSRF_LOG_MARK, "Workstation is %s", workstation);
734 response = oilsAuthVerifyWorkstation( ctx, userObj, workstation );
736 jsonObjectFree(userObj);
737 osrfAppRespondComplete( ctx, oilsEventToJSON(response) );
738 oilsEventFree(response);
743 // Otherwise, use the home org as the workstation org on the user
744 char* orgid = oilsFMGetString(userObj, "home_ou");
745 oilsFMSetString(userObj, "ws_ou", orgid);
749 char* freeable_uname = NULL;
751 uname = freeable_uname = oilsFMGetString( userObj, "usrname" );
754 if( passOK ) { // login successful
756 char* ewhat = "login";
758 if (0 == strcmp(ctx->method->name, "open-ils.auth.authenticate.verify")) {
759 response = oilsNewEvent( OSRF_LOG_MARK, OILS_EVENT_SUCCESS );
763 response = oilsAuthHandleLoginOK( userObj, uname, type, orgloc, workstation );
766 oilsUtilsTrackUserActivity(
767 oilsFMGetObjectId(userObj),
769 osrfAppSessionGetIngress()
773 response = oilsNewEvent( __FILE__, harmless_line_number, OILS_EVENT_AUTH_FAILED );
774 osrfLogInfo(OSRF_LOG_MARK, "failed login: username=%s, barcode=%s, workstation=%s",
775 uname, (barcode ? barcode : "(none)"), ws );
778 jsonObjectFree(userObj);
779 osrfAppRespondComplete( ctx, oilsEventToJSON(response) );
780 oilsEventFree(response);
783 free(freeable_uname);
790 int oilsAuthSessionDelete( osrfMethodContext* ctx ) {
791 OSRF_METHOD_VERIFY_CONTEXT(ctx);
793 const char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0) );
794 jsonObject* resp = NULL;
797 osrfLogDebug(OSRF_LOG_MARK, "Removing auth session: %s", authToken );
798 char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken ); /**/
799 osrfCacheRemove(key);
800 resp = jsonNewObject(authToken); /**/
804 osrfAppRespondComplete( ctx, resp );
805 jsonObjectFree(resp);
810 * Fetches the user object from the database and updates the user object in
811 * the cache object, which then has to be re-inserted into the cache.
812 * User object is retrieved inside a transaction to avoid replication issues.
814 static int _oilsAuthReloadUser(jsonObject* cacheObj) {
816 osrfAppSession* session;
818 jsonObject *param, *userObj, *newUserObj;
820 userObj = jsonObjectGetKey( cacheObj, "userobj" );
821 userId = oilsFMGetObjectId( userObj );
823 session = osrfAppSessionClientInit( "open-ils.cstore" );
824 osrfAppSessionConnect(session);
826 reqid = osrfAppSessionSendRequest(session, NULL, "open-ils.cstore.transaction.begin", 1);
827 omsg = osrfAppSessionRequestRecv(session, reqid, 60);
831 osrfMessageFree(omsg);
832 param = jsonNewNumberObject(userId);
833 reqid = osrfAppSessionSendRequest(session, param, "open-ils.cstore.direct.actor.user.retrieve", 1);
834 omsg = osrfAppSessionRequestRecv(session, reqid, 60);
835 jsonObjectFree(param);
838 newUserObj = jsonObjectClone( osrfMessageGetResult(omsg) );
839 osrfMessageFree(omsg);
840 reqid = osrfAppSessionSendRequest(session, NULL, "open-ils.cstore.transaction.rollback", 1);
841 omsg = osrfAppSessionRequestRecv(session, reqid, 60);
842 osrfMessageFree(omsg);
846 osrfAppSessionFree(session); // calls disconnect internally
850 // ws_ou and wsid are ephemeral and need to be manually propagated
851 // oilsFMSetString dupe()'s internally, no need to clone the string
852 oilsFMSetString(newUserObj, "wsid", oilsFMGetStringConst(userObj, "wsid"));
853 oilsFMSetString(newUserObj, "ws_ou", oilsFMGetStringConst(userObj, "ws_ou"));
855 jsonObjectRemoveKey(cacheObj, "userobj"); // this also frees the old user object
856 jsonObjectSetKey(cacheObj, "userobj", newUserObj);
860 osrfLogError(OSRF_LOG_MARK, "Error retrieving user %d from database", userId);
865 Resets the auth login timeout
866 @return The event object, OILS_EVENT_SUCCESS, or OILS_EVENT_NO_SESSION
868 static oilsEvent* _oilsAuthResetTimeout( const char* authToken, int reloadUser ) {
869 if(!authToken) return NULL;
871 oilsEvent* evt = NULL;
874 osrfLogDebug(OSRF_LOG_MARK, "Resetting auth timeout for session %s", authToken);
875 char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken );
876 jsonObject* cacheObj = osrfCacheGetObject( key );
879 osrfLogInfo(OSRF_LOG_MARK, "No user in the cache exists with key %s", key);
880 evt = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_NO_SESSION);
885 _oilsAuthReloadUser(cacheObj);
888 // Determine a new timeout value
889 jsonObject* endtime_obj = jsonObjectGetKey( cacheObj, "endtime" );
891 // Extend the current endtime by a fixed amount
892 time_t endtime = (time_t) jsonObjectGetNumber( endtime_obj );
893 int reset_interval = DEFAULT_RESET_INTERVAL;
894 const jsonObject* reset_interval_obj = jsonObjectGetKeyConst(
895 cacheObj, "reset_interval" );
896 if( reset_interval_obj ) {
897 reset_interval = (int) jsonObjectGetNumber( reset_interval_obj );
898 if( reset_interval <= 0 )
899 reset_interval = DEFAULT_RESET_INTERVAL;
902 time_t now = time( NULL );
903 time_t new_endtime = now + reset_interval;
904 if( new_endtime > endtime ) {
905 // Keep the session alive a little longer
906 jsonObjectSetNumber( endtime_obj, (double) new_endtime );
907 timeout = reset_interval;
908 osrfCachePutObject( key, cacheObj, timeout );
910 // The session isn't close to expiring, so don't reset anything.
911 // Just report the time remaining.
912 timeout = endtime - now;
915 // Reapply the existing timeout from the current time
916 timeout = (time_t) jsonObjectGetNumber( jsonObjectGetKeyConst( cacheObj, "authtime"));
917 osrfCachePutObject( key, cacheObj, timeout );
920 jsonObject* payload = jsonNewNumberObject( (double) timeout );
921 evt = oilsNewEvent2(OSRF_LOG_MARK, OILS_EVENT_SUCCESS, payload);
922 jsonObjectFree(payload);
923 jsonObjectFree(cacheObj);
930 int oilsAuthResetTimeout( osrfMethodContext* ctx ) {
931 OSRF_METHOD_VERIFY_CONTEXT(ctx);
932 const char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0));
933 double reloadUser = jsonObjectGetNumber( jsonObjectGetIndex(ctx->params, 1));
934 oilsEvent* evt = _oilsAuthResetTimeout(authToken, (int) reloadUser);
935 osrfAppRespondComplete( ctx, oilsEventToJSON(evt) );
941 int oilsAuthSessionRetrieve( osrfMethodContext* ctx ) {
942 OSRF_METHOD_VERIFY_CONTEXT(ctx);
943 bool returnFull = false;
945 const char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0));
947 if(ctx->params->size > 1) {
948 // caller wants full cached object, with authtime, etc.
949 const char* rt = jsonObjectGetString(jsonObjectGetIndex(ctx->params, 1));
950 if(rt && strcmp(rt, "0") != 0)
954 jsonObject* cacheObj = NULL;
955 oilsEvent* evt = NULL;
959 // Reset the timeout to keep the session alive
960 evt = _oilsAuthResetTimeout(authToken, 0);
962 if( evt && strcmp(evt->event, OILS_EVENT_SUCCESS) ) {
963 osrfAppRespondComplete( ctx, oilsEventToJSON( evt )); // can't reset timeout
967 // Retrieve the cached session object
968 osrfLogDebug(OSRF_LOG_MARK, "Retrieving auth session: %s", authToken);
969 char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken );
970 cacheObj = osrfCacheGetObject( key );
972 // Return a copy of the cached user object
974 osrfAppRespondComplete( ctx, cacheObj);
976 osrfAppRespondComplete( ctx, jsonObjectGetKeyConst( cacheObj, "userobj"));
977 jsonObjectFree(cacheObj);
979 // Auth token is invalid or expired
980 oilsEvent* evt2 = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_NO_SESSION);
981 osrfAppRespondComplete( ctx, oilsEventToJSON(evt2) ); /* should be event.. */
990 evt = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_NO_SESSION);
991 osrfAppRespondComplete( ctx, oilsEventToJSON(evt) );