]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/c-apps/oils_auth.c
moved json parsing methods to non-printf style methods, added new Fmt to behave the...
[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_TEMP "temp"
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 'temp' 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                 if( strchr( username, ' ' ) ) {
94
95                         /* spaces are not allowed */
96                         resp = jsonNewObject("x");       /* 'x' will never be a valid seed */
97                         osrfAppRespondComplete( ctx, resp );
98
99                 } else {
100
101                         seed = va_list_to_string( "%d.%d.%s", time(NULL), getpid(), username );
102                         key = va_list_to_string( "%s%s", OILS_AUTH_CACHE_PRFX, username );
103         
104                         md5seed = md5sum(seed);
105                         osrfCachePutString( key, md5seed, 30 );
106         
107                         osrfLogDebug( OSRF_LOG_MARK, "oilsAuthInit(): has seed %s and key %s", md5seed, key );
108         
109                         resp = jsonNewObject(md5seed);  
110                         osrfAppRespondComplete( ctx, resp );
111         
112                         free(seed);
113                         free(md5seed);
114                         free(key);
115                         free(username);
116                 }
117
118                 jsonObjectFree(resp);
119                 return 0;
120         }
121
122         return -1;
123 }
124
125 /** Verifies that the user has permission to login with the 
126  * given type.  If the permission fails, an oilsEvent is returned
127  * to the caller.
128  * @return -1 if the permission check failed, 0 if ther permission
129  * is granted
130  */
131 int oilsAuthCheckLoginPerm( 
132                 osrfMethodContext* ctx, jsonObject* userObj, char* type ) {
133
134         if(!(userObj && type)) return -1;
135         oilsEvent* perm = NULL;
136
137         if(!strcasecmp(type, OILS_AUTH_OPAC)) {
138                 char* permissions[] = { "OPAC_LOGIN" };
139                 perm = oilsUtilsCheckPerms( oilsFMGetObjectId( userObj ), -1, permissions, 1 );
140
141         } else if(!strcasecmp(type, OILS_AUTH_STAFF)) {
142                 char* permissions[] = { "STAFF_LOGIN" };
143                 perm = oilsUtilsCheckPerms( oilsFMGetObjectId( userObj ), -1, permissions, 1 );
144
145         } else if(!strcasecmp(type, OILS_AUTH_TEMP)) {
146                 char* permissions[] = { "STAFF_LOGIN" };
147                 perm = oilsUtilsCheckPerms( oilsFMGetObjectId( userObj ), -1, permissions, 1 );
148         }
149
150         if(perm) {
151                 osrfAppRespondComplete( ctx, oilsEventToJSON(perm) ); 
152                 oilsEventFree(perm);
153                 return -1;
154         }
155
156         return 0;
157 }
158
159 /**
160  * Returns 1 if the password provided matches the user's real password
161  * Returns 0 otherwise
162  * Returns -1 on error
163  */
164 int oilsAuthVerifyPassword( 
165                 osrfMethodContext* ctx, jsonObject* userObj, char* uname, char* password ) {
166
167         int ret = 0;
168         char* realPassword = oilsFMGetString( userObj, "passwd" ); /**/
169         char* seed = osrfCacheGetString( "%s%s", OILS_AUTH_CACHE_PRFX, uname ); /**/
170
171         if(!seed) {
172                 return osrfAppRequestRespondException( ctx->session,
173                         ctx->request, "No authentication seed found. "
174                         "open-ils.auth.authenticate.init must be called first");
175         }
176
177         osrfLogDebug(OSRF_LOG_MARK,  "oilsAuth retrieved seed from cache: %s", seed );
178         char* maskedPw = md5sum( "%s%s", seed, realPassword );
179         if(!maskedPw) return -1;
180         osrfLogDebug(OSRF_LOG_MARK,  "oilsAuth generated masked password %s. "
181                         "Testing against provided password %s", maskedPw, password );
182
183         if( !strcmp( maskedPw, password ) ) ret = 1;
184
185         free(realPassword);
186         free(seed);
187         free(maskedPw);
188
189         return ret;
190 }
191
192 /**
193  * Calculates the login timeout
194  * 1. If orgloc is 1 or greater and has a timeout specified as an 
195  * org unit setting, it is used
196  * 2. If orgloc is not valid, we check the org unit auth timeout 
197  * setting for the home org unit of the user logging in
198  * 3. If that setting is not defined, we use the configured defaults
199  */
200 double oilsAuthGetTimeout( jsonObject* userObj, char* type, double orgloc ) {
201
202         if(!__oilsAuthOPACTimeout) { /* Load the default timeouts */
203
204                 __oilsAuthOPACTimeout = 
205                         jsonObjectGetNumber( 
206                                 osrf_settings_host_value_object( 
207                                         "/apps/open-ils.auth/app_settings/default_timeout/opac"));
208
209                 __oilsAuthStaffTimeout = 
210                         jsonObjectGetNumber( 
211                                 osrf_settings_host_value_object( 
212                                         "/apps/open-ils.auth/app_settings/default_timeout/staff" ));
213
214                 __oilsAuthOverrideTimeout = 
215                         jsonObjectGetNumber( 
216                                 osrf_settings_host_value_object( 
217                                         "/apps/open-ils.auth/app_settings/default_timeout/temp" ));
218
219
220                 osrfLogInfo(OSRF_LOG_MARK, "Set default auth timetouts: opac => %d : staff => %d : temp => %d",
221                                 __oilsAuthOPACTimeout, __oilsAuthStaffTimeout, __oilsAuthOverrideTimeout );
222         }
223
224         char* setting = NULL;
225
226         double home_ou = jsonObjectGetNumber( oilsFMGetObject( userObj, "home_ou" ) );
227         if(orgloc < 1) orgloc = (int) home_ou;
228
229         if(!strcmp(type, OILS_AUTH_OPAC)) 
230                 setting = OILS_ORG_SETTING_OPAC_TIMEOUT;
231         else if(!strcmp(type, OILS_AUTH_STAFF)) 
232                 setting = OILS_ORG_SETTING_STAFF_TIMEOUT;
233         else if(!strcmp(type, OILS_AUTH_TEMP)) 
234                 setting = OILS_ORG_SETTING_TEMP_TIMEOUT;
235
236         char* timeout = oilsUtilsFetchOrgSetting( orgloc, setting );
237
238         if(!timeout) {
239                 if( orgloc != home_ou ) {
240                         osrfLogDebug(OSRF_LOG_MARK, "Auth timeout not defined for org %d, "
241                                                                 "trying home_ou %d", orgloc, home_ou );
242                         timeout = oilsUtilsFetchOrgSetting( (int) home_ou, setting );
243                 }
244                 if(!timeout) {
245                         if(!strcmp(type, OILS_AUTH_STAFF)) return __oilsAuthStaffTimeout;
246                         if(!strcmp(type, OILS_AUTH_TEMP)) return __oilsAuthOverrideTimeout;
247                         return __oilsAuthOPACTimeout;
248                 }
249         }
250
251         double t = atof(timeout);
252         free(timeout);
253         return t ;
254 }
255
256 /* Adds the authentication token to the user cache.  The timeout for the 
257  * auth token is based on the type of login as well as (if type=='opac') 
258  * the org location id.
259  * Returns the event that should be returned to the user.  
260  * Event must be freed
261  */
262 oilsEvent* oilsAuthHandleLoginOK( 
263                 jsonObject* userObj, char* uname, char* type, double orgloc ) { 
264                 
265         oilsEvent* response;
266
267         double timeout;
268         char* wsorg = jsonObjectToSimpleString(oilsFMGetObject(userObj, "ws_ou"));
269         if(wsorg) { /* if there is a workstation, use it for the timeout */
270                 osrfLogDebug( OSRF_LOG_MARK, 
271                                 "Auth session trying workstation id %d for auth timeout", atoi(wsorg));
272                 timeout = oilsAuthGetTimeout( userObj, type, atoi(wsorg) );
273                 free(wsorg);
274         } else {
275                 osrfLogDebug( OSRF_LOG_MARK, 
276                                 "Auth session trying org from param [%d] for auth timeout", orgloc );
277                 timeout = oilsAuthGetTimeout( userObj, type, orgloc );
278         }
279         osrfLogDebug(OSRF_LOG_MARK, "Auth session timeout for %s: %lf", uname, timeout );
280
281         char* string = va_list_to_string( 
282                         "%d.%d.%s", getpid(), time(NULL), uname ); 
283         char* authToken = md5sum(string); 
284         char* authKey = va_list_to_string( 
285                         "%s%s", OILS_AUTH_CACHE_PRFX, authToken ); 
286
287         osrfLogActivity(OSRF_LOG_MARK,  "User %s successfully logged in: %s", uname, authToken );
288
289         oilsFMSetString( userObj, "passwd", "" );
290         jsonObject* cacheObj = jsonParseStringFmt("{\"authtime\": %lf}", timeout);
291         jsonObjectSetKey( cacheObj, "userobj", jsonObjectClone(userObj));
292
293         osrfCachePutObject( authKey, cacheObj, timeout ); 
294         jsonObjectFree(cacheObj);
295         osrfLogInternal(OSRF_LOG_MARK, "oilsAuthComplete(): Placed user object into cache");
296         jsonObject* payload = jsonParseStringFmt(
297                 "{ \"authtoken\": \"%s\", \"authtime\": %lf }", authToken, timeout );
298
299         response = oilsNewEvent2( OSRF_LOG_MARK, OILS_EVENT_SUCCESS, payload );
300         free(string); free(authToken); free(authKey);
301         jsonObjectFree(payload);
302
303         return response;
304 }
305
306 oilsEvent* oilsAuthVerifyWorkstation( 
307                 osrfMethodContext* ctx, jsonObject* userObj, char* ws ) {
308         osrfLogInfo(OSRF_LOG_MARK, "Attaching workstation to user at login: %s", ws);
309         jsonObject* workstation = oilsUtilsFetchWorkstationByName(ws);
310         if(!workstation) return oilsNewEvent(OSRF_LOG_MARK, "WORKSTATION_NOT_FOUND");
311         long wsid = oilsFMGetObjectId(workstation);
312         LONG_TO_STRING(wsid);
313         char* orgid = oilsFMGetString(workstation, "owning_lib");
314         oilsFMSetString(userObj, "wsid", LONGSTR);
315         oilsFMSetString(userObj, "ws_ou", orgid);
316         free(orgid);
317         return NULL;
318 }
319
320
321
322 /* see if the card used to login is marked as barred */
323 static oilsEvent* oilsAuthCheckCard( osrfMethodContext* ctx, jsonObject* userObj, char* barcode) {
324         if(!(ctx && userObj && barcode)) return NULL;
325         osrfLogDebug(OSRF_LOG_MARK, "Checking to see if barcode %s is active", barcode);
326
327         jsonObject* params = jsonParseStringFmt("{\"barcode\":\"%s\"}", barcode);
328         jsonObject* card = oilsUtilsQuickReq(
329                 "open-ils.cstore", "open-ils.cstore.direct.actor.card.search", params );
330
331         char* active = oilsFMGetString(card, "active");
332         if( ! oilsUtilsIsDBTrue(active) ) {
333                 free(active);
334                 osrfLogInfo(OSRF_LOG_MARK, "barcode %s is not active, returning event", barcode);
335                 return oilsNewEvent(OSRF_LOG_MARK, "PATRON_CARD_INACTIVE");
336         }
337
338         free(active);
339         return NULL;
340 }
341
342
343
344 int oilsAuthComplete( osrfMethodContext* ctx ) {
345         OSRF_METHOD_VERIFY_CONTEXT(ctx); 
346
347         jsonObject* args                = jsonObjectGetIndex(ctx->params, 0);
348
349         char* uname                             = jsonObjectGetString(jsonObjectGetKey(args, "username"));
350         char* password                  = jsonObjectGetString(jsonObjectGetKey(args, "password"));
351         char* type                              = jsonObjectGetString(jsonObjectGetKey(args, "type"));
352         double orgloc                   = jsonObjectGetNumber(jsonObjectGetKey(args, "org"));
353         char* workstation               = jsonObjectGetString(jsonObjectGetKey(args, "workstation"));
354         char* barcode                   = jsonObjectToSimpleString(jsonObjectGetKey(args, "barcode"));
355
356
357         if(!type) type = OILS_AUTH_STAFF;
358
359         if( !( (uname || barcode) && password) ) {
360                 free(barcode);
361                 return osrfAppRequestRespondException( ctx->session, ctx->request, 
362                         "username/barocode and password required for method: %s", ctx->method->name );
363         }
364
365         oilsEvent* response = NULL;
366         jsonObject* userObj = NULL;
367
368         if(uname) userObj = oilsUtilsFetchUserByUsername( uname ); 
369         else if(barcode) userObj = oilsUtilsFetchUserByBarcode( barcode );
370         
371         if(!userObj) { 
372                 response = oilsNewEvent( OSRF_LOG_MARK, OILS_EVENT_AUTH_FAILED );
373                 osrfAppRespondComplete( ctx, oilsEventToJSON(response) ); 
374                 oilsEventFree(response);
375                 free(barcode);
376                 return 0;
377         }
378
379         /* first let's see if they have the right credentials */
380         int passOK = -1;
381         if(uname) passOK = oilsAuthVerifyPassword( ctx, userObj, uname, password );
382         else if (barcode) 
383                 passOK = oilsAuthVerifyPassword( ctx, userObj, barcode, password );
384
385         if( passOK < 0 ) {
386                 free(barcode);
387                 return passOK;
388         }
389
390         /* first see if their account is inactive */
391         char* active = oilsFMGetString(userObj, "active");
392         if( !oilsUtilsIsDBTrue(active) ) {
393                 response = oilsNewEvent(OSRF_LOG_MARK, "PATRON_INACTIVE");
394                 osrfAppRespondComplete( ctx, oilsEventToJSON(response) ); 
395                 oilsEventFree(response);
396                 free(barcode);
397                 free(active);
398                 return 0;
399         }
400         free(active);
401
402         /* then see if the barcode they used is active */
403         if( barcode && (response = oilsAuthCheckCard( ctx, userObj, barcode )) ) {
404                 osrfAppRespondComplete( ctx, oilsEventToJSON(response) ); 
405                 oilsEventFree(response);
406                 free(barcode);
407                 return 0;
408         }
409
410
411         /* check to see if the user is even allowed to login */
412         if( oilsAuthCheckLoginPerm( ctx, userObj, type ) == -1 ) {
413                 jsonObjectFree(userObj);
414                 free(barcode);
415                 return 0;
416         }
417         
418
419         /* if a workstation is defined, flesh the user with the workstation info */
420         if( workstation != NULL ) {
421                 osrfLogDebug(OSRF_LOG_MARK, "Workstation is %s", workstation);
422                 response = oilsAuthVerifyWorkstation( ctx, userObj, workstation );
423                 if(response) {
424                         jsonObjectFree(userObj);
425                         osrfAppRespondComplete( ctx, oilsEventToJSON(response) ); 
426                         oilsEventFree(response);
427                         free(barcode);
428                         return 0;
429                 }
430
431         } else {
432                 /* otherwise, use the home org as the workstation org on the user */
433                 char* orgid = oilsFMGetString(userObj, "home_ou");
434                 oilsFMSetString(userObj, "ws_ou", orgid);
435                 free(orgid);
436         }
437
438         if( passOK ) {
439                 response = oilsAuthHandleLoginOK( userObj, uname, type, orgloc );
440
441         } else {
442                 response = oilsNewEvent( OSRF_LOG_MARK, OILS_EVENT_AUTH_FAILED );
443                 osrfLogInfo(OSRF_LOG_MARK,  "Login failed for for %s", uname );
444         }
445
446         jsonObjectFree(userObj);
447         osrfAppRespondComplete( ctx, oilsEventToJSON(response) ); 
448         oilsEventFree(response);
449         free(barcode);
450
451         return 0;
452 }
453
454
455
456 int oilsAuthSessionDelete( osrfMethodContext* ctx ) {
457         OSRF_METHOD_VERIFY_CONTEXT(ctx); 
458
459         char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0) );
460         jsonObject* resp = NULL;
461
462         if( authToken ) {
463                 osrfLogDebug(OSRF_LOG_MARK, "Removing auth session: %s", authToken );
464                 char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken ); /**/
465                 osrfCacheRemove(key);
466                 resp = jsonNewObject(authToken); /**/
467                 free(key);
468         }
469
470         osrfAppRespondComplete( ctx, resp );
471         jsonObjectFree(resp);
472         return 0;
473 }
474
475 /** Resets the auth login timeout
476  * @return The event object, OILS_EVENT_SUCCESS, or OILS_EVENT_NO_SESSION
477  */
478 oilsEvent*  _oilsAuthResetTimeout( char* authToken ) {
479         if(!authToken) return NULL;
480
481         oilsEvent* evt = NULL;
482         double timeout;
483
484         osrfLogDebug(OSRF_LOG_MARK, "Resetting auth timeout for session %s", authToken);
485         char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken ); 
486         jsonObject* cacheObj = osrfCacheGetObject( key ); 
487
488         if(!cacheObj) {
489                 osrfLogError(OSRF_LOG_MARK, "No user in the cache exists with key %s", key);
490                 evt = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_NO_SESSION);
491
492         } else {
493
494                 timeout = jsonObjectGetNumber( jsonObjectGetKey( cacheObj, "authtime"));
495                 osrfCacheSetExpire( timeout, key );
496                 jsonObject* payload = jsonNewNumberObject(timeout);
497                 evt = oilsNewEvent2(OSRF_LOG_MARK, OILS_EVENT_SUCCESS, payload);
498                 jsonObjectFree(payload);
499                 jsonObjectFree(cacheObj);
500         }
501
502         free(key);
503         return evt;
504 }
505
506 int oilsAuthResetTimeout( osrfMethodContext* ctx ) {
507         OSRF_METHOD_VERIFY_CONTEXT(ctx); 
508         char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0));
509         oilsEvent* evt = _oilsAuthResetTimeout(authToken);
510         osrfAppRespondComplete( ctx, oilsEventToJSON(evt) );
511         oilsEventFree(evt);
512         return 0;
513 }
514
515
516 int oilsAuthSessionRetrieve( osrfMethodContext* ctx ) {
517         OSRF_METHOD_VERIFY_CONTEXT(ctx); 
518
519         char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0));
520         jsonObject* cacheObj = NULL;
521         oilsEvent* evt = NULL;
522
523         if( authToken ){
524
525                 evt = _oilsAuthResetTimeout(authToken);
526
527                 if( evt && strcmp(evt->event, OILS_EVENT_SUCCESS) ) {
528                         osrfAppRespondComplete( ctx, oilsEventToJSON(evt) );
529                         oilsEventFree(evt);
530
531                 } else {
532
533                         osrfLogDebug(OSRF_LOG_MARK, "Retrieving auth session: %s", authToken);
534                         char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken ); 
535                         cacheObj = osrfCacheGetObject( key ); 
536                         if(cacheObj) {
537                                 osrfAppRespondComplete( ctx, jsonObjectGetKey( cacheObj, "userobj"));
538                                 jsonObjectFree(cacheObj);
539                         } else {
540                                 oilsEvent* evt = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_NO_SESSION);
541                                 osrfAppRespondComplete( ctx, oilsEventToJSON(evt) ); /* should be event.. */
542                                 oilsEventFree(evt);
543                         }
544                         free(key);
545                 }
546
547         } else {
548
549                 evt = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_NO_SESSION);
550                 osrfAppRespondComplete( ctx, oilsEventToJSON(evt) );
551                 oilsEventFree(evt);
552         }
553
554         return 0;
555 }
556
557
558