]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/c-apps/oils_auth_internal.c
LP1818288 WS Option to pre-fetch record holds
[working/Evergreen.git] / Open-ILS / src / c-apps / oils_auth_internal.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 #define OILS_AUTH_COUNT_SFFX "_count"
12
13 #define MODULENAME "open-ils.auth_internal"
14
15 #define OILS_AUTH_OPAC "opac"
16 #define OILS_AUTH_STAFF "staff"
17 #define OILS_AUTH_TEMP "temp"
18 #define OILS_AUTH_PERSIST "persist"
19
20 // Default time for extending a persistent session: ten minutes
21 #define DEFAULT_RESET_INTERVAL 10 * 60
22
23 int safe_line = __LINE__;
24 #define OILS_LOG_MARK_SAFE __FILE__,safe_line
25
26 int osrfAppInitialize();
27 int osrfAppChildInit();
28
29 static long _oilsAuthOPACTimeout = 0;
30 static long _oilsAuthStaffTimeout = 0;
31 static long _oilsAuthOverrideTimeout = 0;
32 static long _oilsAuthPersistTimeout = 0;
33
34 /**
35     @brief Initialize the application by registering functions for method calls.
36     @return Zero on success, 1 on error.
37 */
38 int osrfAppInitialize() {
39
40     osrfLogInfo(OSRF_LOG_MARK, "Initializing Auth Internal Server...");
41
42     /* load and parse the IDL */
43     /* return non-zero to indicate error */
44     if (!oilsInitIDL(NULL)) return 1; 
45
46     osrfAppRegisterMethod(
47         MODULENAME,
48         "open-ils.auth_internal.session.create",
49         "oilsAuthInternalCreateSession",
50         "Adds a user to the authentication cache to indicate "
51         "the user is authenticated", 1, 0 
52     );
53
54     osrfAppRegisterMethod(
55         MODULENAME,
56         "open-ils.auth_internal.user.validate",
57         "oilsAuthInternalValidate",
58         "Determines whether a user should be allowed to login.  " 
59         "Returns SUCCESS oilsEvent when the user is valid, otherwise "
60         "returns a non-SUCCESS oilsEvent object", 1, 0
61     );
62
63     return 0;
64 }
65
66 /**
67     @brief Dummy placeholder for initializing a server drone.
68
69     There is nothing to do, so do nothing.
70 */
71 int osrfAppChildInit() {
72     return 0;
73 }
74
75
76 /**
77     @brief Determine the login timeout.
78     @param userObj Pointer to an object describing the user.
79     @param type Pointer to one of four possible character strings identifying the login type.
80     @param orgloc Org unit to use for settings lookups (negative or zero means unspecified)
81     @return The length of the timeout, in seconds.
82
83     The default timeout value comes from the configuration file, and
84     depends on the login type.
85
86     The default may be overridden by a corresponding org unit setting.
87     The @a orgloc parameter says what org unit to use for the lookup.
88     If @a orgloc <= 0, or if the lookup for @a orgloc yields no result,
89     we look up the setting for the user's home org unit instead (except
90     that if it's the same as @a orgloc we don't bother repeating the
91     lookup).
92
93     Whether defined in the config file or in an org unit setting, a
94     timeout value may be expressed as a raw number (i.e. all digits,
95     possibly with leading and/or trailing white space) or as an interval
96     string to be translated into seconds by PostgreSQL.
97 */
98 static long oilsAuthGetTimeout(
99     const jsonObject* userObj, const char* type, int orgloc) {
100
101     if(!_oilsAuthOPACTimeout) { /* Load the default timeouts */
102
103         jsonObject* value_obj;
104
105         value_obj = osrf_settings_host_value_object(
106             "/apps/open-ils.auth_internal/app_settings/default_timeout/opac" );
107         _oilsAuthOPACTimeout = oilsUtilsIntervalToSeconds( jsonObjectGetString( value_obj ));
108         jsonObjectFree(value_obj);
109         if( -1 == _oilsAuthOPACTimeout ) {
110             osrfLogWarning( OSRF_LOG_MARK, "Invalid default timeout for OPAC logins" );
111             _oilsAuthOPACTimeout = 0;
112         }
113
114         value_obj = osrf_settings_host_value_object(
115             "/apps/open-ils.auth_internal/app_settings/default_timeout/staff" );
116         _oilsAuthStaffTimeout = oilsUtilsIntervalToSeconds( jsonObjectGetString( value_obj ));
117         jsonObjectFree(value_obj);
118         if( -1 == _oilsAuthStaffTimeout ) {
119             osrfLogWarning( OSRF_LOG_MARK, "Invalid default timeout for staff logins" );
120             _oilsAuthStaffTimeout = 0;
121         }
122
123         value_obj = osrf_settings_host_value_object(
124             "/apps/open-ils.auth_internal/app_settings/default_timeout/temp" );
125         _oilsAuthOverrideTimeout = oilsUtilsIntervalToSeconds( jsonObjectGetString( value_obj ));
126         jsonObjectFree(value_obj);
127         if( -1 == _oilsAuthOverrideTimeout ) {
128             osrfLogWarning( OSRF_LOG_MARK, "Invalid default timeout for temp logins" );
129             _oilsAuthOverrideTimeout = 0;
130         }
131
132         value_obj = osrf_settings_host_value_object(
133             "/apps/open-ils.auth_internal/app_settings/default_timeout/persist" );
134         _oilsAuthPersistTimeout = oilsUtilsIntervalToSeconds( jsonObjectGetString( value_obj ));
135         jsonObjectFree(value_obj);
136         if( -1 == _oilsAuthPersistTimeout ) {
137             osrfLogWarning( OSRF_LOG_MARK, "Invalid default timeout for persist logins" );
138             _oilsAuthPersistTimeout = 0;
139         }
140
141         osrfLogInfo(OSRF_LOG_MARK, "Set default auth timeouts: "
142             "opac => %ld : staff => %ld : temp => %ld : persist => %ld",
143             _oilsAuthOPACTimeout, _oilsAuthStaffTimeout,
144             _oilsAuthOverrideTimeout, _oilsAuthPersistTimeout );
145     }
146
147     int home_ou = (int) jsonObjectGetNumber( oilsFMGetObject( userObj, "home_ou" ));
148     if(orgloc < 1)
149         orgloc = home_ou;
150
151     char* setting = NULL;
152     long default_timeout = 0;
153
154     if( !strcmp( type, OILS_AUTH_OPAC )) {
155         setting = OILS_ORG_SETTING_OPAC_TIMEOUT;
156         default_timeout = _oilsAuthOPACTimeout;
157     } else if( !strcmp( type, OILS_AUTH_STAFF )) {
158         setting = OILS_ORG_SETTING_STAFF_TIMEOUT;
159         default_timeout = _oilsAuthStaffTimeout;
160     } else if( !strcmp( type, OILS_AUTH_TEMP )) {
161         setting = OILS_ORG_SETTING_TEMP_TIMEOUT;
162         default_timeout = _oilsAuthOverrideTimeout;
163     } else if( !strcmp( type, OILS_AUTH_PERSIST )) {
164         setting = OILS_ORG_SETTING_PERSIST_TIMEOUT;
165         default_timeout = _oilsAuthPersistTimeout;
166     }
167
168     // Get the org unit setting, if there is one.
169     char* timeout = oilsUtilsFetchOrgSetting( orgloc, setting );
170     if(!timeout) {
171         if( orgloc != home_ou ) {
172             osrfLogDebug(OSRF_LOG_MARK, "Auth timeout not defined for org %d, "
173                 "trying home_ou %d", orgloc, home_ou );
174             timeout = oilsUtilsFetchOrgSetting( home_ou, setting );
175         }
176     }
177
178     if(!timeout)
179         return default_timeout;   // No override from org unit setting
180
181     // Translate the org unit setting to a number
182     long t;
183     if( !*timeout ) {
184         osrfLogWarning( OSRF_LOG_MARK,
185             "Timeout org unit setting is an empty string for %s login; using default",
186             timeout, type );
187         t = default_timeout;
188     } else {
189         // Treat timeout string as an interval, and convert it to seconds
190         t = oilsUtilsIntervalToSeconds( timeout );
191         if( -1 == t ) {
192             // Unable to convert; possibly an invalid interval string
193             osrfLogError( OSRF_LOG_MARK,
194                 "Unable to convert timeout interval \"%s\" for %s login; using default",
195                 timeout, type );
196             t = default_timeout;
197         }
198     }
199
200     free(timeout);
201     return t;
202 }
203
204 /**
205  * Verify workstation exists and stuff it into the user object to be cached
206  */
207 static oilsEvent* oilsAuthVerifyWorkstation(
208         const osrfMethodContext* ctx, jsonObject* userObj, const char* ws ) {
209
210     jsonObject* workstation = oilsUtilsFetchWorkstationByName(ws);
211
212     if(!workstation || workstation->type == JSON_NULL) {
213         jsonObjectFree(workstation);
214         return oilsNewEvent(OSRF_LOG_MARK, "WORKSTATION_NOT_FOUND");
215     }
216
217     long wsid = oilsFMGetObjectId(workstation);
218     LONG_TO_STRING(wsid);
219     char* orgid = oilsFMGetString(workstation, "owning_lib");
220     oilsFMSetString(userObj, "wsid", LONGSTR);
221     oilsFMSetString(userObj, "ws_ou", orgid);
222     free(orgid);
223     jsonObjectFree(workstation);
224     return NULL;
225 }
226
227 /**
228     Verifies that the user has permission to login with the given type.  
229     Caller is responsible for freeing returned oilsEvent.
230     @return oilsEvent* if the permission check failed, NULL otherwise.
231 */
232 static oilsEvent* oilsAuthCheckLoginPerm(osrfMethodContext* ctx, 
233     int user_id, int org_id, const char* type ) {
234
235     // For backwards compatibility, check all login permissions 
236     // using the root org unit as the context org unit.
237     org_id = -1;
238
239     char* perms[1];
240
241     if (!strcasecmp(type, OILS_AUTH_OPAC)) {
242         perms[0] = "OPAC_LOGIN";
243
244     } else if (!strcasecmp(type, OILS_AUTH_STAFF)) {
245         perms[0] = "STAFF_LOGIN";
246
247     } else if (!strcasecmp(type, OILS_AUTH_TEMP)) {
248         perms[0] = "STAFF_LOGIN";
249
250     } else if (!strcasecmp(type, OILS_AUTH_PERSIST)) {
251         perms[0] = "PERSISTENT_LOGIN";
252     }
253
254     return oilsUtilsCheckPerms(user_id, org_id, perms, 1);
255 }
256
257
258
259 /**
260     @brief Implement the session create method
261     @param ctx The method context.
262     @return -1 upon error; zero if successful, and if a STATUS message has 
263     been sent to the client to indicate completion; a positive integer if 
264     successful but no such STATUS message has been sent.
265
266     Method parameters:
267     - a hash with some combination of the following elements:
268         - "user_id"     -- actor.usr (au) ID for the user to cache.
269         - "org_unit"    -- actor.org_unit (aou) ID representing the physical 
270                            location / context used for timeout, etc. settings.
271         - "login_type"  -- login type (opac, staff, temp, persist)
272         - "workstation" -- workstation name
273
274 */
275 int oilsAuthInternalCreateSession(osrfMethodContext* ctx) {
276     OSRF_METHOD_VERIFY_CONTEXT(ctx);
277
278     const jsonObject* args  = jsonObjectGetIndex(ctx->params, 0);
279
280     const char* user_id     = jsonObjectGetString(jsonObjectGetKeyConst(args, "user_id"));
281     const char* login_type  = jsonObjectGetString(jsonObjectGetKeyConst(args, "login_type"));
282     const char* workstation = jsonObjectGetString(jsonObjectGetKeyConst(args, "workstation"));
283     int org_unit            = jsonObjectGetNumber(jsonObjectGetKeyConst(args, "org_unit"));
284
285     if ( !(user_id && login_type) ) {
286         return osrfAppRequestRespondException( ctx->session, ctx->request,
287             "Missing parameters for method: %s", ctx->method->name );
288     }
289
290     oilsEvent* response = NULL;
291
292     // fetch the user object
293     jsonObject* idParam = jsonNewNumberStringObject(user_id);
294     jsonObject* userObj = oilsUtilsCStoreReqCtx(
295         ctx, "open-ils.cstore.direct.actor.user.retrieve", idParam);
296     jsonObjectFree(idParam);
297
298     if (!userObj) {
299         return osrfAppRequestRespondException(ctx->session, 
300             ctx->request, "No user found with ID %s", user_id);
301     }
302
303     // If a workstation is defined, add the workstation info
304     if (workstation) {
305         response = oilsAuthVerifyWorkstation(ctx, userObj, workstation);
306
307         if (response) { // invalid workstation.
308             jsonObjectFree(userObj);
309             osrfAppRespondComplete(ctx, oilsEventToJSON(response));
310             oilsEventFree(response);
311             return 0;
312
313         } else { // workstation OK.  
314
315             // The worksation org unit supersedes any org unit value 
316             // provided via the API.  oilsAuthVerifyWorkstation() sets the 
317             // ws_ou value to the WS owning lib.  A value is guaranteed.
318             org_unit = atoi(oilsFMGetStringConst(userObj, "ws_ou"));
319         }
320
321     } else { // no workstation
322
323         // For backwards compatibility, when no workstation is provided, use 
324         // the users's home org as its workstation org unit, regardless of 
325         // any API-level org unit value provided.
326         const char* orgid = oilsFMGetStringConst(userObj, "home_ou");
327         oilsFMSetString(userObj, "ws_ou", orgid);
328
329         // The context org unit defaults to the user's home library when
330         // no workstation is used and no API-level value is provided.
331         if (org_unit < 1) org_unit = atoi(orgid);
332     }
333
334     // determine the auth/cache timeout
335     long timeout = oilsAuthGetTimeout(userObj, login_type, org_unit);
336
337     char* string = va_list_to_string("%d.%ld.%ld", 
338         (long) getpid(), time(NULL), oilsFMGetObjectId(userObj));
339     char* authToken = md5sum(string);
340     char* authKey = va_list_to_string(
341         "%s%s", OILS_AUTH_CACHE_PRFX, authToken);
342
343     oilsFMSetString(userObj, "passwd", "");
344     jsonObject* cacheObj = jsonParseFmt("{\"authtime\": %ld}", timeout);
345     jsonObjectSetKey(cacheObj, "userobj", jsonObjectClone(userObj));
346
347     if( !strcmp(login_type, OILS_AUTH_PERSIST)) {
348         // Add entries for endtime and reset_interval, so that we can gracefully
349         // extend the session a bit if the user is active toward the end of the 
350         // timeout originally specified.
351         time_t endtime = time( NULL ) + timeout;
352         jsonObjectSetKey(cacheObj, "endtime", 
353             jsonNewNumberObject( (double) endtime ));
354
355         // Reset interval is hard-coded for now, but if we ever want to make it
356         // configurable, this is the place to do it:
357         jsonObjectSetKey(cacheObj, "reset_interval",
358             jsonNewNumberObject( (double) DEFAULT_RESET_INTERVAL));
359     }
360
361     osrfCachePutObject(authKey, cacheObj, (time_t) timeout);
362     jsonObjectFree(cacheObj);
363     jsonObject* payload = jsonParseFmt(
364         "{\"authtoken\": \"%s\", \"authtime\": %ld}", authToken, timeout);
365
366     response = oilsNewEvent2(OSRF_LOG_MARK, OILS_EVENT_SUCCESS, payload);
367     free(string); free(authToken); free(authKey);
368     jsonObjectFree(payload);
369
370     jsonObjectFree(userObj);
371     osrfAppRespondComplete(ctx, oilsEventToJSON(response));
372     oilsEventFree(response);
373
374     return 0;
375 }
376
377
378 int oilsAuthInternalValidate(osrfMethodContext* ctx) {
379     OSRF_METHOD_VERIFY_CONTEXT(ctx);
380
381     const jsonObject* args  = jsonObjectGetIndex(ctx->params, 0);
382
383     const char* user_id     = jsonObjectGetString(jsonObjectGetKeyConst(args, "user_id"));
384     const char* barcode     = jsonObjectGetString(jsonObjectGetKeyConst(args, "barcode"));
385     const char* login_type  = jsonObjectGetString(jsonObjectGetKeyConst(args, "login_type"));
386     int org_unit            = jsonObjectGetNumber(jsonObjectGetKeyConst(args, "org_unit"));
387
388     if ( !(user_id && login_type) ) {
389         return osrfAppRequestRespondException( ctx->session, ctx->request,
390             "Missing parameters for method: %s", ctx->method->name );
391     }
392
393     oilsEvent* response = NULL;
394     jsonObject *userObj = NULL, *params = NULL;
395     char* tmp_str = NULL;
396     int user_exists = 0, user_active = 0, 
397         user_barred = 0, user_deleted = 0;
398
399     // Confirm user exists, active=true, barred=false, deleted=false
400     params = jsonNewNumberStringObject(user_id);
401     userObj = oilsUtilsCStoreReqCtx(
402         ctx, "open-ils.cstore.direct.actor.user.retrieve", params);
403     jsonObjectFree(params);
404
405     if (userObj && userObj->type != JSON_NULL) {
406         user_exists = 1;
407
408         tmp_str = oilsFMGetString(userObj, "active");
409         user_active = oilsUtilsIsDBTrue(tmp_str);
410         free(tmp_str);
411
412         tmp_str = oilsFMGetString(userObj, "barred");
413         user_barred = oilsUtilsIsDBTrue(tmp_str);
414         free(tmp_str);
415
416         tmp_str = oilsFMGetString(userObj, "deleted");
417         user_deleted = oilsUtilsIsDBTrue(tmp_str);
418         free(tmp_str);
419     }
420
421     if (!user_exists || user_barred || user_deleted) {
422         response = oilsNewEvent(OILS_LOG_MARK_SAFE, OILS_EVENT_AUTH_FAILED);
423     }
424
425     if (!response && !user_active) {
426         // In some cases, it's useful for the caller to know if the
427         // patron was unable to login becuase the account is inactive.
428         // Return a specific event for this.
429         response = oilsNewEvent(OILS_LOG_MARK_SAFE, "PATRON_INACTIVE");
430     }
431
432     if (!response && barcode) {
433         // Caller provided a barcode.  Ensure it exists and is active.
434
435         int card_ok = 0;
436         params = jsonParseFmt("{\"barcode\":\"%s\"}", barcode);
437         jsonObject* card = oilsUtilsCStoreReqCtx(
438             ctx, "open-ils.cstore.direct.actor.card.search", params);
439         jsonObjectFree(params);
440
441         if (card && card->type != JSON_NULL) {
442             tmp_str = oilsFMGetString(card, "active");
443             card_ok = oilsUtilsIsDBTrue(tmp_str);
444             free(tmp_str);
445         }
446
447         jsonObjectFree(card); // card=NULL OK here.
448
449         if (!card_ok) {
450             response = oilsNewEvent(
451                 OILS_LOG_MARK_SAFE, "PATRON_CARD_INACTIVE");
452         }
453     }
454
455     // XXX: login permission checks are always global (see 
456     // oilsAuthCheckLoginPerm()).  No need to extract the 
457     // workstation org unit here.
458
459     if (!response) { // Still OK
460         // Confirm user has permission to login w/ the requested type.
461         response = oilsAuthCheckLoginPerm(
462             ctx, atoi(user_id), org_unit, login_type);
463     }
464
465
466     if (!response) {
467         // No tests failed.  Return SUCCESS.
468         response = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_SUCCESS);
469     }
470
471
472     jsonObjectFree(userObj); // userObj=NULL OK here.
473     osrfAppRespondComplete(ctx, oilsEventToJSON(response));
474     oilsEventFree(response);
475
476     return 0;
477 }
478