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