2c572a6f0b9e185f6a33e8547f03f0980f0a5a1d
[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
10 #define OILS_AUTH_CACHE_PRFX "oils_auth_"
11
12 #define MODULENAME "open-ils.auth"
13
14 #define OILS_AUTH_OPAC "opac"
15 #define OILS_AUTH_STAFF "staff"
16 #define OILS_AUTH_TEMP "temp"
17 #define OILS_AUTH_PERSISTENT "persistent"
18
19 // Default time for extending a persistent session: ten minutes
20 #define DEFAULT_RESET_INTERVAL 10 * 60
21
22 int osrfAppInitialize();
23 int osrfAppChildInit();
24
25 static long _oilsAuthOPACTimeout = 0;
26 static long _oilsAuthStaffTimeout = 0;
27 static long _oilsAuthOverrideTimeout = 0;
28
29
30 /**
31         @brief Initialize the application by registering functions for method calls.
32         @return Zero in all cases.
33 */
34 int osrfAppInitialize() {
35
36         osrfLogInfo(OSRF_LOG_MARK, "Initializing Auth Server...");
37
38         /* load and parse the IDL */
39         if (!oilsInitIDL(NULL)) return 1; /* return non-zero to indicate error */
40
41         osrfAppRegisterMethod(
42                 MODULENAME,
43                 "open-ils.auth.authenticate.init",
44                 "oilsAuthInit",
45                 "Start the authentication process and returns the intermediate authentication seed"
46                 " PARAMS( username )", 1, 0 );
47
48         osrfAppRegisterMethod(
49                 MODULENAME,
50                 "open-ils.auth.authenticate.complete",
51                 "oilsAuthComplete",
52                 "Completes the authentication process.  Returns an object like so: "
53                 "{authtoken : <token>, authtime:<time>}, where authtoken is the login "
54                 "token and authtime is the number of seconds the session will be active"
55                 "PARAMS(username, md5sum( seed + md5sum( password ) ), type, org_id ) "
56                 "type can be one of 'opac','staff', or 'temp' and it defaults to 'staff' "
57                 "org_id is the location at which the login should be considered "
58                 "active for login timeout purposes", 1, 0 );
59
60         osrfAppRegisterMethod(
61                 MODULENAME,
62                 "open-ils.auth.session.retrieve",
63                 "oilsAuthSessionRetrieve",
64                 "Pass in the auth token and this retrieves the user object.  The auth "
65                 "timeout is reset when this call is made "
66                 "Returns the user object (password blanked) for the given login session "
67                 "PARAMS( authToken )", 1, 0 );
68
69         osrfAppRegisterMethod(
70                 MODULENAME,
71                 "open-ils.auth.session.delete",
72                 "oilsAuthSessionDelete",
73                 "Destroys the given login session "
74                 "PARAMS( authToken )",  1, 0 );
75
76         osrfAppRegisterMethod(
77                 MODULENAME,
78                 "open-ils.auth.session.reset_timeout",
79                 "oilsAuthResetTimeout",
80                 "Resets the login timeout for the given session "
81                 "Returns an ILS Event with payload = session_timeout of session "
82                 "if found, otherwise returns the NO_SESSION event"
83                 "PARAMS( authToken )", 1, 0 );
84
85         return 0;
86 }
87
88 /**
89         @brief Dummy placeholder for initializing a server drone.
90
91         There is nothing to do, so do nothing.
92 */
93 int osrfAppChildInit() {
94         return 0;
95 }
96
97 /**
98         @brief Implement the "init" method.
99         @param ctx The method context.
100         @return Zero if successful, or -1 if not.
101
102         Method parameters:
103         - username
104
105         Return to client: Intermediate authentication seed.
106
107         Combine the username with a timestamp and process ID, and take an md5 hash of the result.
108         Store the hash in memcache, with a key based on the username.  Then return the hash to
109         the client.
110
111         However: if the username includes one or more embedded blank spaces, return a dummy
112         hash without storing anything in memcache.  The dummy will never match a stored hash, so
113         any attempt to authenticate with it will fail.
114 */
115 int oilsAuthInit( osrfMethodContext* ctx ) {
116         OSRF_METHOD_VERIFY_CONTEXT(ctx);
117
118         char* username  = jsonObjectToSimpleString( jsonObjectGetIndex(ctx->params, 0) );
119         if( username ) {
120
121                 jsonObject* resp;
122
123                 if( strchr( username, ' ' ) ) {
124
125                         // Embedded spaces are not allowed in a username.  Use "x" as a dummy
126                         // seed.  It will never be a valid seed because 'x' is not a hex digit.
127                         resp = jsonNewObject( "x" );
128
129                 } else {
130
131                         // Build a key and a seed; store them in memcache.
132                         char* key  = va_list_to_string( "%s%s", OILS_AUTH_CACHE_PRFX, username );
133                         char* seed = md5sum( "%d.%ld.%s", (int) time(NULL), (long) getpid(), username );
134                         osrfCachePutString( key, seed, 30 );
135
136                         osrfLogDebug( OSRF_LOG_MARK, "oilsAuthInit(): has seed %s and key %s", seed, key );
137
138                         // Build a returnable object containing the seed.
139                         resp = jsonNewObject( seed );
140
141                         free( seed );
142                         free( key );
143                 }
144
145                 // Return the seed to the client.
146                 osrfAppRespondComplete( ctx, resp );
147
148                 jsonObjectFree(resp);
149                 free(username);
150                 return 0;
151         }
152
153         return -1;  // Error: no username parameter
154 }
155
156 /**
157         Verifies that the user has permission to login with the
158         given type.  If the permission fails, an oilsEvent is returned
159         to the caller.
160         @return -1 if the permission check failed, 0 if the permission
161         is granted
162 */
163 static int oilsAuthCheckLoginPerm(
164                 osrfMethodContext* ctx, const jsonObject* userObj, const char* type ) {
165
166         if(!(userObj && type)) return -1;
167         oilsEvent* perm = NULL;
168
169         if(!strcasecmp(type, OILS_AUTH_OPAC)) {
170                 char* permissions[] = { "OPAC_LOGIN" };
171                 perm = oilsUtilsCheckPerms( oilsFMGetObjectId( userObj ), -1, permissions, 1 );
172
173         } else if(!strcasecmp(type, OILS_AUTH_STAFF)) {
174                 char* permissions[] = { "STAFF_LOGIN" };
175                 perm = oilsUtilsCheckPerms( oilsFMGetObjectId( userObj ), -1, permissions, 1 );
176
177         } else if(!strcasecmp(type, OILS_AUTH_TEMP)) {
178                 char* permissions[] = { "STAFF_LOGIN" };
179                 perm = oilsUtilsCheckPerms( oilsFMGetObjectId( userObj ), -1, permissions, 1 );
180         }
181
182         if(perm) {
183                 osrfAppRespondComplete( ctx, oilsEventToJSON(perm) );
184                 oilsEventFree(perm);
185                 return -1;
186         }
187
188         return 0;
189 }
190
191 /**
192         Returns 1 if the password provided matches the user's real password
193         Returns 0 otherwise
194         Returns -1 on error
195 */
196 /**
197         @brief Verify the password received from the client.
198         @param ctx The method context.
199         @param userObj An object from the database, representing the user.
200         @param password An obfuscated password received from the client.
201         @return 1 if the password is valid; 0 if it isn't; or -1 upon error.
202
203         (None of the so-called "passwords" used here are in plaintext.  All have been passed
204         through at least one layer of hashing to obfuscate them.)
205
206         Take the password from the user object.  Append it to the username seed from memcache,
207         as stored previously by a call to the init method.  Take an md5 hash of the result.
208         Then compare this hash to the password received from the client.
209
210         In order for the two to match, other than by dumb luck, the client had to construct
211         the password it passed in the same way.  That means it neded to know not only the
212         original password (either hashed or plaintext), but also the seed.  The latter requirement
213         means that the client process needs either to be the same process that called the init
214         method or to receive the seed from the process that did so.
215 */
216 static int oilsAuthVerifyPassword( const osrfMethodContext* ctx,
217                 const jsonObject* userObj, const char* uname, const char* password ) {
218
219         // Get the username seed, as stored previously in memcache by the init method
220         char* seed = osrfCacheGetString( "%s%s", OILS_AUTH_CACHE_PRFX, uname );
221         if(!seed) {
222                 return osrfAppRequestRespondException( ctx->session,
223                         ctx->request, "No authentication seed found. "
224                         "open-ils.auth.authenticate.init must be called first");
225         }
226
227         // Get the hashed password from the user object
228         char* realPassword = oilsFMGetString( userObj, "passwd" );
229
230         osrfLogInternal(OSRF_LOG_MARK, "oilsAuth retrieved real password: [%s]", realPassword);
231         osrfLogDebug(OSRF_LOG_MARK, "oilsAuth retrieved seed from cache: %s", seed );
232
233         // Concatenate them and take an MD5 hash of the result
234         char* maskedPw = md5sum( "%s%s", seed, realPassword );
235
236         free(realPassword);
237         free(seed);
238
239         if( !maskedPw ) {
240                 // This happens only if md5sum() runs out of memory
241                 free( maskedPw );
242                 return -1;  // md5sum() ran out of memory
243         }
244
245         osrfLogDebug(OSRF_LOG_MARK,  "oilsAuth generated masked password %s. "
246                         "Testing against provided password %s", maskedPw, password );
247
248         int ret = 0;
249         if( !strcmp( maskedPw, password ) )
250                 ret = 1;
251
252         free(maskedPw);
253
254         return ret;
255 }
256
257 /**
258         Calculates the login timeout
259         1. If orgloc is 1 or greater and has a timeout specified as an
260         org unit setting, it is used
261         2. If orgloc is not valid, we check the org unit auth timeout
262         setting for the home org unit of the user logging in
263         3. If that setting is not defined, we use the configured defaults
264 */
265 static long oilsAuthGetTimeout( const jsonObject* userObj, const char* type, int orgloc ) {
266
267         if(!_oilsAuthOPACTimeout) { /* Load the default timeouts */
268
269                 jsonObject* value_obj;
270
271                 value_obj = osrf_settings_host_value_object(
272                         "/apps/open-ils.auth/app_settings/default_timeout/opac" );
273                 _oilsAuthOPACTimeout = (long) jsonObjectGetNumber(value_obj);
274                 jsonObjectFree(value_obj);
275
276                 value_obj = osrf_settings_host_value_object(
277                         "/apps/open-ils.auth/app_settings/default_timeout/staff" );
278                 _oilsAuthStaffTimeout = (long) jsonObjectGetNumber(value_obj);
279                 jsonObjectFree(value_obj);
280
281                 value_obj = osrf_settings_host_value_object(
282                                 "/apps/open-ils.auth/app_settings/default_timeout/temp" );
283                 _oilsAuthOverrideTimeout = (long) jsonObjectGetNumber(value_obj);
284                 jsonObjectFree(value_obj);
285
286
287                 osrfLogInfo(OSRF_LOG_MARK,
288                                 "Set default auth timeouts: opac => %ld : staff => %ld : temp => %ld",
289                                 _oilsAuthOPACTimeout, _oilsAuthStaffTimeout, _oilsAuthOverrideTimeout );
290         }
291
292         char* setting = NULL;
293
294         int home_ou = (int) jsonObjectGetNumber( oilsFMGetObject( userObj, "home_ou" ));
295         if(orgloc < 1)
296                 orgloc = home_ou;
297
298         if(!strcmp(type, OILS_AUTH_OPAC))
299                 setting = OILS_ORG_SETTING_OPAC_TIMEOUT;
300         else if(!strcmp(type, OILS_AUTH_STAFF))
301                 setting = OILS_ORG_SETTING_STAFF_TIMEOUT;
302         else if(!strcmp(type, OILS_AUTH_TEMP))
303                 setting = OILS_ORG_SETTING_TEMP_TIMEOUT;
304
305         char* timeout = oilsUtilsFetchOrgSetting( orgloc, setting );
306
307         if(!timeout) {
308                 if( orgloc != home_ou ) {
309                         osrfLogDebug(OSRF_LOG_MARK, "Auth timeout not defined for org %d, "
310                                                                 "trying home_ou %d", orgloc, home_ou );
311                         timeout = oilsUtilsFetchOrgSetting( home_ou, setting );
312                 }
313                 if(!timeout) {
314                         if( !strcmp(type, OILS_AUTH_STAFF ))
315                                 return _oilsAuthStaffTimeout;
316                         else if( !strcmp( type, OILS_AUTH_TEMP ))
317                                 return _oilsAuthOverrideTimeout;
318                         else
319                                 return _oilsAuthOPACTimeout;
320                 }
321         }
322
323         long t = (long) atof(timeout);
324         free(timeout);
325         return t ;
326 }
327
328 /*
329         Adds the authentication token to the user cache.  The timeout for the
330         auth token is based on the type of login as well as (if type=='opac')
331         the org location id.
332         Returns the event that should be returned to the user.
333         Event must be freed
334 */
335 static oilsEvent* oilsAuthHandleLoginOK( jsonObject* userObj, const char* uname,
336                 const char* type, int orgloc, const char* workstation ) {
337
338         oilsEvent* response;
339
340         long timeout;
341         char* wsorg = jsonObjectToSimpleString(oilsFMGetObject(userObj, "ws_ou"));
342         if(wsorg) { /* if there is a workstation, use it for the timeout */
343                 osrfLogDebug( OSRF_LOG_MARK,
344                                 "Auth session trying workstation id %d for auth timeout", atoi(wsorg));
345                 timeout = oilsAuthGetTimeout( userObj, type, atoi(wsorg) );
346                 free(wsorg);
347         } else {
348                 osrfLogDebug( OSRF_LOG_MARK,
349                                 "Auth session trying org from param [%d] for auth timeout", orgloc );
350                 timeout = oilsAuthGetTimeout( userObj, type, orgloc );
351         }
352         osrfLogDebug(OSRF_LOG_MARK, "Auth session timeout for %s: %ld", uname, timeout );
353
354         char* string = va_list_to_string(
355                         "%d.%ld.%s", (long) getpid(), time(NULL), uname );
356         char* authToken = md5sum(string);
357         char* authKey = va_list_to_string(
358                         "%s%s", OILS_AUTH_CACHE_PRFX, authToken );
359
360         const char* ws = (workstation) ? workstation : "";
361         osrfLogActivity(OSRF_LOG_MARK,
362                 "successful login: username=%s, authtoken=%s, workstation=%s", uname, authToken, ws );
363
364         oilsFMSetString( userObj, "passwd", "" );
365         jsonObject* cacheObj = jsonParseFmt( "{\"authtime\": %ld}", timeout );
366         jsonObjectSetKey( cacheObj, "userobj", jsonObjectClone(userObj));
367
368         osrfCachePutObject( authKey, cacheObj, (time_t) timeout );
369         jsonObjectFree(cacheObj);
370         osrfLogInternal(OSRF_LOG_MARK, "oilsAuthHandleLoginOK(): Placed user object into cache");
371         jsonObject* payload = jsonParseFmt(
372                 "{ \"authtoken\": \"%s\", \"authtime\": %ld }", authToken, timeout );
373
374         response = oilsNewEvent2( OSRF_LOG_MARK, OILS_EVENT_SUCCESS, payload );
375         free(string); free(authToken); free(authKey);
376         jsonObjectFree(payload);
377
378         return response;
379 }
380
381 static oilsEvent* oilsAuthVerifyWorkstation(
382                 const osrfMethodContext* ctx, jsonObject* userObj, const char* ws ) {
383         osrfLogInfo(OSRF_LOG_MARK, "Attaching workstation to user at login: %s", ws);
384         jsonObject* workstation = oilsUtilsFetchWorkstationByName(ws);
385         if(!workstation || workstation->type == JSON_NULL) {
386                 jsonObjectFree(workstation);
387                 return oilsNewEvent(OSRF_LOG_MARK, "WORKSTATION_NOT_FOUND");
388         }
389         long wsid = oilsFMGetObjectId(workstation);
390         LONG_TO_STRING(wsid);
391         char* orgid = oilsFMGetString(workstation, "owning_lib");
392         oilsFMSetString(userObj, "wsid", LONGSTR);
393         oilsFMSetString(userObj, "ws_ou", orgid);
394         free(orgid);
395         jsonObjectFree(workstation);
396         return NULL;
397 }
398
399
400
401 /**
402         @brief Implement the "complete" method.
403         @param ctx The method context.
404         @return -1 upon error; zero if successful, and if a STATUS message has been sent to the
405         client to indicate completion; a positive integer if successful but no such STATUS
406         message has been sent.
407
408         Method parameters:
409         - a hash with some combination of the following elements:
410                 - "username"
411                 - "barcode"
412                 - "password" (hashed with the cached seed; not plaintext)
413                 - "type"
414                 - "org"
415                 - "workstation"
416
417         The password is required.  Either a username or a barcode must also be present.
418
419         Return to client: Intermediate authentication seed.
420
421         Validate the password, using the username if available, or the barcode if not.  The
422         user must be active, and not barred from logging on.  The barcode, if used for
423         authentication, must be active as well.  The workstation, if specified, must be valid.
424
425         Upon deciding whether to allow the logon, return a corresponding event to the client.
426 */
427 int oilsAuthComplete( osrfMethodContext* ctx ) {
428         OSRF_METHOD_VERIFY_CONTEXT(ctx);
429
430         const jsonObject* args  = jsonObjectGetIndex(ctx->params, 0);
431
432         const char* uname       = jsonObjectGetString(jsonObjectGetKeyConst(args, "username"));
433         const char* password    = jsonObjectGetString(jsonObjectGetKeyConst(args, "password"));
434         const char* type        = jsonObjectGetString(jsonObjectGetKeyConst(args, "type"));
435         int orgloc        = (int) jsonObjectGetNumber(jsonObjectGetKeyConst(args, "org"));
436         const char* workstation = jsonObjectGetString(jsonObjectGetKeyConst(args, "workstation"));
437         const char* barcode     = jsonObjectGetString(jsonObjectGetKeyConst(args, "barcode"));
438
439         const char* ws = (workstation) ? workstation : "";
440
441         if( !type )
442                  type = OILS_AUTH_STAFF;
443
444         if( !( (uname || barcode) && password) ) {
445                 return osrfAppRequestRespondException( ctx->session, ctx->request,
446                         "username/barcode and password required for method: %s", ctx->method->name );
447         }
448
449         oilsEvent* response = NULL;
450         jsonObject* userObj = NULL;
451         int card_active     = 1;      // boolean; assume active until proven otherwise
452
453         // Fetch a row from the actor.usr table, by username if available,
454         // or by barcode if not.
455         if(uname) {
456                 userObj = oilsUtilsFetchUserByUsername( uname );
457                 if( userObj && JSON_NULL == userObj->type ) {
458                         jsonObjectFree( userObj );
459                         userObj = NULL;         // username not found
460                 }
461         }
462         else if(barcode) {
463                 // Read from actor.card by barcode
464
465                 osrfLogInfo( OSRF_LOG_MARK, "Fetching user by barcode %s", barcode );
466
467                 jsonObject* params = jsonParseFmt("{\"barcode\":\"%s\"}", barcode);
468                 jsonObject* card = oilsUtilsQuickReq(
469                         "open-ils.cstore", "open-ils.cstore.direct.actor.card.search", params );
470                 jsonObjectFree( params );
471
472                 if( card && card->type != JSON_NULL ) {
473                         // Determine whether the card is active
474                         char* card_active_str = oilsFMGetString( card, "active" );
475                         card_active = oilsUtilsIsDBTrue( card_active_str );
476                         free( card_active_str );
477
478                         // Look up the user who owns the card
479                         char* userid = oilsFMGetString( card, "usr" );
480                         jsonObjectFree( card );
481                         params = jsonParseFmt( "[%s]", userid );
482                         free( userid );
483                         userObj = oilsUtilsQuickReq(
484                                         "open-ils.cstore", "open-ils.cstore.direct.actor.user.retrieve", params );
485                         jsonObjectFree( params );
486                         if( userObj && JSON_NULL == userObj->type ) {
487                                 // user not found (shouldn't happen, due to foreign key)
488                                 jsonObjectFree( userObj );
489                                 userObj = NULL;
490                         }
491                 }
492         }
493
494         if(!userObj) {
495                 response = oilsNewEvent( OSRF_LOG_MARK, OILS_EVENT_AUTH_FAILED );
496                 osrfLogInfo(OSRF_LOG_MARK,  "failed login: username=%s, barcode=%s, workstation=%s",
497                                 uname, (barcode ? barcode : "(none)"), ws );
498                 osrfAppRespondComplete( ctx, oilsEventToJSON(response) );
499                 oilsEventFree(response);
500                 return 0;           // No such user
501         }
502
503         // Such a user exists.  Now see if he or she has the right credentials.
504         int passOK = -1;
505         if(uname)
506                 passOK = oilsAuthVerifyPassword( ctx, userObj, uname, password );
507         else if (barcode)
508                 passOK = oilsAuthVerifyPassword( ctx, userObj, barcode, password );
509
510         if( passOK < 0 ) {
511                 jsonObjectFree(userObj);
512                 return passOK;
513         }
514
515         // See if the account is active
516         char* active = oilsFMGetString(userObj, "active");
517         if( !oilsUtilsIsDBTrue(active) ) {
518                 if( passOK )
519                         response = oilsNewEvent( OSRF_LOG_MARK, "PATRON_INACTIVE" );
520                 else
521                         response = oilsNewEvent( OSRF_LOG_MARK, OILS_EVENT_AUTH_FAILED );
522
523                 osrfAppRespondComplete( ctx, oilsEventToJSON(response) );
524                 oilsEventFree(response);
525                 jsonObjectFree(userObj);
526                 free(active);
527                 return 0;
528         }
529         free(active);
530
531         osrfLogInfo( OSRF_LOG_MARK, "Fetching card by barcode %s", barcode );
532
533         if( !card_active ) {
534                 osrfLogInfo( OSRF_LOG_MARK, "barcode %s is not active, returning event", barcode );
535                 response = oilsNewEvent( OSRF_LOG_MARK, "PATRON_CARD_INACTIVE" );
536                 osrfAppRespondComplete( ctx, oilsEventToJSON( response ) );
537                 oilsEventFree( response );
538                 jsonObjectFree( userObj );
539                 return 0;
540         }
541
542
543         // See if the user is even allowed to log in
544         if( oilsAuthCheckLoginPerm( ctx, userObj, type ) == -1 ) {
545                 jsonObjectFree(userObj);
546                 return 0;
547         }
548
549         // If a workstation is defined, add the workstation info
550         if( workstation != NULL ) {
551                 osrfLogDebug(OSRF_LOG_MARK, "Workstation is %s", workstation);
552                 response = oilsAuthVerifyWorkstation( ctx, userObj, workstation );
553                 if(response) {
554                         jsonObjectFree(userObj);
555                         osrfAppRespondComplete( ctx, oilsEventToJSON(response) );
556                         oilsEventFree(response);
557                         return 0;
558                 }
559
560         } else {
561                 // Otherwise, use the home org as the workstation org on the user
562                 char* orgid = oilsFMGetString(userObj, "home_ou");
563                 oilsFMSetString(userObj, "ws_ou", orgid);
564                 free(orgid);
565         }
566
567         char* freeable_uname = NULL;
568         if(!uname) {
569                 uname = freeable_uname = oilsFMGetString( userObj, "usrname" );
570         }
571
572         if( passOK ) {
573                 response = oilsAuthHandleLoginOK( userObj, uname, type, orgloc, workstation );
574
575         } else {
576                 response = oilsNewEvent( OSRF_LOG_MARK, OILS_EVENT_AUTH_FAILED );
577                 osrfLogInfo(OSRF_LOG_MARK,  "failed login: username=%s, barcode=%s, workstation=%s",
578                                 uname, (barcode ? barcode : "(none)"), ws );
579         }
580
581         jsonObjectFree(userObj);
582         osrfAppRespondComplete( ctx, oilsEventToJSON(response) );
583         oilsEventFree(response);
584
585         if(freeable_uname)
586                 free(freeable_uname);
587
588         return 0;
589 }
590
591
592
593 int oilsAuthSessionDelete( osrfMethodContext* ctx ) {
594         OSRF_METHOD_VERIFY_CONTEXT(ctx);
595
596         const char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0) );
597         jsonObject* resp = NULL;
598
599         if( authToken ) {
600                 osrfLogDebug(OSRF_LOG_MARK, "Removing auth session: %s", authToken );
601                 char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken ); /**/
602                 osrfCacheRemove(key);
603                 resp = jsonNewObject(authToken); /**/
604                 free(key);
605         }
606
607         osrfAppRespondComplete( ctx, resp );
608         jsonObjectFree(resp);
609         return 0;
610 }
611
612 /**
613         Resets the auth login timeout
614         @return The event object, OILS_EVENT_SUCCESS, or OILS_EVENT_NO_SESSION
615 */
616 static oilsEvent*  _oilsAuthResetTimeout( const char* authToken ) {
617         if(!authToken) return NULL;
618
619         oilsEvent* evt = NULL;
620         time_t timeout;
621
622         osrfLogDebug(OSRF_LOG_MARK, "Resetting auth timeout for session %s", authToken);
623         char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken );
624         jsonObject* cacheObj = osrfCacheGetObject( key );
625
626         if(!cacheObj) {
627                 osrfLogInfo(OSRF_LOG_MARK, "No user in the cache exists with key %s", key);
628                 evt = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_NO_SESSION);
629
630         } else {
631                 // Determine a new timeout value
632                 jsonObject* endtime_obj = jsonObjectGetKey( cacheObj, "endtime" );
633                 if( endtime_obj ) {
634                         // Extend the current endtime by a fixed amount
635                         time_t endtime = (time_t) jsonObjectGetNumber( endtime_obj );
636                         int reset_interval = DEFAULT_RESET_INTERVAL;
637                         jsonObject* reset_interval_obj = jsonObjectGetKey( cacheObj, "reset_interval" );
638                         if( reset_interval_obj ) {
639                                 reset_interval = (int) jsonObjectGetNumber( reset_interval_obj );
640                                 if( reset_interval <= 0 )
641                                         reset_interval = DEFAULT_RESET_INTERVAL;
642                         }
643
644                         time_t now = time( NULL );
645                         time_t new_endtime = now + reset_interval;
646                         if( new_endtime > endtime ) {
647                                 // Keep the session alive a little longer
648                                 jsonObjectSetNumber( endtime_obj, (double) new_endtime );
649                                 timeout = reset_interval;
650                                 osrfCachePutObject( key, cacheObj, timeout );
651                         } else {
652                                 // The session isn't close to expiring, so don't reset anything.
653                                 // Just report the time remaining.
654                                 timeout = endtime - now;
655                         }
656                 } else {
657                         // Reapply the existing timeout from the current time
658                         timeout = (time_t) jsonObjectGetNumber( jsonObjectGetKeyConst( cacheObj, "authtime"));
659                         osrfCachePutObject( key, cacheObj, timeout );
660                 }
661
662                 jsonObject* payload = jsonNewNumberObject( (double) timeout );
663                 evt = oilsNewEvent2(OSRF_LOG_MARK, OILS_EVENT_SUCCESS, payload);
664                 jsonObjectFree(payload);
665                 jsonObjectFree(cacheObj);
666         }
667
668         free(key);
669         return evt;
670 }
671
672 int oilsAuthResetTimeout( osrfMethodContext* ctx ) {
673         OSRF_METHOD_VERIFY_CONTEXT(ctx);
674         const char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0));
675         oilsEvent* evt = _oilsAuthResetTimeout(authToken);
676         osrfAppRespondComplete( ctx, oilsEventToJSON(evt) );
677         oilsEventFree(evt);
678         return 0;
679 }
680
681
682 int oilsAuthSessionRetrieve( osrfMethodContext* ctx ) {
683         OSRF_METHOD_VERIFY_CONTEXT(ctx);
684
685         const char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0));
686         jsonObject* cacheObj = NULL;
687         oilsEvent* evt = NULL;
688
689         if( authToken ){
690
691                 // Reset the timeout to keep the session alive
692                 evt = _oilsAuthResetTimeout(authToken);
693
694                 if( evt && strcmp(evt->event, OILS_EVENT_SUCCESS) ) {
695                         osrfAppRespondComplete( ctx, oilsEventToJSON( evt ));    // can't reset timeout
696
697                 } else {
698
699                         // Retrieve the cached session object
700                         osrfLogDebug(OSRF_LOG_MARK, "Retrieving auth session: %s", authToken);
701                         char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken );
702                         cacheObj = osrfCacheGetObject( key );
703                         if(cacheObj) {
704                                 // Return a copy of the cached user object
705                                 osrfAppRespondComplete( ctx, jsonObjectGetKeyConst( cacheObj, "userobj"));
706                                 jsonObjectFree(cacheObj);
707                         } else {
708                                 // Auth token is invalid or expired
709                                 oilsEvent* evt2 = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_NO_SESSION);
710                                 osrfAppRespondComplete( ctx, oilsEventToJSON(evt2) ); /* should be event.. */
711                                 oilsEventFree(evt2);
712                         }
713                         free(key);
714                 }
715
716         } else {
717
718                 // No session
719                 evt = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_NO_SESSION);
720                 osrfAppRespondComplete( ctx, oilsEventToJSON(evt) );
721         }
722
723         if(evt)
724                 oilsEventFree(evt);
725
726         return 0;
727 }