]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/c-apps/oils_auth.c
Docs: incorporating offline circ docs
[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     osrfCacheRemove("%s%s%s", OILS_AUTH_CACHE_PRFX, identifier, nonce);
456
457     // Ask the DB to verify the user's password.
458     // Here, the password is md5(md5(password) + salt)
459
460     jsonObject* params = jsonParseFmt( // free
461         "{\"from\":[\"actor.verify_passwd\",%d,\"main\",\"%s\"]}", 
462         user_id, password);
463
464     jsonObject* verify_obj = // free 
465         oilsUtilsCStoreReq("open-ils.cstore.json_query", params);
466
467     jsonObjectFree(params);
468
469     if (verify_obj) {
470         verified = oilsUtilsIsDBTrue(
471             jsonObjectGetString(
472                 jsonObjectGetKeyConst(
473                     verify_obj, "actor.verify_passwd")));
474
475         jsonObjectFree(verify_obj);
476     }
477
478     char* countkey = va_list_to_string("%s%s%s", 
479         OILS_AUTH_CACHE_PRFX, identifier, OILS_AUTH_COUNT_SFFX );
480     jsonObject* countobject = osrfCacheGetObject( countkey );
481     if(countobject) {
482         long failcount = (long) jsonObjectGetNumber( countobject );
483         if(failcount >= _oilsAuthBlockCount) {
484             verified = 0;
485             osrfLogInfo(OSRF_LOG_MARK, 
486                 "oilsAuth found too many recent failures for '%s' : %i, "
487                 "forcing failure state.", identifier, failcount);
488         }
489         if(verified == 0) {
490             failcount += 1;
491         }
492         jsonObjectSetNumber( countobject, failcount );
493         osrfCachePutObject( countkey, countobject, _oilsAuthBlockTimeout );
494         jsonObjectFree(countobject);
495     }
496     free(countkey);
497
498     return verified;
499 }
500
501 /**
502  * Returns true if the provided password is correct.
503  * Turn the password into the nested md5 hash required of migrated
504  * passwords, then check the password in the DB.
505  */
506 static int oilsAuthLoginCheckPassword(int user_id, const char* password) {
507
508     growing_buffer* gb = buffer_init(33); // free me 1
509     char* salt = oilsAuthGetSalt(user_id); // free me 2
510     char* passhash = md5sum(password); // free me 3
511
512     buffer_add(gb, salt); // gb strdup's internally
513     buffer_add(gb, passhash);
514
515     free(salt); // free 2
516     free(passhash); // free 3
517
518     // salt + md5(password)
519     passhash = buffer_release(gb); // free 1 ; free me 4
520     char* finalpass = md5sum(passhash); // free me 5
521
522     free(passhash); // free 4
523
524     jsonObject *arr = jsonNewObjectType(JSON_ARRAY);
525     jsonObjectPush(arr, jsonNewObject("actor.verify_passwd"));
526     jsonObjectPush(arr, jsonNewNumberObject((long) user_id));
527     jsonObjectPush(arr, jsonNewObject("main"));
528     jsonObjectPush(arr, jsonNewObject(finalpass));
529     jsonObject *params = jsonNewObjectType(JSON_HASH); // free me 6
530     jsonObjectSetKey(params, "from", arr);
531
532     free(finalpass); // free 5
533
534     jsonObject* verify_obj = // free 
535         oilsUtilsCStoreReq("open-ils.cstore.json_query", params);
536
537     jsonObjectFree(params); // free 6
538
539     if (!verify_obj) return 0; // error
540
541     int verified = oilsUtilsIsDBTrue(
542         jsonObjectGetString(
543             jsonObjectGetKeyConst(verify_obj, "actor.verify_passwd")
544         )
545     );
546
547     jsonObjectFree(verify_obj);
548
549     return verified;
550 }
551
552 static int oilsAuthLoginVerifyPassword(const osrfMethodContext* ctx, 
553     int user_id, const char* username, const char* password) {
554
555     // build the cache key
556     growing_buffer* gb = buffer_init(64); // free me
557     buffer_add(gb, OILS_AUTH_CACHE_PRFX);
558     buffer_add(gb, username);
559     buffer_add(gb, OILS_AUTH_COUNT_SFFX);
560     char* countkey = buffer_release(gb); // free me
561
562     jsonObject* countobject = osrfCacheGetObject(countkey); // free me
563
564     long failcount = 0;
565     if (countobject) {
566         failcount = (long) jsonObjectGetNumber(countobject);
567
568         if (failcount >= _oilsAuthBlockCount) {
569             // User is blocked.  Don't waste any more CPU cycles on them.
570
571             osrfLogInfo(OSRF_LOG_MARK, 
572                 "oilsAuth found too many recent failures for '%s' : %i, "
573                 "forcing failure state.", username, failcount);
574
575             jsonObjectFree(countobject);
576             free(countkey);   
577             return 0;
578         }
579     }
580
581     int verified = oilsAuthLoginCheckPassword(user_id, password);
582
583     if (!verified) { // login failed.  increment failure counter.
584         failcount++;
585
586         if (countobject) {
587             // append to existing counter
588             jsonObjectSetNumber(countobject, failcount);
589
590         } else { 
591             // first failure, create a new counter
592             countobject = jsonNewNumberObject((double) failcount);
593         }
594
595         osrfCachePutObject(countkey, countobject, _oilsAuthBlockTimeout);
596     }
597
598     jsonObjectFree(countobject); // NULL OK
599     free(countkey);
600
601     return verified;
602 }
603
604
605 /*
606         Adds the authentication token to the user cache.  The timeout for the
607         auth token is based on the type of login as well as (if type=='opac')
608         the org location id.
609         Returns the event that should be returned to the user.
610         Event must be freed
611 */
612 static oilsEvent* oilsAuthHandleLoginOK( osrfMethodContext* ctx, jsonObject* userObj, const char* uname,
613                 const char* type, int orgloc, const char* workstation ) {
614
615         oilsEvent* response = NULL;
616
617     jsonObject* params = jsonNewObject(NULL);
618     jsonObjectSetKey(params, "user_id", 
619         jsonNewNumberObject(oilsFMGetObjectId(userObj)));
620     jsonObjectSetKey(params,"org_unit", jsonNewNumberObject(orgloc));
621     jsonObjectSetKey(params, "login_type", jsonNewObject(type));
622     if (workstation) 
623         jsonObjectSetKey(params, "workstation", jsonNewObject(workstation));
624
625     jsonObject* authEvt = oilsUtilsQuickReqCtx(
626         ctx,
627         "open-ils.auth_internal",
628         "open-ils.auth_internal.session.create", params);
629     jsonObjectFree(params);
630
631     if (authEvt) {
632
633         response = oilsNewEvent2(
634             OSRF_LOG_MARK, 
635             jsonObjectGetString(jsonObjectGetKey(authEvt, "textcode")),
636             jsonObjectGetKey(authEvt, "payload")   // cloned within Event
637         );
638
639         osrfLogActivity(OSRF_LOG_MARK,
640             "successful login: username=%s, authtoken=%s, workstation=%s",
641             uname,
642             jsonObjectGetString(
643                 jsonObjectGetKeyConst(
644                     jsonObjectGetKeyConst(authEvt, "payload"),
645                     "authtoken"
646                 )
647             ),
648             workstation ? workstation : ""
649         );
650
651         jsonObjectFree(authEvt);
652
653     } else {
654         osrfLogError(OSRF_LOG_MARK, 
655             "Error caching auth session in open-ils.auth_internal");
656     }
657
658     return response;
659 }
660
661
662 /**
663         @brief Implement the "complete" method.
664         @param ctx The method context.
665         @return -1 upon error; zero if successful, and if a STATUS message has been sent to the
666         client to indicate completion; a positive integer if successful but no such STATUS
667         message has been sent.
668
669         Method parameters:
670         - a hash with some combination of the following elements:
671                 - "username"
672                 - "barcode"
673                 - "password" (hashed with the cached seed; not plaintext)
674                 - "type"
675                 - "org"
676                 - "workstation"
677                 - "agent" (what software/interface/3rd-party is making the request)
678                 - "nonce" optional login seed to differentiate logins using the same username.
679
680         The password is required.  Either a username or a barcode must also be present.
681
682         Return to client: Intermediate authentication seed.
683
684         Validate the password, using the username if available, or the barcode if not.  The
685         user must be active, and not barred from logging on.  The barcode, if used for
686         authentication, must be active as well.  The workstation, if specified, must be valid.
687
688         Upon deciding whether to allow the logon, return a corresponding event to the client.
689 */
690 int oilsAuthComplete( osrfMethodContext* ctx ) {
691     OSRF_METHOD_VERIFY_CONTEXT(ctx);
692
693     const jsonObject* args  = jsonObjectGetIndex(ctx->params, 0);
694
695     const char* uname       = jsonObjectGetString(jsonObjectGetKeyConst(args, "username"));
696     const char* identifier  = jsonObjectGetString(jsonObjectGetKeyConst(args, "identifier"));
697     const char* password    = jsonObjectGetString(jsonObjectGetKeyConst(args, "password"));
698     const char* type        = jsonObjectGetString(jsonObjectGetKeyConst(args, "type"));
699     int orgloc        = (int) jsonObjectGetNumber(jsonObjectGetKeyConst(args, "org"));
700     const char* workstation = jsonObjectGetString(jsonObjectGetKeyConst(args, "workstation"));
701     const char* barcode     = jsonObjectGetString(jsonObjectGetKeyConst(args, "barcode"));
702     const char* ewho        = jsonObjectGetString(jsonObjectGetKeyConst(args, "agent"));
703     const char* nonce       = jsonObjectGetString(jsonObjectGetKeyConst(args, "nonce"));
704
705     const char* ws = (workstation) ? workstation : "";
706     if (!nonce) nonce = "";
707
708     // we no longer care how the identifier reaches us, 
709     // as long as we have one.
710     if (!identifier) {
711         if (uname) {
712             identifier = uname;
713         } else if (barcode) {
714             identifier = barcode;
715         }
716     }
717
718     if (!identifier) {
719         return osrfAppRequestRespondException(ctx->session, ctx->request,
720             "username/barcode and password required for method: %s", 
721             ctx->method->name);
722     }
723
724     osrfLogInfo(OSRF_LOG_MARK, 
725         "Patron completing authentication with identifer %s", identifier);
726
727     /* Use __FILE__, harmless_line_number for creating
728      * OILS_EVENT_AUTH_FAILED events (instead of OSRF_LOG_MARK) to avoid
729      * giving away information about why an authentication attempt failed.
730      */
731     int harmless_line_number = __LINE__;
732
733     if( !type )
734          type = OILS_AUTH_STAFF;
735
736     oilsEvent* response = NULL; // free
737     jsonObject* userObj = NULL; // free
738
739     char* cache_key = va_list_to_string(
740         "%s%s%s", OILS_AUTH_CACHE_PRFX, identifier, nonce);
741     jsonObject* cacheObj = osrfCacheGetObject(cache_key); // free
742
743     if (!cacheObj) {
744         return osrfAppRequestRespondException(ctx->session,
745             ctx->request, "No authentication seed found. "
746             "open-ils.auth.authenticate.init must be called first "
747             " (check that memcached is running and can be connected to) "
748         );
749     }
750
751     int user_id = jsonObjectGetNumber(
752         jsonObjectGetKeyConst(cacheObj, "user_id"));
753
754     if (user_id == -1) {
755         // User was not found during init.  Clean up and exit early.
756         response = oilsNewEvent(
757             __FILE__, harmless_line_number, OILS_EVENT_AUTH_FAILED);
758         osrfAppRespondComplete(ctx, oilsEventToJSON(response));
759         oilsEventFree(response); // frees event JSON
760         osrfCacheRemove(cache_key);
761         jsonObjectFree(cacheObj);
762         return 0;
763     }
764
765     jsonObject* param = jsonNewNumberObject(user_id); // free
766     userObj = oilsUtilsCStoreReqCtx(
767         ctx, "open-ils.cstore.direct.actor.user.retrieve", param);
768     jsonObjectFree(param);
769
770     char* freeable_uname = NULL;
771     if (!uname) {
772         uname = freeable_uname = oilsFMGetString(userObj, "usrname");
773     }
774
775     // See if the user is allowed to login.
776
777     jsonObject* params = jsonNewObject(NULL);
778     jsonObjectSetKey(params, "user_id", 
779         jsonNewNumberObject(oilsFMGetObjectId(userObj)));
780     jsonObjectSetKey(params,"org_unit", jsonNewNumberObject(orgloc));
781     jsonObjectSetKey(params, "login_type", jsonNewObject(type));
782     if (barcode) jsonObjectSetKey(params, "barcode", jsonNewObject(barcode));
783
784     jsonObject* authEvt = oilsUtilsQuickReqCtx( // freed after password test
785         ctx,
786         "open-ils.auth_internal",
787         "open-ils.auth_internal.user.validate", params);
788     jsonObjectFree(params);
789
790     if (!authEvt) {
791         // Something went seriously wrong.  Get outta here before 
792         // we start segfaulting.
793         jsonObjectFree(userObj);
794         if(freeable_uname) free(freeable_uname);
795         return -1;
796     }
797
798     const char* authEvtCode = 
799         jsonObjectGetString(jsonObjectGetKey(authEvt, "textcode"));
800
801     if (!strcmp(authEvtCode, OILS_EVENT_AUTH_FAILED)) {
802         // Received the generic login failure event.
803
804         osrfLogInfo(OSRF_LOG_MARK,  
805             "failed login: username=%s, barcode=%s, workstation=%s",
806             uname, (barcode ? barcode : "(none)"), ws);
807
808         response = oilsNewEvent(
809             __FILE__, harmless_line_number, OILS_EVENT_AUTH_FAILED);
810     }
811
812     int passOK = 0;
813     
814     if (!response) {
815         // User exists and is not barred, etc.  Test the password.
816
817         passOK = oilsAuthVerifyPassword(
818             ctx, user_id, identifier, password, nonce);
819
820         if (!passOK) {
821             // Password check failed. Return generic login failure.
822
823             response = oilsNewEvent(
824                 __FILE__, harmless_line_number, OILS_EVENT_AUTH_FAILED);
825
826             osrfLogInfo(OSRF_LOG_MARK,  
827                 "failed login: username=%s, barcode=%s, workstation=%s",
828                     uname, (barcode ? barcode : "(none)"), ws );
829         }
830     }
831
832
833     // Below here, we know the password check succeeded if no response
834     // object is present.
835
836     if (!response && (
837         !strcmp(authEvtCode, "PATRON_INACTIVE") ||
838         !strcmp(authEvtCode, "PATRON_CARD_INACTIVE"))) {
839         // Patron and/or card is inactive but the correct password 
840         // was provided.  Alert the caller to the inactive-ness.
841         response = oilsNewEvent2(
842             OSRF_LOG_MARK, authEvtCode,
843             jsonObjectGetKey(authEvt, "payload")   // cloned within Event
844         );
845     }
846
847     if (!response && strcmp(authEvtCode, OILS_EVENT_SUCCESS)) {
848         // Validate API returned an unexpected non-success event.
849         // To be safe, treat this as a generic login failure.
850
851         response = oilsNewEvent(
852             __FILE__, harmless_line_number, OILS_EVENT_AUTH_FAILED);
853     }
854
855     if (!response) {
856         // password OK and no other events have prevented login completion.
857
858         char* ewhat = "login";
859
860         if (0 == strcmp(ctx->method->name, "open-ils.auth.authenticate.verify")) {
861             response = oilsNewEvent( OSRF_LOG_MARK, OILS_EVENT_SUCCESS );
862             ewhat = "verify";
863
864         } else {
865             response = oilsAuthHandleLoginOK(
866                 ctx, userObj, uname, type, orgloc, workstation);
867         }
868
869         oilsUtilsTrackUserActivity(
870             ctx,
871             oilsFMGetObjectId(userObj), 
872             ewho, ewhat, 
873             osrfAppSessionGetIngress()
874         );
875     }
876
877     // reply
878     osrfAppRespondComplete(ctx, oilsEventToJSON(response));
879
880     // clean up
881     oilsEventFree(response);
882     jsonObjectFree(userObj);
883     jsonObjectFree(authEvt);
884     jsonObjectFree(cacheObj);
885     if(freeable_uname)
886         free(freeable_uname);
887
888     return 0;
889 }
890
891
892 int oilsAuthLogin(osrfMethodContext* ctx) {
893     OSRF_METHOD_VERIFY_CONTEXT(ctx);
894
895     const jsonObject* args  = jsonObjectGetIndex(ctx->params, 0);
896
897     const char* username    = jsonObjectGetString(jsonObjectGetKeyConst(args, "username"));
898     const char* identifier  = jsonObjectGetString(jsonObjectGetKeyConst(args, "identifier"));
899     const char* password    = jsonObjectGetString(jsonObjectGetKeyConst(args, "password"));
900     const char* type        = jsonObjectGetString(jsonObjectGetKeyConst(args, "type"));
901     int orgloc        = (int) jsonObjectGetNumber(jsonObjectGetKeyConst(args, "org"));
902     const char* workstation = jsonObjectGetString(jsonObjectGetKeyConst(args, "workstation"));
903     const char* barcode     = jsonObjectGetString(jsonObjectGetKeyConst(args, "barcode"));
904     const char* ewho        = jsonObjectGetString(jsonObjectGetKeyConst(args, "agent"));
905
906     const char* ws = (workstation) ? workstation : "";
907     if (!type) type = OILS_AUTH_STAFF;
908
909     jsonObject* userObj = NULL; // free me
910     oilsEvent* response = NULL; // free me
911
912     /* Use __FILE__, harmless_line_number for creating
913      * OILS_EVENT_AUTH_FAILED events (instead of OSRF_LOG_MARK) to avoid
914      * giving away information about why an authentication attempt failed.
915      */
916     int harmless_line_number = __LINE__;
917
918     // translate a generic identifier into a username or barcode if necessary.
919     if (identifier && !username && !barcode) {
920         if (oilsAuthIdentIsBarcode(identifier, orgloc)) {
921             barcode = identifier;
922         } else {
923             username = identifier;
924         }
925     }
926
927     if (username) {
928         barcode = NULL; // avoid superfluous identifiers
929         userObj = oilsUtilsFetchUserByUsername(ctx, username);
930
931     } else if (barcode) {
932         userObj = oilsUtilsFetchUserByBarcode(ctx, barcode);
933
934     } else {
935         // not enough params
936         return osrfAppRequestRespondException(ctx->session, ctx->request,
937             "username/barcode and password required for method: %s", 
938             ctx->method->name);
939     }
940
941     if (!userObj) { // user not found.  
942         response = oilsNewEvent(
943             __FILE__, harmless_line_number, OILS_EVENT_AUTH_FAILED);
944         osrfAppRespondComplete(ctx, oilsEventToJSON(response));
945         oilsEventFree(response); // frees event JSON
946         return 0;
947     }
948
949     long user_id = oilsFMGetObjectId(userObj);
950
951     // username is freed when userObj is freed.
952     // From here we can use the username as the generic identifier
953     // since it's guaranteed to have a value.
954     if (!username) username = oilsFMGetStringConst(userObj, "usrname");
955
956     // See if the user is allowed to login.
957     jsonObject* params = jsonNewObject(NULL);
958     jsonObjectSetKey(params, "user_id", jsonNewNumberObject(user_id));
959     jsonObjectSetKey(params,"org_unit", jsonNewNumberObject(orgloc));
960     jsonObjectSetKey(params, "login_type", jsonNewObject(type));
961     if (barcode) jsonObjectSetKey(params, "barcode", jsonNewObject(barcode));
962
963     jsonObject* authEvt = oilsUtilsQuickReqCtx( // freed after password test
964         ctx,
965         "open-ils.auth_internal",
966         "open-ils.auth_internal.user.validate", params);
967     jsonObjectFree(params);
968
969     if (!authEvt) { // unknown error
970         jsonObjectFree(userObj);
971         return -1;
972     }
973
974     const char* authEvtCode = 
975         jsonObjectGetString(jsonObjectGetKey(authEvt, "textcode"));
976
977     if (!strcmp(authEvtCode, OILS_EVENT_AUTH_FAILED)) {
978         // Received the generic login failure event.
979
980         osrfLogInfo(OSRF_LOG_MARK,  
981             "failed login: username=%s, barcode=%s, workstation=%s",
982             username, (barcode ? barcode : "(none)"), ws);
983
984         response = oilsNewEvent(
985             __FILE__, harmless_line_number, OILS_EVENT_AUTH_FAILED);
986     }
987
988     if (!response && // user exists and is not barred, etc.
989         !oilsAuthLoginVerifyPassword(ctx, user_id, username, password)) {
990         // User provided the wrong password or is blocked from too 
991         // many previous login failures.
992
993         response = oilsNewEvent(
994             __FILE__, harmless_line_number, OILS_EVENT_AUTH_FAILED);
995
996         osrfLogInfo(OSRF_LOG_MARK,  
997             "failed login: username=%s, barcode=%s, workstation=%s",
998                 username, (barcode ? barcode : "(none)"), ws );
999     }
1000
1001     // Below here, we know the password check succeeded if no response
1002     // object is present.
1003
1004     if (!response && (
1005         !strcmp(authEvtCode, "PATRON_INACTIVE") ||
1006         !strcmp(authEvtCode, "PATRON_CARD_INACTIVE"))) {
1007         // Patron and/or card is inactive but the correct password 
1008         // was provided.  Alert the caller to the inactive-ness.
1009         response = oilsNewEvent2(
1010             OSRF_LOG_MARK, authEvtCode,
1011             jsonObjectGetKey(authEvt, "payload")   // cloned within Event
1012         );
1013     }
1014
1015     if (!response && strcmp(authEvtCode, OILS_EVENT_SUCCESS)) {
1016         // Validate API returned an unexpected non-success event.
1017         // To be safe, treat this as a generic login failure.
1018
1019         response = oilsNewEvent(
1020             __FILE__, harmless_line_number, OILS_EVENT_AUTH_FAILED);
1021     }
1022
1023     if (!response) {
1024         // password OK and no other events have prevented login completion.
1025
1026         char* ewhat = "login";
1027
1028         if (0 == strcmp(ctx->method->name, "open-ils.auth.authenticate.verify")) {
1029             response = oilsNewEvent( OSRF_LOG_MARK, OILS_EVENT_SUCCESS );
1030             ewhat = "verify";
1031
1032         } else {
1033             response = oilsAuthHandleLoginOK(
1034                 ctx, userObj, username, type, orgloc, workstation);
1035         }
1036
1037         oilsUtilsTrackUserActivity(
1038             ctx,
1039             oilsFMGetObjectId(userObj), 
1040             ewho, ewhat, 
1041             osrfAppSessionGetIngress()
1042         );
1043     }
1044
1045     // reply
1046     osrfAppRespondComplete(ctx, oilsEventToJSON(response));
1047
1048     // clean up
1049     oilsEventFree(response);
1050     jsonObjectFree(userObj);
1051     jsonObjectFree(authEvt);
1052
1053         return 0;
1054 }
1055
1056
1057
1058 int oilsAuthSessionDelete( osrfMethodContext* ctx ) {
1059         OSRF_METHOD_VERIFY_CONTEXT(ctx);
1060
1061         const char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0) );
1062         jsonObject* resp = NULL;
1063
1064         if( authToken ) {
1065                 osrfLogDebug(OSRF_LOG_MARK, "Removing auth session: %s", authToken );
1066                 char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken ); /**/
1067                 osrfCacheRemove(key);
1068                 resp = jsonNewObject(authToken); /**/
1069                 free(key);
1070         }
1071
1072         osrfAppRespondComplete( ctx, resp );
1073         jsonObjectFree(resp);
1074         return 0;
1075 }
1076
1077 /**
1078  * Fetches the user object from the database and updates the user object in 
1079  * the cache object, which then has to be re-inserted into the cache.
1080  * User object is retrieved inside a transaction to avoid replication issues.
1081  */
1082 static int _oilsAuthReloadUser(jsonObject* cacheObj) {
1083     int reqid, userId;
1084     osrfAppSession* session;
1085         osrfMessage* omsg;
1086     jsonObject *param, *userObj, *newUserObj = NULL;
1087
1088     userObj = jsonObjectGetKey( cacheObj, "userobj" );
1089     userId = oilsFMGetObjectId( userObj );
1090
1091     session = osrfAppSessionClientInit( "open-ils.cstore" );
1092     osrfAppSessionConnect(session);
1093
1094     reqid = osrfAppSessionSendRequest(session, NULL, "open-ils.cstore.transaction.begin", 1);
1095         omsg = osrfAppSessionRequestRecv(session, reqid, 60);
1096
1097     if(omsg) {
1098
1099         osrfMessageFree(omsg);
1100         param = jsonNewNumberObject(userId);
1101         reqid = osrfAppSessionSendRequest(session, param, "open-ils.cstore.direct.actor.user.retrieve", 1);
1102             omsg = osrfAppSessionRequestRecv(session, reqid, 60);
1103         jsonObjectFree(param);
1104
1105         if(omsg) {
1106             newUserObj = jsonObjectClone( osrfMessageGetResult(omsg) );
1107             osrfMessageFree(omsg);
1108             reqid = osrfAppSessionSendRequest(session, NULL, "open-ils.cstore.transaction.rollback", 1);
1109                 omsg = osrfAppSessionRequestRecv(session, reqid, 60);
1110             osrfMessageFree(omsg);
1111         }
1112     }
1113
1114     osrfAppSessionFree(session); // calls disconnect internally
1115
1116     if(newUserObj) {
1117
1118         // ws_ou and wsid are ephemeral and need to be manually propagated
1119         // oilsFMSetString dupe()'s internally, no need to clone the string
1120         oilsFMSetString(newUserObj, "wsid", oilsFMGetStringConst(userObj, "wsid"));
1121         oilsFMSetString(newUserObj, "ws_ou", oilsFMGetStringConst(userObj, "ws_ou"));
1122
1123         jsonObjectRemoveKey(cacheObj, "userobj"); // this also frees the old user object
1124         jsonObjectSetKey(cacheObj, "userobj", newUserObj);
1125         return 1;
1126     } 
1127
1128     osrfLogError(OSRF_LOG_MARK, "Error retrieving user %d from database", userId);
1129     return 0;
1130 }
1131
1132 /**
1133         Resets the auth login timeout
1134         @return The event object, OILS_EVENT_SUCCESS, or OILS_EVENT_NO_SESSION
1135 */
1136 static oilsEvent*  _oilsAuthResetTimeout( const char* authToken, int reloadUser ) {
1137         if(!authToken) return NULL;
1138
1139         oilsEvent* evt = NULL;
1140         time_t timeout;
1141
1142         osrfLogDebug(OSRF_LOG_MARK, "Resetting auth timeout for session %s", authToken);
1143         char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken );
1144         jsonObject* cacheObj = osrfCacheGetObject( key );
1145
1146         if(!cacheObj) {
1147                 osrfLogInfo(OSRF_LOG_MARK, "No user in the cache exists with key %s", key);
1148                 evt = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_NO_SESSION);
1149
1150         } else {
1151
1152         if(reloadUser) {
1153             _oilsAuthReloadUser(cacheObj);
1154         }
1155
1156                 // Determine a new timeout value
1157                 jsonObject* endtime_obj = jsonObjectGetKey( cacheObj, "endtime" );
1158                 if( endtime_obj ) {
1159                         // Extend the current endtime by a fixed amount
1160                         time_t endtime = (time_t) jsonObjectGetNumber( endtime_obj );
1161                         int reset_interval = DEFAULT_RESET_INTERVAL;
1162                         const jsonObject* reset_interval_obj = jsonObjectGetKeyConst(
1163                                 cacheObj, "reset_interval" );
1164                         if( reset_interval_obj ) {
1165                                 reset_interval = (int) jsonObjectGetNumber( reset_interval_obj );
1166                                 if( reset_interval <= 0 )
1167                                         reset_interval = DEFAULT_RESET_INTERVAL;
1168                         }
1169
1170                         time_t now = time( NULL );
1171                         time_t new_endtime = now + reset_interval;
1172                         if( new_endtime > endtime ) {
1173                                 // Keep the session alive a little longer
1174                                 jsonObjectSetNumber( endtime_obj, (double) new_endtime );
1175                                 timeout = reset_interval;
1176                                 osrfCachePutObject( key, cacheObj, timeout );
1177                         } else {
1178                                 // The session isn't close to expiring, so don't reset anything.
1179                                 // Just report the time remaining.
1180                                 timeout = endtime - now;
1181                         }
1182                 } else {
1183                         // Reapply the existing timeout from the current time
1184                         timeout = (time_t) jsonObjectGetNumber( jsonObjectGetKeyConst( cacheObj, "authtime"));
1185                         osrfCachePutObject( key, cacheObj, timeout );
1186                 }
1187
1188                 jsonObject* payload = jsonNewNumberObject( (double) timeout );
1189                 evt = oilsNewEvent2(OSRF_LOG_MARK, OILS_EVENT_SUCCESS, payload);
1190                 jsonObjectFree(payload);
1191                 jsonObjectFree(cacheObj);
1192         }
1193
1194         free(key);
1195         return evt;
1196 }
1197
1198 int oilsAuthResetTimeout( osrfMethodContext* ctx ) {
1199         OSRF_METHOD_VERIFY_CONTEXT(ctx);
1200         const char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0));
1201     double reloadUser = jsonObjectGetNumber( jsonObjectGetIndex(ctx->params, 1));
1202         oilsEvent* evt = _oilsAuthResetTimeout(authToken, (int) reloadUser);
1203         osrfAppRespondComplete( ctx, oilsEventToJSON(evt) );
1204         oilsEventFree(evt);
1205         return 0;
1206 }
1207
1208
1209 int oilsAuthSessionRetrieve( osrfMethodContext* ctx ) {
1210         OSRF_METHOD_VERIFY_CONTEXT(ctx);
1211     bool returnFull = false;
1212     bool noTimeoutReset = false;
1213
1214         const char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0));
1215
1216     if(ctx->params->size > 1) {
1217         // caller wants full cached object, with authtime, etc.
1218         const char* rt = jsonObjectGetString(jsonObjectGetIndex(ctx->params, 1));
1219         if(rt && strcmp(rt, "0") != 0) 
1220             returnFull = true;
1221
1222         if (ctx->params->size > 2) {
1223             // Avoid resetting the auth session timeout.
1224             const char* noReset = 
1225                 jsonObjectGetString(jsonObjectGetIndex(ctx->params, 2));
1226             if (noReset && strcmp(noReset, "0") != 0) 
1227                 noTimeoutReset = true;
1228         }
1229     }
1230
1231         jsonObject* cacheObj = NULL;
1232         oilsEvent* evt = NULL;
1233
1234         if( authToken ){
1235
1236                 // Reset the timeout to keep the session alive
1237         if (!noTimeoutReset) 
1238                     evt = _oilsAuthResetTimeout(authToken, 0);
1239
1240                 if( evt && strcmp(evt->event, OILS_EVENT_SUCCESS) ) {
1241                         osrfAppRespondComplete( ctx, oilsEventToJSON( evt ));    // can't reset timeout
1242
1243                 } else {
1244
1245                         // Retrieve the cached session object
1246                         osrfLogDebug(OSRF_LOG_MARK, "Retrieving auth session: %s", authToken);
1247                         char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken );
1248                         cacheObj = osrfCacheGetObject( key );
1249                         if(cacheObj) {
1250                                 // Return a copy of the cached user object
1251                 if(returnFull)
1252                                     osrfAppRespondComplete( ctx, cacheObj);
1253                 else
1254                                     osrfAppRespondComplete( ctx, jsonObjectGetKeyConst( cacheObj, "userobj"));
1255                                 jsonObjectFree(cacheObj);
1256                         } else {
1257                                 // Auth token is invalid or expired
1258                                 oilsEvent* evt2 = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_NO_SESSION);
1259                                 osrfAppRespondComplete( ctx, oilsEventToJSON(evt2) ); /* should be event.. */
1260                                 oilsEventFree(evt2);
1261                         }
1262                         free(key);
1263                 }
1264
1265         } else {
1266
1267                 // No session
1268                 evt = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_NO_SESSION);
1269                 osrfAppRespondComplete( ctx, oilsEventToJSON(evt) );
1270         }
1271
1272         if(evt)
1273                 oilsEventFree(evt);
1274
1275         return 0;
1276 }