LP1779158 Angular7 and ng-lint updates
[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     char* freeable_uname = NULL;
773     if (!uname) {
774         uname = freeable_uname = oilsFMGetString(userObj, "usrname");
775     }
776
777     // See if the user is allowed to login.
778
779     jsonObject* params = jsonNewObject(NULL);
780     jsonObjectSetKey(params, "user_id", 
781         jsonNewNumberObject(oilsFMGetObjectId(userObj)));
782     jsonObjectSetKey(params,"org_unit", jsonNewNumberObject(orgloc));
783     jsonObjectSetKey(params, "login_type", jsonNewObject(type));
784     if (barcode) jsonObjectSetKey(params, "barcode", jsonNewObject(barcode));
785
786     jsonObject* authEvt = oilsUtilsQuickReqCtx( // freed after password test
787         ctx,
788         "open-ils.auth_internal",
789         "open-ils.auth_internal.user.validate", params);
790     jsonObjectFree(params);
791
792     if (!authEvt) {
793         // Something went seriously wrong.  Get outta here before 
794         // we start segfaulting.
795         jsonObjectFree(userObj);
796         if(freeable_uname) free(freeable_uname);
797         return -1;
798     }
799
800     const char* authEvtCode = 
801         jsonObjectGetString(jsonObjectGetKey(authEvt, "textcode"));
802
803     if (!strcmp(authEvtCode, OILS_EVENT_AUTH_FAILED)) {
804         // Received the generic login failure event.
805
806         osrfLogInfo(OSRF_LOG_MARK,  
807             "failed login: username=%s, barcode=%s, workstation=%s",
808             uname, (barcode ? barcode : "(none)"), ws);
809
810         response = oilsNewEvent(
811             __FILE__, harmless_line_number, OILS_EVENT_AUTH_FAILED);
812     }
813
814     int passOK = 0;
815     
816     if (!response) {
817         // User exists and is not barred, etc.  Test the password.
818
819         passOK = oilsAuthVerifyPassword(
820             ctx, user_id, identifier, password, nonce);
821
822         if (!passOK) {
823             // Password check failed. Return generic login failure.
824
825             response = oilsNewEvent(
826                 __FILE__, harmless_line_number, OILS_EVENT_AUTH_FAILED);
827
828             osrfLogInfo(OSRF_LOG_MARK,  
829                 "failed login: username=%s, barcode=%s, workstation=%s",
830                     uname, (barcode ? barcode : "(none)"), ws );
831         }
832     }
833
834
835     // Below here, we know the password check succeeded if no response
836     // object is present.
837
838     if (!response && (
839         !strcmp(authEvtCode, "PATRON_INACTIVE") ||
840         !strcmp(authEvtCode, "PATRON_CARD_INACTIVE"))) {
841         // Patron and/or card is inactive but the correct password 
842         // was provided.  Alert the caller to the inactive-ness.
843         response = oilsNewEvent2(
844             OSRF_LOG_MARK, authEvtCode,
845             jsonObjectGetKey(authEvt, "payload")   // cloned within Event
846         );
847     }
848
849     if (!response && strcmp(authEvtCode, OILS_EVENT_SUCCESS)) {
850         // Validate API returned an unexpected non-success event.
851         // To be safe, treat this as a generic login failure.
852
853         response = oilsNewEvent(
854             __FILE__, harmless_line_number, OILS_EVENT_AUTH_FAILED);
855     }
856
857     if (!response) {
858         // password OK and no other events have prevented login completion.
859
860         char* ewhat = "login";
861
862         if (0 == strcmp(ctx->method->name, "open-ils.auth.authenticate.verify")) {
863             response = oilsNewEvent( OSRF_LOG_MARK, OILS_EVENT_SUCCESS );
864             ewhat = "verify";
865
866         } else {
867             response = oilsAuthHandleLoginOK(
868                 ctx, userObj, uname, type, orgloc, workstation);
869         }
870
871         oilsUtilsTrackUserActivity(
872             ctx,
873             oilsFMGetObjectId(userObj), 
874             ewho, ewhat, 
875             osrfAppSessionGetIngress()
876         );
877     }
878
879     // reply
880     osrfAppRespondComplete(ctx, oilsEventToJSON(response));
881
882     // clean up
883     oilsEventFree(response);
884     jsonObjectFree(userObj);
885     jsonObjectFree(authEvt);
886     jsonObjectFree(cacheObj);
887     if(freeable_uname)
888         free(freeable_uname);
889
890     return 0;
891 }
892
893
894 int oilsAuthLogin(osrfMethodContext* ctx) {
895     OSRF_METHOD_VERIFY_CONTEXT(ctx);
896
897     const jsonObject* args  = jsonObjectGetIndex(ctx->params, 0);
898
899     const char* username    = jsonObjectGetString(jsonObjectGetKeyConst(args, "username"));
900     const char* identifier  = jsonObjectGetString(jsonObjectGetKeyConst(args, "identifier"));
901     const char* password    = jsonObjectGetString(jsonObjectGetKeyConst(args, "password"));
902     const char* type        = jsonObjectGetString(jsonObjectGetKeyConst(args, "type"));
903     int orgloc        = (int) jsonObjectGetNumber(jsonObjectGetKeyConst(args, "org"));
904     const char* workstation = jsonObjectGetString(jsonObjectGetKeyConst(args, "workstation"));
905     const char* barcode     = jsonObjectGetString(jsonObjectGetKeyConst(args, "barcode"));
906     const char* ewho        = jsonObjectGetString(jsonObjectGetKeyConst(args, "agent"));
907
908     const char* ws = (workstation) ? workstation : "";
909     if (!type) type = OILS_AUTH_STAFF;
910
911     jsonObject* userObj = NULL; // free me
912     oilsEvent* response = NULL; // free me
913
914     /* Use __FILE__, harmless_line_number for creating
915      * OILS_EVENT_AUTH_FAILED events (instead of OSRF_LOG_MARK) to avoid
916      * giving away information about why an authentication attempt failed.
917      */
918     int harmless_line_number = __LINE__;
919
920     // translate a generic identifier into a username or barcode if necessary.
921     if (identifier && !username && !barcode) {
922         if (oilsAuthIdentIsBarcode(identifier, orgloc)) {
923             barcode = identifier;
924         } else {
925             username = identifier;
926         }
927     }
928
929     if (username) {
930         barcode = NULL; // avoid superfluous identifiers
931         userObj = oilsUtilsFetchUserByUsername(ctx, username);
932
933     } else if (barcode) {
934         userObj = oilsUtilsFetchUserByBarcode(ctx, barcode);
935
936     } else {
937         // not enough params
938         return osrfAppRequestRespondException(ctx->session, ctx->request,
939             "username/barcode and password required for method: %s", 
940             ctx->method->name);
941     }
942
943     if (!userObj) { // user not found.  
944         response = oilsNewEvent(
945             __FILE__, harmless_line_number, OILS_EVENT_AUTH_FAILED);
946         osrfAppRespondComplete(ctx, oilsEventToJSON(response));
947         oilsEventFree(response); // frees event JSON
948         return 0;
949     }
950
951     long user_id = oilsFMGetObjectId(userObj);
952
953     // username is freed when userObj is freed.
954     // From here we can use the username as the generic identifier
955     // since it's guaranteed to have a value.
956     if (!username) username = oilsFMGetStringConst(userObj, "usrname");
957
958     // See if the user is allowed to login.
959     jsonObject* params = jsonNewObject(NULL);
960     jsonObjectSetKey(params, "user_id", jsonNewNumberObject(user_id));
961     jsonObjectSetKey(params,"org_unit", jsonNewNumberObject(orgloc));
962     jsonObjectSetKey(params, "login_type", jsonNewObject(type));
963     if (barcode) jsonObjectSetKey(params, "barcode", jsonNewObject(barcode));
964
965     jsonObject* authEvt = oilsUtilsQuickReqCtx( // freed after password test
966         ctx,
967         "open-ils.auth_internal",
968         "open-ils.auth_internal.user.validate", params);
969     jsonObjectFree(params);
970
971     if (!authEvt) { // unknown error
972         jsonObjectFree(userObj);
973         return -1;
974     }
975
976     const char* authEvtCode = 
977         jsonObjectGetString(jsonObjectGetKey(authEvt, "textcode"));
978
979     if (!strcmp(authEvtCode, OILS_EVENT_AUTH_FAILED)) {
980         // Received the generic login failure event.
981
982         osrfLogInfo(OSRF_LOG_MARK,  
983             "failed login: username=%s, barcode=%s, workstation=%s",
984             username, (barcode ? barcode : "(none)"), ws);
985
986         response = oilsNewEvent(
987             __FILE__, harmless_line_number, OILS_EVENT_AUTH_FAILED);
988     }
989
990     if (!response && // user exists and is not barred, etc.
991         !oilsAuthLoginVerifyPassword(ctx, user_id, username, password)) {
992         // User provided the wrong password or is blocked from too 
993         // many previous login failures.
994
995         response = oilsNewEvent(
996             __FILE__, harmless_line_number, OILS_EVENT_AUTH_FAILED);
997
998         osrfLogInfo(OSRF_LOG_MARK,  
999             "failed login: username=%s, barcode=%s, workstation=%s",
1000                 username, (barcode ? barcode : "(none)"), ws );
1001     }
1002
1003     // Below here, we know the password check succeeded if no response
1004     // object is present.
1005
1006     if (!response && (
1007         !strcmp(authEvtCode, "PATRON_INACTIVE") ||
1008         !strcmp(authEvtCode, "PATRON_CARD_INACTIVE"))) {
1009         // Patron and/or card is inactive but the correct password 
1010         // was provided.  Alert the caller to the inactive-ness.
1011         response = oilsNewEvent2(
1012             OSRF_LOG_MARK, authEvtCode,
1013             jsonObjectGetKey(authEvt, "payload")   // cloned within Event
1014         );
1015     }
1016
1017     if (!response && strcmp(authEvtCode, OILS_EVENT_SUCCESS)) {
1018         // Validate API returned an unexpected non-success event.
1019         // To be safe, treat this as a generic login failure.
1020
1021         response = oilsNewEvent(
1022             __FILE__, harmless_line_number, OILS_EVENT_AUTH_FAILED);
1023     }
1024
1025     if (!response) {
1026         // password OK and no other events have prevented login completion.
1027
1028         char* ewhat = "login";
1029
1030         if (0 == strcmp(ctx->method->name, "open-ils.auth.authenticate.verify")) {
1031             response = oilsNewEvent( OSRF_LOG_MARK, OILS_EVENT_SUCCESS );
1032             ewhat = "verify";
1033
1034         } else {
1035             response = oilsAuthHandleLoginOK(
1036                 ctx, userObj, username, type, orgloc, workstation);
1037         }
1038
1039         oilsUtilsTrackUserActivity(
1040             ctx,
1041             oilsFMGetObjectId(userObj), 
1042             ewho, ewhat, 
1043             osrfAppSessionGetIngress()
1044         );
1045     }
1046
1047     // reply
1048     osrfAppRespondComplete(ctx, oilsEventToJSON(response));
1049
1050     // clean up
1051     oilsEventFree(response);
1052     jsonObjectFree(userObj);
1053     jsonObjectFree(authEvt);
1054
1055         return 0;
1056 }
1057
1058
1059
1060 int oilsAuthSessionDelete( osrfMethodContext* ctx ) {
1061         OSRF_METHOD_VERIFY_CONTEXT(ctx);
1062
1063         const char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0) );
1064         jsonObject* resp = NULL;
1065
1066         if( authToken ) {
1067                 osrfLogDebug(OSRF_LOG_MARK, "Removing auth session: %s", authToken );
1068                 char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken ); /**/
1069                 osrfCacheRemove(key);
1070                 resp = jsonNewObject(authToken); /**/
1071                 free(key);
1072         }
1073
1074         osrfAppRespondComplete( ctx, resp );
1075         jsonObjectFree(resp);
1076         return 0;
1077 }
1078
1079 /**
1080  * Fetches the user object from the database and updates the user object in 
1081  * the cache object, which then has to be re-inserted into the cache.
1082  * User object is retrieved inside a transaction to avoid replication issues.
1083  */
1084 static int _oilsAuthReloadUser(jsonObject* cacheObj) {
1085     int reqid, userId;
1086     osrfAppSession* session;
1087         osrfMessage* omsg;
1088     jsonObject *param, *userObj, *newUserObj = NULL;
1089
1090     userObj = jsonObjectGetKey( cacheObj, "userobj" );
1091     userId = oilsFMGetObjectId( userObj );
1092
1093     session = osrfAppSessionClientInit( "open-ils.cstore" );
1094     osrfAppSessionConnect(session);
1095
1096     reqid = osrfAppSessionSendRequest(session, NULL, "open-ils.cstore.transaction.begin", 1);
1097         omsg = osrfAppSessionRequestRecv(session, reqid, 60);
1098
1099     if(omsg) {
1100
1101         osrfMessageFree(omsg);
1102         param = jsonNewNumberObject(userId);
1103         reqid = osrfAppSessionSendRequest(session, param, "open-ils.cstore.direct.actor.user.retrieve", 1);
1104             omsg = osrfAppSessionRequestRecv(session, reqid, 60);
1105         jsonObjectFree(param);
1106
1107         if(omsg) {
1108             newUserObj = jsonObjectClone( osrfMessageGetResult(omsg) );
1109             osrfMessageFree(omsg);
1110             reqid = osrfAppSessionSendRequest(session, NULL, "open-ils.cstore.transaction.rollback", 1);
1111                 omsg = osrfAppSessionRequestRecv(session, reqid, 60);
1112             osrfMessageFree(omsg);
1113         }
1114     }
1115
1116     osrfAppSessionFree(session); // calls disconnect internally
1117
1118     if(newUserObj) {
1119
1120         // ws_ou and wsid are ephemeral and need to be manually propagated
1121         // oilsFMSetString dupe()'s internally, no need to clone the string
1122         oilsFMSetString(newUserObj, "wsid", oilsFMGetStringConst(userObj, "wsid"));
1123         oilsFMSetString(newUserObj, "ws_ou", oilsFMGetStringConst(userObj, "ws_ou"));
1124
1125         jsonObjectRemoveKey(cacheObj, "userobj"); // this also frees the old user object
1126         jsonObjectSetKey(cacheObj, "userobj", newUserObj);
1127         return 1;
1128     } 
1129
1130     osrfLogError(OSRF_LOG_MARK, "Error retrieving user %d from database", userId);
1131     return 0;
1132 }
1133
1134 /**
1135         Resets the auth login timeout
1136         @return The event object, OILS_EVENT_SUCCESS, or OILS_EVENT_NO_SESSION
1137 */
1138 static oilsEvent*  _oilsAuthResetTimeout( const char* authToken, int reloadUser ) {
1139         if(!authToken) return NULL;
1140
1141         oilsEvent* evt = NULL;
1142         time_t timeout;
1143
1144         osrfLogDebug(OSRF_LOG_MARK, "Resetting auth timeout for session %s", authToken);
1145         char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken );
1146         jsonObject* cacheObj = osrfCacheGetObject( key );
1147
1148         if(!cacheObj) {
1149                 osrfLogInfo(OSRF_LOG_MARK, "No user in the cache exists with key %s", key);
1150                 evt = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_NO_SESSION);
1151
1152         } else {
1153
1154         if(reloadUser) {
1155             _oilsAuthReloadUser(cacheObj);
1156         }
1157
1158                 // Determine a new timeout value
1159                 jsonObject* endtime_obj = jsonObjectGetKey( cacheObj, "endtime" );
1160                 if( endtime_obj ) {
1161                         // Extend the current endtime by a fixed amount
1162                         time_t endtime = (time_t) jsonObjectGetNumber( endtime_obj );
1163                         int reset_interval = DEFAULT_RESET_INTERVAL;
1164                         const jsonObject* reset_interval_obj = jsonObjectGetKeyConst(
1165                                 cacheObj, "reset_interval" );
1166                         if( reset_interval_obj ) {
1167                                 reset_interval = (int) jsonObjectGetNumber( reset_interval_obj );
1168                                 if( reset_interval <= 0 )
1169                                         reset_interval = DEFAULT_RESET_INTERVAL;
1170                         }
1171
1172                         time_t now = time( NULL );
1173                         time_t new_endtime = now + reset_interval;
1174                         if( new_endtime > endtime ) {
1175                                 // Keep the session alive a little longer
1176                                 jsonObjectSetNumber( endtime_obj, (double) new_endtime );
1177                                 timeout = reset_interval;
1178                                 osrfCachePutObject( key, cacheObj, timeout );
1179                         } else {
1180                                 // The session isn't close to expiring, so don't reset anything.
1181                                 // Just report the time remaining.
1182                                 timeout = endtime - now;
1183                         }
1184                 } else {
1185                         // Reapply the existing timeout from the current time
1186                         timeout = (time_t) jsonObjectGetNumber( jsonObjectGetKeyConst( cacheObj, "authtime"));
1187                         osrfCachePutObject( key, cacheObj, timeout );
1188                 }
1189
1190                 jsonObject* payload = jsonNewNumberObject( (double) timeout );
1191                 evt = oilsNewEvent2(OSRF_LOG_MARK, OILS_EVENT_SUCCESS, payload);
1192                 jsonObjectFree(payload);
1193                 jsonObjectFree(cacheObj);
1194         }
1195
1196         free(key);
1197         return evt;
1198 }
1199
1200 int oilsAuthResetTimeout( osrfMethodContext* ctx ) {
1201         OSRF_METHOD_VERIFY_CONTEXT(ctx);
1202         const char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0));
1203     double reloadUser = jsonObjectGetNumber( jsonObjectGetIndex(ctx->params, 1));
1204         oilsEvent* evt = _oilsAuthResetTimeout(authToken, (int) reloadUser);
1205         osrfAppRespondComplete( ctx, oilsEventToJSON(evt) );
1206         oilsEventFree(evt);
1207         return 0;
1208 }
1209
1210
1211 int oilsAuthSessionRetrieve( osrfMethodContext* ctx ) {
1212         OSRF_METHOD_VERIFY_CONTEXT(ctx);
1213     bool returnFull = false;
1214     bool noTimeoutReset = false;
1215
1216         const char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0));
1217
1218     if(ctx->params->size > 1) {
1219         // caller wants full cached object, with authtime, etc.
1220         const char* rt = jsonObjectGetString(jsonObjectGetIndex(ctx->params, 1));
1221         if(rt && strcmp(rt, "0") != 0) 
1222             returnFull = true;
1223
1224         if (ctx->params->size > 2) {
1225             // Avoid resetting the auth session timeout.
1226             const char* noReset = 
1227                 jsonObjectGetString(jsonObjectGetIndex(ctx->params, 2));
1228             if (noReset && strcmp(noReset, "0") != 0) 
1229                 noTimeoutReset = true;
1230         }
1231     }
1232
1233         jsonObject* cacheObj = NULL;
1234         oilsEvent* evt = NULL;
1235
1236         if( authToken ){
1237
1238                 // Reset the timeout to keep the session alive
1239         if (!noTimeoutReset) 
1240                     evt = _oilsAuthResetTimeout(authToken, 0);
1241
1242                 if( evt && strcmp(evt->event, OILS_EVENT_SUCCESS) ) {
1243                         osrfAppRespondComplete( ctx, oilsEventToJSON( evt ));    // can't reset timeout
1244
1245                 } else {
1246
1247                         // Retrieve the cached session object
1248                         osrfLogDebug(OSRF_LOG_MARK, "Retrieving auth session: %s", authToken);
1249                         char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken );
1250                         cacheObj = osrfCacheGetObject( key );
1251                         if(cacheObj) {
1252                                 // Return a copy of the cached user object
1253                 if(returnFull)
1254                                     osrfAppRespondComplete( ctx, cacheObj);
1255                 else
1256                                     osrfAppRespondComplete( ctx, jsonObjectGetKeyConst( cacheObj, "userobj"));
1257                                 jsonObjectFree(cacheObj);
1258                         } else {
1259                                 // Auth token is invalid or expired
1260                                 oilsEvent* evt2 = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_NO_SESSION);
1261                                 osrfAppRespondComplete( ctx, oilsEventToJSON(evt2) ); /* should be event.. */
1262                                 oilsEventFree(evt2);
1263                         }
1264                         free(key);
1265                 }
1266
1267         } else {
1268
1269                 // No session
1270                 evt = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_NO_SESSION);
1271                 osrfAppRespondComplete( ctx, oilsEventToJSON(evt) );
1272         }
1273
1274         if(evt)
1275                 oilsEventFree(evt);
1276
1277         return 0;
1278 }