]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/c-apps/oils_auth_internal.c
LP#1468422 Login permission checks are global
[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         "oilsAutInternalCreateSession",
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         "oilsAutInternalValidate",
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/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/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/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/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 oilsAutInternalCreateSession(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* org_unit    = jsonObjectGetString(jsonObjectGetKeyConst(args, "org_unit"));
282     const char* login_type  = jsonObjectGetString(jsonObjectGetKeyConst(args, "login_type"));
283     const char* workstation = jsonObjectGetString(jsonObjectGetKeyConst(args, "workstation"));
284
285     if ( !(user_id && login_type && org_unit) ) {
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 = oilsUtilsCStoreReq(
295         "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         if (response) {
307             jsonObjectFree(userObj);
308             osrfAppRespondComplete(ctx, oilsEventToJSON(response));
309             oilsEventFree(response);
310             return 0;
311         }
312
313     } else {
314         // Otherwise, use the home org as the workstation org on the user
315         char* orgid = oilsFMGetString(userObj, "home_ou");
316         oilsFMSetString(userObj, "ws_ou", orgid);
317         free(orgid);
318     }
319
320     // determine the auth/cache timeout
321     long timeout = oilsAuthGetTimeout(userObj, login_type, atoi(org_unit));
322
323     char* string = va_list_to_string("%d.%ld.%ld", 
324         (long) getpid(), time(NULL), oilsFMGetObjectId(userObj));
325     char* authToken = md5sum(string);
326     char* authKey = va_list_to_string(
327         "%s%s", OILS_AUTH_CACHE_PRFX, authToken);
328
329     oilsFMSetString(userObj, "passwd", "");
330     jsonObject* cacheObj = jsonParseFmt("{\"authtime\": %ld}", timeout);
331     jsonObjectSetKey(cacheObj, "userobj", jsonObjectClone(userObj));
332
333     if( !strcmp(login_type, OILS_AUTH_PERSIST)) {
334         // Add entries for endtime and reset_interval, so that we can gracefully
335         // extend the session a bit if the user is active toward the end of the 
336         // timeout originally specified.
337         time_t endtime = time( NULL ) + timeout;
338         jsonObjectSetKey(cacheObj, "endtime", 
339             jsonNewNumberObject( (double) endtime ));
340
341         // Reset interval is hard-coded for now, but if we ever want to make it
342         // configurable, this is the place to do it:
343         jsonObjectSetKey(cacheObj, "reset_interval",
344             jsonNewNumberObject( (double) DEFAULT_RESET_INTERVAL));
345     }
346
347     osrfCachePutObject(authKey, cacheObj, (time_t) timeout);
348     jsonObjectFree(cacheObj);
349     jsonObject* payload = jsonParseFmt(
350         "{\"authtoken\": \"%s\", \"authtime\": %ld}", authToken, timeout);
351
352     response = oilsNewEvent2(OSRF_LOG_MARK, OILS_EVENT_SUCCESS, payload);
353     free(string); free(authToken); free(authKey);
354     jsonObjectFree(payload);
355
356     jsonObjectFree(userObj);
357     osrfAppRespondComplete(ctx, oilsEventToJSON(response));
358     oilsEventFree(response);
359
360     return 0;
361 }
362
363
364 int oilsAutInternalValidate(osrfMethodContext* ctx) {
365     OSRF_METHOD_VERIFY_CONTEXT(ctx);
366
367     const jsonObject* args  = jsonObjectGetIndex(ctx->params, 0);
368
369     const char* user_id     = jsonObjectGetString(jsonObjectGetKeyConst(args, "user_id"));
370     const char* barcode     = jsonObjectGetString(jsonObjectGetKeyConst(args, "barcode"));
371     const char* org_unit    = jsonObjectGetString(jsonObjectGetKeyConst(args, "org_unit"));
372     const char* login_type  = jsonObjectGetString(jsonObjectGetKeyConst(args, "login_type"));
373
374     if ( !(user_id && login_type && org_unit) ) {
375         return osrfAppRequestRespondException( ctx->session, ctx->request,
376             "Missing parameters for method: %s", ctx->method->name );
377     }
378
379     oilsEvent* response = NULL;
380     jsonObject *userObj = NULL, *params = NULL;
381     char* tmp_str = NULL;
382     int user_exists = 0, user_active = 0, 
383         user_barred = 0, user_deleted = 0;
384
385     // Confirm user exists, active=true, barred=false, deleted=false
386     params = jsonNewNumberStringObject(user_id);
387     userObj = oilsUtilsCStoreReq(
388         "open-ils.cstore.direct.actor.user.retrieve", params);
389     jsonObjectFree(params);
390
391     if (userObj && userObj->type != JSON_NULL) {
392         user_exists = 1;
393
394         tmp_str = oilsFMGetString(userObj, "active");
395         user_active = oilsUtilsIsDBTrue(tmp_str);
396         free(tmp_str);
397
398         tmp_str = oilsFMGetString(userObj, "barred");
399         user_barred = oilsUtilsIsDBTrue(tmp_str);
400         free(tmp_str);
401
402         tmp_str = oilsFMGetString(userObj, "deleted");
403         user_deleted = oilsUtilsIsDBTrue(tmp_str);
404         free(tmp_str);
405     }
406
407     if (!user_exists || user_barred || user_deleted) {
408         response = oilsNewEvent(OILS_LOG_MARK_SAFE, OILS_EVENT_AUTH_FAILED);
409     }
410
411     if (!response && !user_active) {
412         // In some cases, it's useful for the caller to know if the
413         // patron was unable to login becuase the account is inactive.
414         // Return a specific event for this.
415         response = oilsNewEvent(OILS_LOG_MARK_SAFE, "PATRON_INACTIVE");
416     }
417
418     if (!response && barcode) {
419         // Caller provided a barcode.  Ensure it exists and is active.
420
421         int card_ok = 0;
422         params = jsonParseFmt("{\"barcode\":\"%s\"}", barcode);
423         jsonObject* card = oilsUtilsCStoreReq(
424             "open-ils.cstore.direct.actor.card.search", params);
425         jsonObjectFree(params);
426
427         if (card && card->type != JSON_NULL) {
428             tmp_str = oilsFMGetString(card, "active");
429             card_ok = oilsUtilsIsDBTrue(tmp_str);
430             free(tmp_str);
431         }
432
433         jsonObjectFree(card); // card=NULL OK here.
434
435         if (!card_ok) {
436             response = oilsNewEvent(
437                 OILS_LOG_MARK_SAFE, "PATRON_CARD_INACTIVE");
438         }
439     }
440
441     if (!response) { // Still OK
442         // Confirm user has permission to login w/ the requested type.
443         response = oilsAuthCheckLoginPerm(
444             ctx, atoi(user_id), atoi(org_unit), login_type);
445     }
446
447
448     if (!response) {
449         // No tests failed.  Return SUCCESS.
450         response = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_SUCCESS);
451     }
452
453
454     jsonObjectFree(userObj); // userObj=NULL OK here.
455     osrfAppRespondComplete(ctx, oilsEventToJSON(response));
456     oilsEventFree(response);
457
458     return 0;
459 }
460