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