]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/c-apps/oils_auth.c
LP1915464 follow-up: use spaces, not tabs; remove extra comma
[Evergreen.git] / Open-ILS / src / c-apps / oils_auth.c
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"
9 #include <pcre.h>
10
11 #define OILS_AUTH_CACHE_PRFX "oils_auth_"
12 #define OILS_AUTH_COUNT_SFFX "_count"
13
14 #define MODULENAME "open-ils.auth"
15
16 #define OILS_AUTH_OPAC "opac"
17 #define OILS_AUTH_STAFF "staff"
18 #define OILS_AUTH_TEMP "temp"
19 #define OILS_AUTH_PERSIST "persist"
20
21 // Default time for extending a persistent session: ten minutes
22 #define DEFAULT_RESET_INTERVAL 10 * 60
23
24 int osrfAppInitialize();
25 int osrfAppChildInit();
26
27 static long _oilsAuthSeedTimeout = 0;
28 static long _oilsAuthBlockTimeout = 0;
29 static long _oilsAuthBlockCount = 0;
30
31
32 /**
33         @brief Initialize the application by registering functions for method calls.
34         @return Zero in all cases.
35 */
36 int osrfAppInitialize() {
37
38         osrfLogInfo(OSRF_LOG_MARK, "Initializing Auth Server...");
39
40         /* load and parse the IDL */
41         if (!oilsInitIDL(NULL)) return 1; /* return non-zero to indicate error */
42
43         osrfAppRegisterMethod(
44                 MODULENAME,
45                 "open-ils.auth.authenticate.init",
46                 "oilsAuthInit",
47                 "Start the authentication process and returns the intermediate authentication seed"
48                 " PARAMS( username )", 1, 0 );
49
50     osrfAppRegisterMethod(
51         MODULENAME,
52         "open-ils.auth.authenticate.init.barcode",
53         "oilsAuthInitBarcode",
54         "Start the authentication process using a patron barcode and return "
55         "the intermediate authentication seed. PARAMS(barcode)", 1, 0);
56
57     osrfAppRegisterMethod(
58         MODULENAME,
59         "open-ils.auth.authenticate.init.username",
60         "oilsAuthInitUsername",
61         "Start the authentication process using a patron username and return "
62         "the intermediate authentication seed. PARAMS(username)", 1, 0);
63
64         osrfAppRegisterMethod(
65                 MODULENAME,
66                 "open-ils.auth.authenticate.complete",
67                 "oilsAuthComplete",
68                 "Completes the authentication process.  Returns an object like so: "
69                 "{authtoken : <token>, authtime:<time>}, where authtoken is the login "
70                 "token and authtime is the number of seconds the session will be active"
71                 "PARAMS(username, md5sum( seed + md5sum( password ) ), type, org_id ) "
72                 "type can be one of 'opac','staff', or 'temp' and it defaults to 'staff' "
73                 "org_id is the location at which the login should be considered "
74                 "active for login timeout purposes", 1, 0 );
75
76         osrfAppRegisterMethod(
77                 MODULENAME,
78                 "open-ils.auth.login",
79                 "oilsAuthLogin",
80         "Request an authentication token logging in with username or "
81         "barcode.  Parameter is a keyword arguments hash with keys "
82         "username, barcode, identifier, password, type, org, workstation, "
83         "agent.  The 'identifier' option is used when the caller wants the "
84         "API to determine if an identifier string is a username or barcode "
85         "using the barcode format configuration.",
86         1, 0);
87
88         osrfAppRegisterMethod(
89                 MODULENAME,
90                 "open-ils.auth.authenticate.verify",
91                 "oilsAuthComplete",
92                 "Verifies the user provided a valid username and password."
93                 "Params and are the same as open-ils.auth.authenticate.complete."
94                 "Returns SUCCESS event on success, failure event on failure", 1, 0);
95
96
97         osrfAppRegisterMethod(
98                 MODULENAME,
99                 "open-ils.auth.session.retrieve",
100                 "oilsAuthSessionRetrieve",
101                 "Pass in the auth token and this retrieves the user object.  By "
102                 "default, the auth timeout is reset when this call is made.  If "
103                 "a second non-zero parameter is passed, the auth timeout info is "
104                 "returned to the caller along with the user object.  If a 3rd "
105                 "non-zero parameter is passed, the auth timeout will not be reset."
106                 "Returns the user object (password blanked) for the given login session "
107                 "PARAMS( authToken[, returnTime[, doNotResetSession]] )", 1, 0 );
108
109         osrfAppRegisterMethod(
110                 MODULENAME,
111                 "open-ils.auth.session.delete",
112                 "oilsAuthSessionDelete",
113                 "Destroys the given login session "
114                 "PARAMS( authToken )",  1, 0 );
115
116         osrfAppRegisterMethod(
117                 MODULENAME,
118                 "open-ils.auth.session.reset_timeout",
119                 "oilsAuthResetTimeout",
120                 "Resets the login timeout for the given session "
121                 "Returns an ILS Event with payload = session_timeout of session "
122                 "if found, otherwise returns the NO_SESSION event"
123                 "PARAMS( authToken )", 1, 0 );
124
125         if(!_oilsAuthSeedTimeout) { /* Load the default timeouts */
126
127                 jsonObject* value_obj;
128
129                 value_obj = osrf_settings_host_value_object(
130                         "/apps/open-ils.auth/app_settings/auth_limits/seed" );
131                 _oilsAuthSeedTimeout = oilsUtilsIntervalToSeconds( jsonObjectGetString( value_obj ));
132                 jsonObjectFree(value_obj);
133                 if( -1 == _oilsAuthSeedTimeout ) {
134                         osrfLogWarning( OSRF_LOG_MARK, "Invalid timeout for Auth Seeds - Using 30 seconds" );
135                         _oilsAuthSeedTimeout = 30;
136                 }
137
138                 value_obj = osrf_settings_host_value_object(
139                         "/apps/open-ils.auth/app_settings/auth_limits/block_time" );
140                 _oilsAuthBlockTimeout = oilsUtilsIntervalToSeconds( jsonObjectGetString( value_obj ));
141                 jsonObjectFree(value_obj);
142                 if( -1 == _oilsAuthBlockTimeout ) {
143                         osrfLogWarning( OSRF_LOG_MARK, "Invalid timeout for Blocking Timeout - Using 3x Seed" );
144                         _oilsAuthBlockTimeout = _oilsAuthSeedTimeout * 3;
145                 }
146
147                 value_obj = osrf_settings_host_value_object(
148                         "/apps/open-ils.auth/app_settings/auth_limits/block_count" );
149                 _oilsAuthBlockCount = oilsUtilsIntervalToSeconds( jsonObjectGetString( value_obj ));
150                 jsonObjectFree(value_obj);
151                 if( -1 == _oilsAuthBlockCount ) {
152                         osrfLogWarning( OSRF_LOG_MARK, "Invalid count for Blocking - Using 10" );
153                         _oilsAuthBlockCount = 10;
154                 }
155
156                 osrfLogInfo(OSRF_LOG_MARK, "Set auth limits: "
157                         "seed => %ld : block_timeout => %ld : block_count => %ld",
158                         _oilsAuthSeedTimeout, _oilsAuthBlockTimeout, _oilsAuthBlockCount );
159         }
160
161         return 0;
162 }
163
164 /**
165         @brief Dummy placeholder for initializing a server drone.
166
167         There is nothing to do, so do nothing.
168 */
169 int osrfAppChildInit() {
170         return 0;
171 }
172
173 // free() response
174 static char* oilsAuthGetSalt(int user_id) {
175     char* salt_str = NULL;
176
177     jsonObject* params = jsonParseFmt( // free
178         "{\"from\":[\"actor.get_salt\",%d,\"%s\"]}", user_id, "main");
179
180     jsonObject* salt_obj = // free
181         oilsUtilsCStoreReq("open-ils.cstore.json_query", params);
182
183     jsonObjectFree(params);
184
185     if (salt_obj) {
186
187         if (salt_obj->type != JSON_NULL) {
188
189             const char* salt_val = jsonObjectGetString(
190                 jsonObjectGetKeyConst(salt_obj, "actor.get_salt"));
191
192             // caller expects a free-able string, could be NULL.
193             if (salt_val) { salt_str = strdup(salt_val); } 
194         }
195
196         jsonObjectFree(salt_obj);
197     }
198
199     return salt_str;
200 }
201
202 // ident is either a username or barcode
203 // Returns the init seed -> requires free();
204 static char* oilsAuthBuildInitCache(
205     int user_id, const char* ident, const char* ident_type, const char* nonce) {
206
207     char* cache_key  = va_list_to_string(
208         "%s%s%s", OILS_AUTH_CACHE_PRFX, ident, nonce);
209
210     char* count_key = va_list_to_string(
211         "%s%s%s", OILS_AUTH_CACHE_PRFX, ident, OILS_AUTH_COUNT_SFFX);
212
213     char* auth_seed;
214     if (user_id == -1) {
215         // user does not exist.  Use a dummy seed
216         auth_seed = strdup("x");
217     } else {
218         auth_seed = oilsAuthGetSalt(user_id);
219     }
220
221     jsonObject* seed_object = jsonParseFmt(
222         "{\"%s\":\"%s\",\"user_id\":%d,\"seed\":\"%s\"}",
223         ident_type, ident, user_id, auth_seed);
224
225     jsonObject* count_object = osrfCacheGetObject(count_key);
226     if(!count_object) {
227         count_object = jsonNewNumberObject((double) 0);
228     }
229
230     osrfCachePutObject(cache_key, seed_object, _oilsAuthSeedTimeout);
231
232     if (user_id != -1) {
233         // Only track login counts for existing users, since a 
234         // login for a nonexistent user will never succeed anyway.
235         osrfCachePutObject(count_key, count_object, _oilsAuthBlockTimeout);
236     }
237
238     osrfLogDebug(OSRF_LOG_MARK, 
239         "oilsAuthInit(): has seed %s and key %s", auth_seed, cache_key);
240
241     free(cache_key);
242     free(count_key);
243     jsonObjectFree(count_object);
244     jsonObjectFree(seed_object);
245
246     return auth_seed;
247 }
248
249 static int oilsAuthInitUsernameHandler(
250     osrfMethodContext* ctx, const char* username, const char* nonce) {
251
252     osrfLogInfo(OSRF_LOG_MARK, 
253         "User logging in with username %s", username);
254
255     int user_id = -1;
256     jsonObject* resp = NULL; // free
257     jsonObject* user_obj = oilsUtilsFetchUserByUsername(ctx, username); // free
258
259     if (user_obj && user_obj->type != JSON_NULL) 
260         user_id = oilsFMGetObjectId(user_obj);
261
262     jsonObjectFree(user_obj); // NULL OK
263
264     char* seed = oilsAuthBuildInitCache(user_id, username, "username", nonce);
265     resp = jsonNewObject(seed);
266     free(seed);
267
268     osrfAppRespondComplete(ctx, resp);
269     jsonObjectFree(resp);
270     return 0;
271 }
272
273 // open-ils.auth.authenticate.init.username
274 int oilsAuthInitUsername(osrfMethodContext* ctx) {
275     OSRF_METHOD_VERIFY_CONTEXT(ctx);
276
277     char* username =  // free
278         jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, 0));
279     const char* nonce = 
280         jsonObjectGetString(jsonObjectGetIndex(ctx->params, 1));
281
282     if (!nonce) nonce = "";
283     if (!username) return -1;
284
285     int resp = oilsAuthInitUsernameHandler(ctx, username, nonce);
286
287     free(username);
288     return resp;
289 }
290
291 static int oilsAuthInitBarcodeHandler(
292     osrfMethodContext* ctx, const char* barcode, const char* nonce) {
293
294     osrfLogInfo(OSRF_LOG_MARK, 
295         "User logging in with barcode %s", barcode);
296
297     int user_id = -1;
298     jsonObject* resp = NULL; // free
299     jsonObject* user_obj = oilsUtilsFetchUserByBarcode(ctx, barcode); // free
300
301     if (user_obj && user_obj->type != JSON_NULL) 
302         user_id = oilsFMGetObjectId(user_obj);
303
304     jsonObjectFree(user_obj); // NULL OK
305
306     char* seed = oilsAuthBuildInitCache(user_id, barcode, "barcode", nonce);
307     resp = jsonNewObject(seed);
308     free(seed);
309
310     osrfAppRespondComplete(ctx, resp);
311     jsonObjectFree(resp);
312     return 0;
313 }
314
315
316 // open-ils.auth.authenticate.init.barcode
317 int oilsAuthInitBarcode(osrfMethodContext* ctx) {
318     OSRF_METHOD_VERIFY_CONTEXT(ctx);
319
320     char* barcode = // free
321         jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, 0));
322     const char* nonce = 
323         jsonObjectGetString(jsonObjectGetIndex(ctx->params, 1));
324
325     if (!nonce) nonce = "";
326     if (!barcode) return -1;
327
328     int resp = oilsAuthInitBarcodeHandler(ctx, barcode, nonce);
329
330     free(barcode);
331     return resp;
332 }
333
334 // returns true if the provided identifier matches the barcode regex.
335 static int oilsAuthIdentIsBarcode(const char* identifier, int org_id) {
336
337     if (org_id < 1)
338         org_id = oilsUtilsGetRootOrgId();
339
340     char* bc_regex = oilsUtilsFetchOrgSetting(org_id, "opac.barcode_regex");
341
342     if (!bc_regex) {
343         // if no regex is set, assume any identifier starting
344         // with a number is a barcode.
345         bc_regex = strdup("^\\d"); // dupe for later free'ing
346     }
347
348     const char *err_str;
349     int err_offset, match_ret;
350
351     pcre *compiled = pcre_compile(
352         bc_regex, 0, &err_str, &err_offset, NULL);
353
354     if (compiled == NULL) {
355         osrfLogError(OSRF_LOG_MARK,
356             "Could not compile '%s': %s", bc_regex, err_str);
357         free(bc_regex);
358         pcre_free(compiled);
359         return 0;
360     }
361
362     pcre_extra *extra = pcre_study(compiled, 0, &err_str);
363
364     if(err_str != NULL) {
365         osrfLogError(OSRF_LOG_MARK,
366             "Could not study regex '%s': %s", bc_regex, err_str);
367         free(bc_regex);
368         pcre_free(compiled);
369         return 0;
370     } 
371
372     match_ret = pcre_exec(
373         compiled, extra, identifier, strlen(identifier), 0, 0, NULL, 0);       
374
375     free(bc_regex);
376     pcre_free(compiled);
377     if (extra) pcre_free(extra);
378
379     if (match_ret >= 0) return 1; // regex matched
380
381     if (match_ret != PCRE_ERROR_NOMATCH) 
382         osrfLogError(OSRF_LOG_MARK, "Unknown error processing barcode regex");
383
384     return 0; // regex did not match
385 }
386
387
388 /**
389         @brief Implement the "init" method.
390         @param ctx The method context.
391         @return Zero if successful, or -1 if not.
392
393         Method parameters:
394         - username
395         - nonce : optional login seed (string) provided by the caller which
396                 is added to the auth init cache to differentiate between logins
397                 using the same username and thus avoiding cache collisions for
398                 near-simultaneous logins.
399
400         Return to client: Intermediate authentication seed.
401 */
402 int oilsAuthInit(osrfMethodContext* ctx) {
403     OSRF_METHOD_VERIFY_CONTEXT(ctx);
404     int resp = 0;
405
406     char* identifier = // free
407         jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, 0));
408     const char* nonce = 
409         jsonObjectGetString(jsonObjectGetIndex(ctx->params, 1));
410
411     if (!nonce) nonce = "";
412     if (!identifier) return -1;  // we need an identifier
413
414     if (oilsAuthIdentIsBarcode(identifier, 0)) {
415         resp = oilsAuthInitBarcodeHandler(ctx, identifier, nonce);
416     } else {
417         resp = oilsAuthInitUsernameHandler(ctx, identifier, nonce);
418     }
419
420     free(identifier);
421     return resp;
422 }
423
424 /**
425         Returns 1 if the password provided matches the user's real password
426         Returns 0 otherwise
427         Returns -1 on error
428 */
429 /**
430         @brief Verify the password received from the client.
431         @param ctx The method context.
432         @param userObj An object from the database, representing the user.
433         @param password An obfuscated password received from the client.
434         @return 1 if the password is valid; 0 if it isn't; or -1 upon error.
435
436         (None of the so-called "passwords" used here are in plaintext.  All have been passed
437         through at least one layer of hashing to obfuscate them.)
438
439         Take the password from the user object.  Append it to the username seed from memcache,
440         as stored previously by a call to the init method.  Take an md5 hash of the result.
441         Then compare this hash to the password received from the client.
442
443         In order for the two to match, other than by dumb luck, the client had to construct
444         the password it passed in the same way.  That means it neded to know not only the
445         original password (either hashed or plaintext), but also the seed.  The latter requirement
446         means that the client process needs either to be the same process that called the init
447         method or to receive the seed from the process that did so.
448 */
449 static int oilsAuthVerifyPassword( const osrfMethodContext* ctx, int user_id, 
450         const char* identifier, const char* password, const char* nonce) {
451
452     int verified = 0;
453
454     // We won't be needing the seed again, remove it
455         char* key = va_list_to_string("%s%s%s", OILS_AUTH_CACHE_PRFX, identifier, nonce ); /**/
456     osrfCacheRemove(key);
457     free(key);
458
459     // Ask the DB to verify the user's password.
460     // Here, the password is md5(md5(password) + salt)
461
462     jsonObject* params = jsonParseFmt( // free
463         "{\"from\":[\"actor.verify_passwd\",%d,\"main\",\"%s\"]}", 
464         user_id, password);
465
466     jsonObject* verify_obj = // free 
467         oilsUtilsCStoreReq("open-ils.cstore.json_query", params);
468
469     jsonObjectFree(params);
470
471     if (verify_obj) {
472         verified = oilsUtilsIsDBTrue(
473             jsonObjectGetString(
474                 jsonObjectGetKeyConst(
475                     verify_obj, "actor.verify_passwd")));
476
477         jsonObjectFree(verify_obj);
478     }
479
480     char* countkey = va_list_to_string("%s%s%s", 
481         OILS_AUTH_CACHE_PRFX, identifier, OILS_AUTH_COUNT_SFFX );
482     jsonObject* countobject = osrfCacheGetObject( countkey );
483     if(countobject) {
484         long failcount = (long) jsonObjectGetNumber( countobject );
485         if(failcount >= _oilsAuthBlockCount) {
486             verified = 0;
487             osrfLogInfo(OSRF_LOG_MARK, 
488                 "oilsAuth found too many recent failures for '%s' : %i, "
489                 "forcing failure state.", identifier, failcount);
490         }
491         if(verified == 0) {
492             failcount += 1;
493         }
494         jsonObjectSetNumber( countobject, failcount );
495         osrfCachePutObject( countkey, countobject, _oilsAuthBlockTimeout );
496         jsonObjectFree(countobject);
497     }
498     free(countkey);
499
500     return verified;
501 }
502
503 /**
504  * Returns true if the provided password is correct.
505  * Turn the password into the nested md5 hash required of migrated
506  * passwords, then check the password in the DB.
507  */
508 static int oilsAuthLoginCheckPassword(int user_id, const char* password) {
509
510     growing_buffer* gb = buffer_init(33); // free me 1
511     char* salt = oilsAuthGetSalt(user_id); // free me 2
512     char* passhash = md5sum(password); // free me 3
513
514     buffer_add(gb, salt); // gb strdup's internally
515     buffer_add(gb, passhash);
516
517     free(salt); // free 2
518     free(passhash); // free 3
519
520     // salt + md5(password)
521     passhash = buffer_release(gb); // free 1 ; free me 4
522     char* finalpass = md5sum(passhash); // free me 5
523
524     free(passhash); // free 4
525
526     jsonObject *arr = jsonNewObjectType(JSON_ARRAY);
527     jsonObjectPush(arr, jsonNewObject("actor.verify_passwd"));
528     jsonObjectPush(arr, jsonNewNumberObject((long) user_id));
529     jsonObjectPush(arr, jsonNewObject("main"));
530     jsonObjectPush(arr, jsonNewObject(finalpass));
531     jsonObject *params = jsonNewObjectType(JSON_HASH); // free me 6
532     jsonObjectSetKey(params, "from", arr);
533
534     free(finalpass); // free 5
535
536     jsonObject* verify_obj = // free 
537         oilsUtilsCStoreReq("open-ils.cstore.json_query", params);
538
539     jsonObjectFree(params); // free 6
540
541     if (!verify_obj) return 0; // error
542
543     int verified = oilsUtilsIsDBTrue(
544         jsonObjectGetString(
545             jsonObjectGetKeyConst(verify_obj, "actor.verify_passwd")
546         )
547     );
548
549     jsonObjectFree(verify_obj);
550
551     return verified;
552 }
553
554 static int oilsAuthLoginVerifyPassword(const osrfMethodContext* ctx, 
555     int user_id, const char* username, const char* password) {
556
557     // build the cache key
558     growing_buffer* gb = buffer_init(64); // free me
559     buffer_add(gb, OILS_AUTH_CACHE_PRFX);
560     buffer_add(gb, username);
561     buffer_add(gb, OILS_AUTH_COUNT_SFFX);
562     char* countkey = buffer_release(gb); // free me
563
564     jsonObject* countobject = osrfCacheGetObject(countkey); // free me
565
566     long failcount = 0;
567     if (countobject) {
568         failcount = (long) jsonObjectGetNumber(countobject);
569
570         if (failcount >= _oilsAuthBlockCount) {
571             // User is blocked.  Don't waste any more CPU cycles on them.
572
573             osrfLogInfo(OSRF_LOG_MARK, 
574                 "oilsAuth found too many recent failures for '%s' : %i, "
575                 "forcing failure state.", username, failcount);
576
577             jsonObjectFree(countobject);
578             free(countkey);   
579             return 0;
580         }
581     }
582
583     int verified = oilsAuthLoginCheckPassword(user_id, password);
584
585     if (!verified) { // login failed.  increment failure counter.
586         failcount++;
587
588         if (countobject) {
589             // append to existing counter
590             jsonObjectSetNumber(countobject, failcount);
591
592         } else { 
593             // first failure, create a new counter
594             countobject = jsonNewNumberObject((double) failcount);
595         }
596
597         osrfCachePutObject(countkey, countobject, _oilsAuthBlockTimeout);
598     }
599
600     jsonObjectFree(countobject); // NULL OK
601     free(countkey);
602
603     return verified;
604 }
605
606
607 /*
608         Adds the authentication token to the user cache.  The timeout for the
609         auth token is based on the type of login as well as (if type=='opac')
610         the org location id.
611         Returns the event that should be returned to the user.
612         Event must be freed
613 */
614 static oilsEvent* oilsAuthHandleLoginOK( osrfMethodContext* ctx, jsonObject* userObj, const char* uname,
615                 const char* type, int orgloc, const char* workstation ) {
616
617         oilsEvent* response = NULL;
618
619     jsonObject* params = jsonNewObject(NULL);
620     jsonObjectSetKey(params, "user_id", 
621         jsonNewNumberObject(oilsFMGetObjectId(userObj)));
622     jsonObjectSetKey(params,"org_unit", jsonNewNumberObject(orgloc));
623     jsonObjectSetKey(params, "login_type", jsonNewObject(type));
624     if (workstation) 
625         jsonObjectSetKey(params, "workstation", jsonNewObject(workstation));
626
627     jsonObject* authEvt = oilsUtilsQuickReqCtx(
628         ctx,
629         "open-ils.auth_internal",
630         "open-ils.auth_internal.session.create", params);
631     jsonObjectFree(params);
632
633     if (authEvt) {
634
635         response = oilsNewEvent2(
636             OSRF_LOG_MARK, 
637             jsonObjectGetString(jsonObjectGetKey(authEvt, "textcode")),
638             jsonObjectGetKey(authEvt, "payload")   // cloned within Event
639         );
640
641         osrfLogActivity(OSRF_LOG_MARK,
642             "successful login: username=%s, authtoken=%s, workstation=%s",
643             uname,
644             jsonObjectGetString(
645                 jsonObjectGetKeyConst(
646                     jsonObjectGetKeyConst(authEvt, "payload"),
647                     "authtoken"
648                 )
649             ),
650             workstation ? workstation : ""
651         );
652
653         jsonObjectFree(authEvt);
654
655     } else {
656         osrfLogError(OSRF_LOG_MARK, 
657             "Error caching auth session in open-ils.auth_internal");
658     }
659
660     return response;
661 }
662
663
664 /**
665         @brief Implement the "complete" method.
666         @param ctx The method context.
667         @return -1 upon error; zero if successful, and if a STATUS message has been sent to the
668         client to indicate completion; a positive integer if successful but no such STATUS
669         message has been sent.
670
671         Method parameters:
672         - a hash with some combination of the following elements:
673                 - "username"
674                 - "barcode"
675                 - "password" (hashed with the cached seed; not plaintext)
676                 - "type"
677                 - "org"
678                 - "workstation"
679                 - "agent" (what software/interface/3rd-party is making the request)
680                 - "nonce" optional login seed to differentiate logins using the same username.
681
682         The password is required.  Either a username or a barcode must also be present.
683
684         Return to client: Intermediate authentication seed.
685
686         Validate the password, using the username if available, or the barcode if not.  The
687         user must be active, and not barred from logging on.  The barcode, if used for
688         authentication, must be active as well.  The workstation, if specified, must be valid.
689
690         Upon deciding whether to allow the logon, return a corresponding event to the client.
691 */
692 int oilsAuthComplete( osrfMethodContext* ctx ) {
693     OSRF_METHOD_VERIFY_CONTEXT(ctx);
694
695     const jsonObject* args  = jsonObjectGetIndex(ctx->params, 0);
696
697     const char* uname       = jsonObjectGetString(jsonObjectGetKeyConst(args, "username"));
698     const char* identifier  = jsonObjectGetString(jsonObjectGetKeyConst(args, "identifier"));
699     const char* password    = jsonObjectGetString(jsonObjectGetKeyConst(args, "password"));
700     const char* type        = jsonObjectGetString(jsonObjectGetKeyConst(args, "type"));
701     int orgloc        = (int) jsonObjectGetNumber(jsonObjectGetKeyConst(args, "org"));
702     const char* workstation = jsonObjectGetString(jsonObjectGetKeyConst(args, "workstation"));
703     const char* barcode     = jsonObjectGetString(jsonObjectGetKeyConst(args, "barcode"));
704     const char* ewho        = jsonObjectGetString(jsonObjectGetKeyConst(args, "agent"));
705     const char* nonce       = jsonObjectGetString(jsonObjectGetKeyConst(args, "nonce"));
706
707     const char* ws = (workstation) ? workstation : "";
708     if (!nonce) nonce = "";
709
710     // we no longer care how the identifier reaches us, 
711     // as long as we have one.
712     if (!identifier) {
713         if (uname) {
714             identifier = uname;
715         } else if (barcode) {
716             identifier = barcode;
717         }
718     }
719
720     if (!identifier) {
721         return osrfAppRequestRespondException(ctx->session, ctx->request,
722             "username/barcode and password required for method: %s", 
723             ctx->method->name);
724     }
725
726     osrfLogInfo(OSRF_LOG_MARK, 
727         "Patron completing authentication with identifer %s", identifier);
728
729     /* Use __FILE__, harmless_line_number for creating
730      * OILS_EVENT_AUTH_FAILED events (instead of OSRF_LOG_MARK) to avoid
731      * giving away information about why an authentication attempt failed.
732      */
733     int harmless_line_number = __LINE__;
734
735     if( !type )
736          type = OILS_AUTH_STAFF;
737
738     oilsEvent* response = NULL; // free
739     jsonObject* userObj = NULL; // free
740
741     char* cache_key = va_list_to_string(
742         "%s%s%s", OILS_AUTH_CACHE_PRFX, identifier, nonce);
743     jsonObject* cacheObj = osrfCacheGetObject(cache_key); // free
744
745     if (!cacheObj) {
746         return osrfAppRequestRespondException(ctx->session,
747             ctx->request, "No authentication seed found. "
748             "open-ils.auth.authenticate.init must be called first "
749             " (check that memcached is running and can be connected to) "
750         );
751     }
752
753     int user_id = jsonObjectGetNumber(
754         jsonObjectGetKeyConst(cacheObj, "user_id"));
755
756     if (user_id == -1) {
757         // User was not found during init.  Clean up and exit early.
758         response = oilsNewEvent(
759             __FILE__, harmless_line_number, OILS_EVENT_AUTH_FAILED);
760         osrfAppRespondComplete(ctx, oilsEventToJSON(response));
761         oilsEventFree(response); // frees event JSON
762         osrfCacheRemove(cache_key);
763         jsonObjectFree(cacheObj);
764         return 0;
765     }
766
767     jsonObject* param = jsonNewNumberObject(user_id); // free
768     userObj = oilsUtilsCStoreReqCtx(
769         ctx, "open-ils.cstore.direct.actor.user.retrieve", param);
770     jsonObjectFree(param);
771
772     // determine if authenticate.init had found the user by barcode,
773     // regardless of whether authenticate.complete is being passed
774     // a username or identifier key.
775     bool initFoundUserByBarcode = false;
776     jsonObject* value = NULL;
777     jsonIterator* cacheIter = jsonNewIterator(cacheObj);
778     while (value = jsonIteratorNext(cacheIter)) {
779         const char *key_name = cacheIter->key;
780         if (!strcmp(key_name, "barcode")) {
781             initFoundUserByBarcode = true;
782             break;
783         }
784     }
785     jsonIteratorFree(cacheIter);
786
787     char* freeable_uname = NULL;
788     if (!uname) {
789         uname = freeable_uname = oilsFMGetString(userObj, "usrname");
790     }
791
792     // See if the user is allowed to login.
793
794     jsonObject* params = jsonNewObject(NULL);
795     jsonObjectSetKey(params, "user_id", 
796         jsonNewNumberObject(oilsFMGetObjectId(userObj)));
797     jsonObjectSetKey(params,"org_unit", jsonNewNumberObject(orgloc));
798     jsonObjectSetKey(params, "login_type", jsonNewObject(type));
799     if (initFoundUserByBarcode) {
800          jsonObjectSetKey(params, "barcode", jsonNewObject(identifier));
801     } else if (barcode) {
802          jsonObjectSetKey(params, "barcode", jsonNewObject(barcode));
803     }
804
805     jsonObject* authEvt = oilsUtilsQuickReqCtx( // freed after password test
806         ctx,
807         "open-ils.auth_internal",
808         "open-ils.auth_internal.user.validate", params);
809     jsonObjectFree(params);
810
811     if (!authEvt) {
812         // Something went seriously wrong.  Get outta here before 
813         // we start segfaulting.
814         jsonObjectFree(userObj);
815         if(freeable_uname) free(freeable_uname);
816         return -1;
817     }
818
819     const char* authEvtCode = 
820         jsonObjectGetString(jsonObjectGetKey(authEvt, "textcode"));
821
822     if (!strcmp(authEvtCode, OILS_EVENT_AUTH_FAILED)) {
823         // Received the generic login failure event.
824
825         osrfLogInfo(OSRF_LOG_MARK,  
826             "failed login: username=%s, barcode=%s, workstation=%s",
827             uname, (barcode ? barcode : "(none)"), ws);
828
829         response = oilsNewEvent(
830             __FILE__, harmless_line_number, OILS_EVENT_AUTH_FAILED);
831     }
832
833     int passOK = 0;
834     
835     if (!response) {
836         // User exists and is not barred, etc.  Test the password.
837
838         passOK = oilsAuthVerifyPassword(
839             ctx, user_id, identifier, password, nonce);
840
841         if (!passOK) {
842             // Password check failed. Return generic login failure.
843
844             response = oilsNewEvent(
845                 __FILE__, harmless_line_number, OILS_EVENT_AUTH_FAILED);
846
847             osrfLogInfo(OSRF_LOG_MARK,  
848                 "failed login: username=%s, barcode=%s, workstation=%s",
849                     uname, (barcode ? barcode : "(none)"), ws );
850         }
851     }
852
853
854     // Below here, we know the password check succeeded if no response
855     // object is present.
856
857     if (!response && (
858         !strcmp(authEvtCode, "PATRON_INACTIVE") ||
859         !strcmp(authEvtCode, "PATRON_CARD_INACTIVE"))) {
860         // Patron and/or card is inactive but the correct password 
861         // was provided.  Alert the caller to the inactive-ness.
862         response = oilsNewEvent2(
863             OSRF_LOG_MARK, authEvtCode,
864             jsonObjectGetKey(authEvt, "payload")   // cloned within Event
865         );
866     }
867
868     if (!response && strcmp(authEvtCode, OILS_EVENT_SUCCESS)) {
869         // Validate API returned an unexpected non-success event.
870         // To be safe, treat this as a generic login failure.
871
872         response = oilsNewEvent(
873             __FILE__, harmless_line_number, OILS_EVENT_AUTH_FAILED);
874     }
875
876     if (!response) {
877         // password OK and no other events have prevented login completion.
878
879         char* ewhat = "login";
880
881         if (0 == strcmp(ctx->method->name, "open-ils.auth.authenticate.verify")) {
882             response = oilsNewEvent( OSRF_LOG_MARK, OILS_EVENT_SUCCESS );
883             ewhat = "verify";
884
885         } else {
886             response = oilsAuthHandleLoginOK(
887                 ctx, userObj, uname, type, orgloc, workstation);
888         }
889
890         oilsUtilsTrackUserActivity(
891             ctx,
892             oilsFMGetObjectId(userObj), 
893             ewho, ewhat, 
894             osrfAppSessionGetIngress()
895         );
896     }
897
898     // reply
899     osrfAppRespondComplete(ctx, oilsEventToJSON(response));
900
901     // clean up
902     oilsEventFree(response);
903     jsonObjectFree(userObj);
904     jsonObjectFree(authEvt);
905     jsonObjectFree(cacheObj);
906     if(freeable_uname)
907         free(freeable_uname);
908
909     return 0;
910 }
911
912
913 int oilsAuthLogin(osrfMethodContext* ctx) {
914     OSRF_METHOD_VERIFY_CONTEXT(ctx);
915
916     const jsonObject* args  = jsonObjectGetIndex(ctx->params, 0);
917
918     const char* username    = jsonObjectGetString(jsonObjectGetKeyConst(args, "username"));
919     const char* identifier  = jsonObjectGetString(jsonObjectGetKeyConst(args, "identifier"));
920     const char* password    = jsonObjectGetString(jsonObjectGetKeyConst(args, "password"));
921     const char* type        = jsonObjectGetString(jsonObjectGetKeyConst(args, "type"));
922     int orgloc        = (int) jsonObjectGetNumber(jsonObjectGetKeyConst(args, "org"));
923     const char* workstation = jsonObjectGetString(jsonObjectGetKeyConst(args, "workstation"));
924     const char* barcode     = jsonObjectGetString(jsonObjectGetKeyConst(args, "barcode"));
925     const char* ewho        = jsonObjectGetString(jsonObjectGetKeyConst(args, "agent"));
926
927     const char* ws = (workstation) ? workstation : "";
928     if (!type) type = OILS_AUTH_STAFF;
929
930     jsonObject* userObj = NULL; // free me
931     oilsEvent* response = NULL; // free me
932
933     /* Use __FILE__, harmless_line_number for creating
934      * OILS_EVENT_AUTH_FAILED events (instead of OSRF_LOG_MARK) to avoid
935      * giving away information about why an authentication attempt failed.
936      */
937     int harmless_line_number = __LINE__;
938
939     // translate a generic identifier into a username or barcode if necessary.
940     if (identifier && !username && !barcode) {
941         if (oilsAuthIdentIsBarcode(identifier, orgloc)) {
942             barcode = identifier;
943         } else {
944             username = identifier;
945         }
946     }
947
948     if (username) {
949         barcode = NULL; // avoid superfluous identifiers
950         userObj = oilsUtilsFetchUserByUsername(ctx, username);
951
952     } else if (barcode) {
953         userObj = oilsUtilsFetchUserByBarcode(ctx, barcode);
954
955     } else {
956         // not enough params
957         return osrfAppRequestRespondException(ctx->session, ctx->request,
958             "username/barcode and password required for method: %s", 
959             ctx->method->name);
960     }
961
962     if (!userObj) { // user not found.  
963         response = oilsNewEvent(
964             __FILE__, harmless_line_number, OILS_EVENT_AUTH_FAILED);
965         osrfAppRespondComplete(ctx, oilsEventToJSON(response));
966         oilsEventFree(response); // frees event JSON
967         return 0;
968     }
969
970     long user_id = oilsFMGetObjectId(userObj);
971
972     // username is freed when userObj is freed.
973     // From here we can use the username as the generic identifier
974     // since it's guaranteed to have a value.
975     if (!username) username = oilsFMGetStringConst(userObj, "usrname");
976
977     // See if the user is allowed to login.
978     jsonObject* params = jsonNewObject(NULL);
979     jsonObjectSetKey(params, "user_id", jsonNewNumberObject(user_id));
980     jsonObjectSetKey(params,"org_unit", jsonNewNumberObject(orgloc));
981     jsonObjectSetKey(params, "login_type", jsonNewObject(type));
982     if (barcode) jsonObjectSetKey(params, "barcode", jsonNewObject(barcode));
983
984     jsonObject* authEvt = oilsUtilsQuickReqCtx( // freed after password test
985         ctx,
986         "open-ils.auth_internal",
987         "open-ils.auth_internal.user.validate", params);
988     jsonObjectFree(params);
989
990     if (!authEvt) { // unknown error
991         jsonObjectFree(userObj);
992         return -1;
993     }
994
995     const char* authEvtCode = 
996         jsonObjectGetString(jsonObjectGetKey(authEvt, "textcode"));
997
998     if (!strcmp(authEvtCode, OILS_EVENT_AUTH_FAILED)) {
999         // Received the generic login failure event.
1000
1001         osrfLogInfo(OSRF_LOG_MARK,  
1002             "failed login: username=%s, barcode=%s, workstation=%s",
1003             username, (barcode ? barcode : "(none)"), ws);
1004
1005         response = oilsNewEvent(
1006             __FILE__, harmless_line_number, OILS_EVENT_AUTH_FAILED);
1007     }
1008
1009     if (!response && // user exists and is not barred, etc.
1010         !oilsAuthLoginVerifyPassword(ctx, user_id, username, password)) {
1011         // User provided the wrong password or is blocked from too 
1012         // many previous login failures.
1013
1014         response = oilsNewEvent(
1015             __FILE__, harmless_line_number, OILS_EVENT_AUTH_FAILED);
1016
1017         osrfLogInfo(OSRF_LOG_MARK,  
1018             "failed login: username=%s, barcode=%s, workstation=%s",
1019                 username, (barcode ? barcode : "(none)"), ws );
1020     }
1021
1022     // Below here, we know the password check succeeded if no response
1023     // object is present.
1024
1025     if (!response && (
1026         !strcmp(authEvtCode, "PATRON_INACTIVE") ||
1027         !strcmp(authEvtCode, "PATRON_CARD_INACTIVE"))) {
1028         // Patron and/or card is inactive but the correct password 
1029         // was provided.  Alert the caller to the inactive-ness.
1030         response = oilsNewEvent2(
1031             OSRF_LOG_MARK, authEvtCode,
1032             jsonObjectGetKey(authEvt, "payload")   // cloned within Event
1033         );
1034     }
1035
1036     if (!response && strcmp(authEvtCode, OILS_EVENT_SUCCESS)) {
1037         // Validate API returned an unexpected non-success event.
1038         // To be safe, treat this as a generic login failure.
1039
1040         response = oilsNewEvent(
1041             __FILE__, harmless_line_number, OILS_EVENT_AUTH_FAILED);
1042     }
1043
1044     if (!response) {
1045         // password OK and no other events have prevented login completion.
1046
1047         char* ewhat = "login";
1048
1049         if (0 == strcmp(ctx->method->name, "open-ils.auth.authenticate.verify")) {
1050             response = oilsNewEvent( OSRF_LOG_MARK, OILS_EVENT_SUCCESS );
1051             ewhat = "verify";
1052
1053         } else {
1054             response = oilsAuthHandleLoginOK(
1055                 ctx, userObj, username, type, orgloc, workstation);
1056         }
1057
1058         oilsUtilsTrackUserActivity(
1059             ctx,
1060             oilsFMGetObjectId(userObj), 
1061             ewho, ewhat, 
1062             osrfAppSessionGetIngress()
1063         );
1064     }
1065
1066     // reply
1067     osrfAppRespondComplete(ctx, oilsEventToJSON(response));
1068
1069     // clean up
1070     oilsEventFree(response);
1071     jsonObjectFree(userObj);
1072     jsonObjectFree(authEvt);
1073
1074         return 0;
1075 }
1076
1077
1078
1079 int oilsAuthSessionDelete( osrfMethodContext* ctx ) {
1080         OSRF_METHOD_VERIFY_CONTEXT(ctx);
1081
1082         const char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0) );
1083         jsonObject* resp = NULL;
1084
1085         if( authToken ) {
1086                 osrfLogDebug(OSRF_LOG_MARK, "Removing auth session: %s", authToken );
1087                 char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken ); /**/
1088                 osrfCacheRemove(key);
1089                 resp = jsonNewObject(authToken); /**/
1090                 free(key);
1091         }
1092
1093         osrfAppRespondComplete( ctx, resp );
1094         jsonObjectFree(resp);
1095         return 0;
1096 }
1097
1098 /**
1099  * Fetches the user object from the database and updates the user object in 
1100  * the cache object, which then has to be re-inserted into the cache.
1101  * User object is retrieved inside a transaction to avoid replication issues.
1102  */
1103 static int _oilsAuthReloadUser(jsonObject* cacheObj) {
1104     int reqid, userId;
1105     osrfAppSession* session;
1106         osrfMessage* omsg;
1107     jsonObject *param, *userObj, *newUserObj = NULL;
1108
1109     userObj = jsonObjectGetKey( cacheObj, "userobj" );
1110     userId = oilsFMGetObjectId( userObj );
1111
1112     session = osrfAppSessionClientInit( "open-ils.cstore" );
1113     osrfAppSessionConnect(session);
1114
1115     reqid = osrfAppSessionSendRequest(session, NULL, "open-ils.cstore.transaction.begin", 1);
1116         omsg = osrfAppSessionRequestRecv(session, reqid, 60);
1117
1118     if(omsg) {
1119
1120         osrfMessageFree(omsg);
1121         param = jsonNewNumberObject(userId);
1122         reqid = osrfAppSessionSendRequest(session, param, "open-ils.cstore.direct.actor.user.retrieve", 1);
1123             omsg = osrfAppSessionRequestRecv(session, reqid, 60);
1124         jsonObjectFree(param);
1125
1126         if(omsg) {
1127             newUserObj = jsonObjectClone( osrfMessageGetResult(omsg) );
1128             osrfMessageFree(omsg);
1129             reqid = osrfAppSessionSendRequest(session, NULL, "open-ils.cstore.transaction.rollback", 1);
1130                 omsg = osrfAppSessionRequestRecv(session, reqid, 60);
1131             osrfMessageFree(omsg);
1132         }
1133     }
1134
1135     osrfAppSessionFree(session); // calls disconnect internally
1136
1137     if(newUserObj) {
1138
1139         // ws_ou and wsid are ephemeral and need to be manually propagated
1140         // oilsFMSetString dupe()'s internally, no need to clone the string
1141         oilsFMSetString(newUserObj, "wsid", oilsFMGetStringConst(userObj, "wsid"));
1142         oilsFMSetString(newUserObj, "ws_ou", oilsFMGetStringConst(userObj, "ws_ou"));
1143
1144         jsonObjectRemoveKey(cacheObj, "userobj"); // this also frees the old user object
1145         jsonObjectSetKey(cacheObj, "userobj", newUserObj);
1146         return 1;
1147     } 
1148
1149     osrfLogError(OSRF_LOG_MARK, "Error retrieving user %d from database", userId);
1150     return 0;
1151 }
1152
1153 /**
1154         Resets the auth login timeout
1155         @return The event object, OILS_EVENT_SUCCESS, or OILS_EVENT_NO_SESSION
1156 */
1157 static oilsEvent*  _oilsAuthResetTimeout( const char* authToken, int reloadUser ) {
1158         if(!authToken) return NULL;
1159
1160         oilsEvent* evt = NULL;
1161         time_t timeout;
1162
1163         osrfLogDebug(OSRF_LOG_MARK, "Resetting auth timeout for session %s", authToken);
1164         char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken );
1165         jsonObject* cacheObj = osrfCacheGetObject( key );
1166
1167         if(!cacheObj) {
1168                 osrfLogInfo(OSRF_LOG_MARK, "No user in the cache exists with key %s", key);
1169                 evt = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_NO_SESSION);
1170
1171         } else {
1172
1173         if(reloadUser) {
1174             _oilsAuthReloadUser(cacheObj);
1175         }
1176
1177                 // Determine a new timeout value
1178                 jsonObject* endtime_obj = jsonObjectGetKey( cacheObj, "endtime" );
1179                 if( endtime_obj ) {
1180                         // Extend the current endtime by a fixed amount
1181                         time_t endtime = (time_t) jsonObjectGetNumber( endtime_obj );
1182                         int reset_interval = DEFAULT_RESET_INTERVAL;
1183                         const jsonObject* reset_interval_obj = jsonObjectGetKeyConst(
1184                                 cacheObj, "reset_interval" );
1185                         if( reset_interval_obj ) {
1186                                 reset_interval = (int) jsonObjectGetNumber( reset_interval_obj );
1187                                 if( reset_interval <= 0 )
1188                                         reset_interval = DEFAULT_RESET_INTERVAL;
1189                         }
1190
1191                         time_t now = time( NULL );
1192                         time_t new_endtime = now + reset_interval;
1193                         if( new_endtime > endtime ) {
1194                                 // Keep the session alive a little longer
1195                                 jsonObjectSetNumber( endtime_obj, (double) new_endtime );
1196                                 timeout = reset_interval;
1197                                 osrfCachePutObject( key, cacheObj, timeout );
1198                         } else {
1199                                 // The session isn't close to expiring, so don't reset anything.
1200                                 // Just report the time remaining.
1201                                 timeout = endtime - now;
1202                         }
1203                 } else {
1204                         // Reapply the existing timeout from the current time
1205                         timeout = (time_t) jsonObjectGetNumber( jsonObjectGetKeyConst( cacheObj, "authtime"));
1206                         osrfCachePutObject( key, cacheObj, timeout );
1207                 }
1208
1209                 jsonObject* payload = jsonNewNumberObject( (double) timeout );
1210                 evt = oilsNewEvent2(OSRF_LOG_MARK, OILS_EVENT_SUCCESS, payload);
1211                 jsonObjectFree(payload);
1212                 jsonObjectFree(cacheObj);
1213         }
1214
1215         free(key);
1216         return evt;
1217 }
1218
1219 int oilsAuthResetTimeout( osrfMethodContext* ctx ) {
1220         OSRF_METHOD_VERIFY_CONTEXT(ctx);
1221         const char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0));
1222     double reloadUser = jsonObjectGetNumber( jsonObjectGetIndex(ctx->params, 1));
1223         oilsEvent* evt = _oilsAuthResetTimeout(authToken, (int) reloadUser);
1224         osrfAppRespondComplete( ctx, oilsEventToJSON(evt) );
1225         oilsEventFree(evt);
1226         return 0;
1227 }
1228
1229
1230 int oilsAuthSessionRetrieve( osrfMethodContext* ctx ) {
1231         OSRF_METHOD_VERIFY_CONTEXT(ctx);
1232     bool returnFull = false;
1233     bool noTimeoutReset = false;
1234
1235         const char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0));
1236
1237     if(ctx->params->size > 1) {
1238         // caller wants full cached object, with authtime, etc.
1239         const char* rt = jsonObjectGetString(jsonObjectGetIndex(ctx->params, 1));
1240         if(rt && strcmp(rt, "0") != 0) 
1241             returnFull = true;
1242
1243         if (ctx->params->size > 2) {
1244             // Avoid resetting the auth session timeout.
1245             const char* noReset = 
1246                 jsonObjectGetString(jsonObjectGetIndex(ctx->params, 2));
1247             if (noReset && strcmp(noReset, "0") != 0) 
1248                 noTimeoutReset = true;
1249         }
1250     }
1251
1252         jsonObject* cacheObj = NULL;
1253         oilsEvent* evt = NULL;
1254
1255         if( authToken ){
1256
1257                 // Reset the timeout to keep the session alive
1258         if (!noTimeoutReset) 
1259                     evt = _oilsAuthResetTimeout(authToken, 0);
1260
1261                 if( evt && strcmp(evt->event, OILS_EVENT_SUCCESS) ) {
1262                         osrfAppRespondComplete( ctx, oilsEventToJSON( evt ));    // can't reset timeout
1263
1264                 } else {
1265
1266                         // Retrieve the cached session object
1267                         osrfLogDebug(OSRF_LOG_MARK, "Retrieving auth session: %s", authToken);
1268                         char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken );
1269                         cacheObj = osrfCacheGetObject( key );
1270                         if(cacheObj) {
1271                                 // Return a copy of the cached user object
1272                 if(returnFull)
1273                                     osrfAppRespondComplete( ctx, cacheObj);
1274                 else
1275                                     osrfAppRespondComplete( ctx, jsonObjectGetKeyConst( cacheObj, "userobj"));
1276                                 jsonObjectFree(cacheObj);
1277                         } else {
1278                                 // Auth token is invalid or expired
1279                                 oilsEvent* evt2 = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_NO_SESSION);
1280                                 osrfAppRespondComplete( ctx, oilsEventToJSON(evt2) ); /* should be event.. */
1281                                 oilsEventFree(evt2);
1282                         }
1283                         free(key);
1284                 }
1285
1286         } else {
1287
1288                 // No session
1289                 evt = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_NO_SESSION);
1290                 osrfAppRespondComplete( ctx, oilsEventToJSON(evt) );
1291         }
1292
1293         if(evt)
1294                 oilsEventFree(evt);
1295
1296         return 0;
1297 }