re-arranged login params to take named params so one method can
[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 "objson/object.h"
5 #include "opensrf/log.h"
6 #include "oils_utils.h"
7 #include "oils_constants.h"
8 #include "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_OVERRIDE "override"
17
18 int osrfAppInitialize();
19 int osrfAppChildInit();
20
21 int __oilsAuthOPACTimeout = 0;
22 int __oilsAuthStaffTimeout = 0;
23 int __oilsAuthOverrideTimeout = 0;
24
25
26 int osrfAppInitialize() {
27
28         osrfLogInfo(OSRF_LOG_MARK, "Initializing Auth Server...");
29
30         osrfAppRegisterMethod( 
31                 MODULENAME, 
32                 "open-ils.auth.authenticate.init", 
33                 "oilsAuthInit", 
34                 "Start the authentication process and returns the intermediate authentication seed"
35                 " PARAMS( username )", 1, 0 );
36
37         osrfAppRegisterMethod( 
38                 MODULENAME, 
39                 "open-ils.auth.authenticate.complete", 
40                 "oilsAuthComplete", 
41                 "Completes the authentication process.  Returns an object like so: "
42                 "{authtoken : <token>, authtime:<time>}, where authtoken is the login "
43                 "tokena and authtime is the number of seconds the session will be active"
44                 "PARAMS(username, md5sum( seed + password ), type, org_id ) "
45                 "type can be one of 'opac','staff', or 'override' and it defaults to 'staff' "
46                 "org_id is the location at which the login should be considered "
47                 "active for login timeout purposes"     , 1, 0 );
48
49         osrfAppRegisterMethod( 
50                 MODULENAME, 
51                 "open-ils.auth.session.retrieve", 
52                 "oilsAuthSessionRetrieve", 
53                 "Pass in the auth token and this retrieves the user object.  The auth "
54                 "timeout is reset when this call is made "
55                 "Returns the user object (password blanked) for the given login session "
56                 "PARAMS( authToken )", 1, 0 );
57
58         osrfAppRegisterMethod( 
59                 MODULENAME, 
60                 "open-ils.auth.session.delete", 
61                 "oilsAuthSessionDelete", 
62                 "Destroys the given login session "
63                 "PARAMS( authToken )",  1, 0 );
64
65         osrfAppRegisterMethod(
66                 MODULENAME,
67                 "open-ils.auth.session.reset_timeout",
68                 "oilsAuthResetTimeout",
69                 "Resets the login timeout for the given session "
70                 "Returns an ILS Event with payload = session_timeout of session "
71                 "is found, otherwise returns the NO_SESSION event"
72                 "PARAMS( authToken )", 1, 0 );
73
74         return 0;
75 }
76
77 int osrfAppChildInit() {
78         return 0;
79 }
80
81 int oilsAuthInit( osrfMethodContext* ctx ) {
82         OSRF_METHOD_VERIFY_CONTEXT(ctx); 
83
84         jsonObject* resp;
85
86         char* username = NULL;
87         char* seed              = NULL;
88         char* md5seed   = NULL;
89         char* key               = NULL;
90
91         if( (username = jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, 0))) ) {
92
93                 seed = va_list_to_string( "%d.%d.%s", time(NULL), getpid(), username );
94                 key = va_list_to_string( "%s%s", OILS_AUTH_CACHE_PRFX, username );
95
96                 md5seed = md5sum(seed);
97                 osrfCachePutString( key, md5seed, 30 );
98
99                 osrfLogDebug( OSRF_LOG_MARK, "oilsAuthInit(): has seed %s and key %s", md5seed, key );
100
101                 resp = jsonNewObject(md5seed);  
102                 osrfAppRespondComplete( ctx, resp );
103
104                 jsonObjectFree(resp);
105                 free(seed);
106                 free(md5seed);
107                 free(key);
108                 free(username);
109                 return 0;
110         }
111
112         return -1;
113 }
114
115 /** Verifies that the user has permission to login with the 
116  * given type.  If the permission fails, an oilsEvent is returned
117  * to the caller.
118  * @return -1 if the permission check failed, 0 if ther permission
119  * is granted
120  */
121 int oilsAuthCheckLoginPerm( 
122                 osrfMethodContext* ctx, jsonObject* userObj, char* type ) {
123
124         if(!(userObj && type)) return -1;
125         oilsEvent* perm = NULL;
126
127         if(!strcasecmp(type, OILS_AUTH_OPAC)) {
128                 char* permissions[] = { "OPAC_LOGIN" };
129                 perm = oilsUtilsCheckPerms( oilsFMGetObjectId( userObj ), -1, permissions, 1 );
130
131         } else if(!strcasecmp(type, OILS_AUTH_STAFF)) {
132                 char* permissions[] = { "STAFF_LOGIN" };
133                 perm = oilsUtilsCheckPerms( oilsFMGetObjectId( userObj ), -1, permissions, 1 );
134
135         } else if(!strcasecmp(type, OILS_AUTH_OVERRIDE)) {
136                 char* permissions[] = { "STAFF_LOGIN" };
137                 perm = oilsUtilsCheckPerms( oilsFMGetObjectId( userObj ), -1, permissions, 1 );
138         }
139
140         if(perm) {
141                 osrfAppRespondComplete( ctx, oilsEventToJSON(perm) ); 
142                 oilsEventFree(perm);
143                 return -1;
144         }
145
146         return 0;
147 }
148
149 /**
150  * Returns 1 if the password provided matches the user's real password
151  * Returns 0 otherwise
152  * Returns -1 on error
153  */
154 int oilsAuthVerifyPassword( 
155                 osrfMethodContext* ctx, jsonObject* userObj, char* uname, char* password ) {
156
157         int ret = 0;
158         char* realPassword = oilsFMGetString( userObj, "passwd" ); /**/
159         char* seed = osrfCacheGetString( "%s%s", OILS_AUTH_CACHE_PRFX, uname ); /**/
160
161         if(!seed) {
162                 return osrfAppRequestRespondException( ctx->session,
163                         ctx->request, "No authentication seed found. "
164                         "open-ils.auth.authenticate.init must be called first");
165         }
166
167         osrfLogDebug(OSRF_LOG_MARK,  "oilsAuth retrieved seed from cache: %s", seed );
168         char* maskedPw = md5sum( "%s%s", seed, realPassword );
169         if(!maskedPw) return -1;
170         osrfLogDebug(OSRF_LOG_MARK,  "oilsAuth generated masked password %s. "
171                         "Testing against provided password %s", maskedPw, password );
172
173         if( !strcmp( maskedPw, password ) ) ret = 1;
174
175         free(realPassword);
176         free(seed);
177         free(maskedPw);
178
179         return ret;
180 }
181
182 /**
183  * Calculates the login timeout
184  * 1. If orgloc is 1 or greater and has a timeout specified as an 
185  * org unit setting, it is used
186  * 2. If orgloc is not valid, we check the org unit auth timeout 
187  * setting for the home org unit of the user logging in
188  * 3. If that setting is not defined, we use the configured defaults
189  */
190 double oilsAuthGetTimeout( jsonObject* userObj, char* type, double orgloc ) {
191
192         if(!__oilsAuthOPACTimeout) { /* Load the default timeouts */
193
194                 __oilsAuthOPACTimeout = 
195                         jsonObjectGetNumber( 
196                                 osrf_settings_host_value_object( 
197                                         "/apps/open-ils.auth/app_settings/default_timeout/opac"));
198
199                 __oilsAuthStaffTimeout = 
200                         jsonObjectGetNumber( 
201                                 osrf_settings_host_value_object( 
202                                         "/apps/open-ils.auth/app_settings/default_timeout/staff" ));
203
204                 __oilsAuthOverrideTimeout = 
205                         jsonObjectGetNumber( 
206                                 osrf_settings_host_value_object( 
207                                         "/apps/open-ils.auth/app_settings/default_timeout/override" ));
208
209
210                 osrfLogInfo(OSRF_LOG_MARK, "Set default auth timetouts: opac => %d : staff => %d : override => %d",
211                                 __oilsAuthOPACTimeout, __oilsAuthStaffTimeout, __oilsAuthOverrideTimeout );
212         }
213
214         char* setting = NULL;
215
216         double home_ou = jsonObjectGetNumber( oilsFMGetObject( userObj, "home_ou" ) );
217         if(orgloc < 1) orgloc = (int) home_ou;
218
219         if(!strcmp(type, OILS_AUTH_OPAC)) 
220                 setting = OILS_ORG_SETTING_OPAC_TIMEOUT;
221         else if(!strcmp(type, OILS_AUTH_STAFF)) 
222                 setting = OILS_ORG_SETTING_STAFF_TIMEOUT;
223         else if(!strcmp(type, OILS_AUTH_OVERRIDE)) 
224                 setting = OILS_ORG_SETTING_OVERRIDE_TIMEOUT;
225
226         char* timeout = oilsUtilsFetchOrgSetting( orgloc, setting );
227
228         if(!timeout) {
229                 if( orgloc != home_ou ) {
230                         osrfLogDebug(OSRF_LOG_MARK, "Auth timeout not defined for org %d, "
231                                                                 "trying home_ou %d", orgloc, home_ou );
232                         timeout = oilsUtilsFetchOrgSetting( (int) home_ou, setting );
233                 }
234                 if(!timeout) {
235                         if(!strcmp(type, OILS_AUTH_STAFF)) return __oilsAuthStaffTimeout;
236                         if(!strcmp(type, OILS_AUTH_OVERRIDE)) return __oilsAuthOverrideTimeout;
237                         return __oilsAuthOPACTimeout;
238                 }
239         }
240
241         double t = atof(timeout);
242         free(timeout);
243         return t ;
244 }
245
246 /* Adds the authentication token to the user cache.  The timeout for the 
247  * auth token is based on the type of login as well as (if type=='opac') 
248  * the org location id.
249  * Returns the event that should be returned to the user.  
250  * Event must be freed
251  */
252 oilsEvent* oilsAuthHandleLoginOK( 
253                 jsonObject* userObj, char* uname, char* type, double orgloc ) { 
254                 
255         oilsEvent* response;
256         osrfLogActivity(OSRF_LOG_MARK,  "User %s successfully logged in", uname );
257
258         double timeout;
259         char* wsorg = jsonObjectToSimpleString(oilsFMGetObject(userObj, "ws_ou"));
260         if(wsorg) { /* if there is a workstation, use it for the timeout */
261                 osrfLogDebug( OSRF_LOG_MARK, 
262                                 "Auth session trying workstation id %d for auth timeout", atoi(wsorg));
263                 timeout = oilsAuthGetTimeout( userObj, type, atoi(wsorg) );
264                 free(wsorg);
265         } else {
266                 osrfLogDebug( OSRF_LOG_MARK, 
267                                 "Auth session trying org from param [%d] for auth timeout", orgloc );
268                 timeout = oilsAuthGetTimeout( userObj, type, orgloc );
269         }
270         osrfLogDebug(OSRF_LOG_MARK, "Auth session timeout for %s: %lf", uname, timeout );
271
272         char* string = va_list_to_string( 
273                         "%d.%d.%s", getpid(), time(NULL), uname ); 
274         char* authToken = md5sum(string); 
275         char* authKey = va_list_to_string( 
276                         "%s%s", OILS_AUTH_CACHE_PRFX, authToken ); 
277
278         oilsFMSetString( userObj, "passwd", "" );
279         jsonObject* cacheObj = jsonParseString("{\"authtime\": %lf}", timeout);
280         jsonObjectSetKey( cacheObj, "userobj", jsonObjectClone(userObj));
281
282         osrfCachePutObject( authKey, cacheObj, timeout ); 
283         jsonObjectFree(cacheObj);
284         osrfLogInternal(OSRF_LOG_MARK, "oilsAuthComplete(): Placed user object into cache");
285         jsonObject* payload = jsonParseString(
286                 "{ \"authtoken\": \"%s\", \"authtime\": %lf }", authToken, timeout );
287
288         response = oilsNewEvent2( OILS_EVENT_SUCCESS, payload );
289         free(string); free(authToken); free(authKey);
290         jsonObjectFree(payload);
291
292         return response;
293 }
294
295 oilsEvent* oilsAuthVerifyWorkstation( 
296                 osrfMethodContext* ctx, jsonObject* userObj, char* ws ) {
297         osrfLogInfo(OSRF_LOG_MARK, "Attaching workstation to user at login: %s", ws);
298         jsonObject* workstation = oilsUtilsFetchWorkstationByName(ws);
299         if(!workstation) return oilsNewEvent("WORKSTATION_NOT_FOUND");
300         long wsid = oilsFMGetObjectId(workstation);
301         LONG_TO_STRING(wsid);
302         char* orgid = oilsFMGetString(workstation, "owning_lib");
303         oilsFMSetString(userObj, "wsid", LONGSTR);
304         oilsFMSetString(userObj, "ws_ou", orgid);
305         free(orgid);
306         return NULL;
307 }
308
309
310
311 int oilsAuthComplete( osrfMethodContext* ctx ) {
312         OSRF_METHOD_VERIFY_CONTEXT(ctx); 
313
314         jsonObject* args                = jsonObjectGetIndex(ctx->params, 0);
315
316         char* uname                             = jsonObjectGetString(jsonObjectGetKey(args, "username"));
317         char* password                  = jsonObjectGetString(jsonObjectGetKey(args, "password"));
318         char* type                              = jsonObjectGetString(jsonObjectGetKey(args, "type"));
319         double orgloc                   = jsonObjectGetNumber(jsonObjectGetKey(args, "org"));
320         char* workstation               = jsonObjectGetString(jsonObjectGetKey(args, "workstation"));
321         char* barcode                   = jsonObjectToSimpleString(jsonObjectGetKey(args, "barcode"));
322
323
324         if(!type) type = OILS_AUTH_STAFF;
325
326         if( !( (uname || barcode) && password) ) {
327                 free(barcode);
328                 return osrfAppRequestRespondException( ctx->session, ctx->request, 
329                         "username/barocode and password required for method: %s", ctx->method->name );
330         }
331
332         oilsEvent* response = NULL;
333         jsonObject* userObj = NULL;
334
335         if(uname) userObj = oilsUtilsFetchUserByUsername( uname ); 
336         else if(barcode) userObj = oilsUtilsFetchUserByBarcode( barcode );
337         
338         if(!userObj) { 
339                 response = oilsNewEvent( OILS_EVENT_AUTH_FAILED );
340                 osrfAppRespondComplete( ctx, oilsEventToJSON(response) ); 
341                 oilsEventFree(response);
342                 free(barcode);
343                 return 0;
344         }
345
346         /* check to see if the user is allowed to login */
347         if( oilsAuthCheckLoginPerm( ctx, userObj, type ) == -1 ) {
348                 jsonObjectFree(userObj);
349                 free(barcode);
350                 return 0;
351         }
352
353         
354         int passOK = -1;
355         if(uname) passOK = oilsAuthVerifyPassword( ctx, userObj, uname, password );
356         else if (barcode) 
357                 passOK = oilsAuthVerifyPassword( ctx, userObj, barcode, password );
358
359         if( passOK < 0 ) {
360                 free(barcode);
361                 return passOK;
362         }
363
364         /* if a workstation is defined, flesh the user with the workstation info */
365         if( workstation != NULL ) {
366                 osrfLogDebug(OSRF_LOG_MARK, "Workstation is %s", workstation);
367                 response = oilsAuthVerifyWorkstation( ctx, userObj, workstation );
368                 if(response) {
369                         jsonObjectFree(userObj);
370                         osrfAppRespondComplete( ctx, oilsEventToJSON(response) ); 
371                         oilsEventFree(response);
372                         free(barcode);
373                         return 0;
374                 }
375         }
376
377         if( passOK ) {
378                 response = oilsAuthHandleLoginOK( userObj, uname, type, orgloc );
379
380         } else {
381                 response = oilsNewEvent( OILS_EVENT_AUTH_FAILED );
382                 osrfLogInfo(OSRF_LOG_MARK,  "Login failed for for %s", uname );
383         }
384
385         jsonObjectFree(userObj);
386         osrfAppRespondComplete( ctx, oilsEventToJSON(response) ); 
387         oilsEventFree(response);
388         free(barcode);
389
390         return 0;
391 }
392
393
394
395 int oilsAuthSessionDelete( osrfMethodContext* ctx ) {
396         OSRF_METHOD_VERIFY_CONTEXT(ctx); 
397
398         char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0) );
399         jsonObject* resp = NULL;
400
401         if( authToken ) {
402                 osrfLogDebug(OSRF_LOG_MARK, "Removing auth session: %s", authToken );
403                 char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken ); /**/
404                 osrfCacheRemove(key);
405                 resp = jsonNewObject(authToken); /**/
406                 free(key);
407         }
408
409         osrfAppRespondComplete( ctx, resp );
410         jsonObjectFree(resp);
411         return 0;
412 }
413
414 /** Resets the auth login timeout
415  * @return The event object, OILS_EVENT_SUCCESS, or OILS_EVENT_NO_SESSION
416  */
417 oilsEvent*  _oilsAuthResetTimeout( char* authToken ) {
418         if(!authToken) return NULL;
419
420         oilsEvent* evt = NULL;
421         double timeout;
422
423         osrfLogDebug(OSRF_LOG_MARK, "Resetting auth timeout for session %s", authToken);
424         char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken ); 
425         jsonObject* cacheObj = osrfCacheGetObject( key ); 
426
427         if(!cacheObj) {
428                 osrfLogError(OSRF_LOG_MARK, "No user in the cache exists with key %s", key);
429                 evt = oilsNewEvent(OILS_EVENT_NO_SESSION);
430
431         } else {
432
433                 timeout = jsonObjectGetNumber( jsonObjectGetKey( cacheObj, "authtime"));
434                 osrfCacheSetExpire( timeout, key );
435                 jsonObject* payload = jsonNewNumberObject(timeout);
436                 evt = oilsNewEvent2(OILS_EVENT_SUCCESS, payload);
437                 jsonObjectFree(payload);
438                 jsonObjectFree(cacheObj);
439         }
440
441         free(key);
442         return evt;
443 }
444
445 int oilsAuthResetTimeout( osrfMethodContext* ctx ) {
446         OSRF_METHOD_VERIFY_CONTEXT(ctx); 
447         char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0));
448         oilsEvent* evt = _oilsAuthResetTimeout(authToken);
449         osrfAppRespondComplete( ctx, oilsEventToJSON(evt) );
450         oilsEventFree(evt);
451         return 0;
452 }
453
454
455 int oilsAuthSessionRetrieve( osrfMethodContext* ctx ) {
456         OSRF_METHOD_VERIFY_CONTEXT(ctx); 
457
458         char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0));
459         jsonObject* cacheObj = NULL;
460         oilsEvent* evt = NULL;
461
462         if( authToken ){
463
464                 evt = _oilsAuthResetTimeout(authToken);
465
466                 if( evt && strcmp(evt->event, OILS_EVENT_SUCCESS) ) {
467                         osrfAppRespondComplete( ctx, oilsEventToJSON(evt) );
468                         oilsEventFree(evt);
469
470                 } else {
471
472                         osrfLogDebug(OSRF_LOG_MARK, "Retrieving auth session: %s", authToken);
473                         char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken ); 
474                         cacheObj = osrfCacheGetObject( key ); 
475                         if(cacheObj) {
476                                 osrfAppRespondComplete( ctx, jsonObjectGetKey( cacheObj, "userobj"));
477                                 jsonObjectFree(cacheObj);
478                         } else {
479                                 oilsEvent* evt = oilsNewEvent(OILS_EVENT_NO_SESSION);
480                                 osrfAppRespondComplete( ctx, oilsEventToJSON(evt) ); /* should be event.. */
481                                 oilsEventFree(evt);
482                         }
483                         free(key);
484                 }
485
486         } else {
487
488                 evt = oilsNewEvent(OILS_EVENT_NO_SESSION);
489                 osrfAppRespondComplete( ctx, oilsEventToJSON(evt) );
490                 oilsEventFree(evt);
491         }
492
493         return 0;
494 }
495
496
497