]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/c-apps/oils_auth_internal.c
LP#1468422 auth-internal validate API
[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         "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     char* perms[1];
236
237     if (!strcasecmp(type, OILS_AUTH_OPAC)) {
238         perms[0] = "OPAC_LOGIN";
239
240     } else if (!strcasecmp(type, OILS_AUTH_STAFF)) {
241         perms[0] = "STAFF_LOGIN";
242
243     } else if (!strcasecmp(type, OILS_AUTH_TEMP)) {
244         perms[0] = "STAFF_LOGIN";
245
246     } else if (!strcasecmp(type, OILS_AUTH_PERSIST)) {
247         perms[0] = "PERSISTENT_LOGIN";
248     }
249
250     return oilsUtilsCheckPerms(user_id, org_id, perms, 1);
251 }
252
253
254
255 /**
256     @brief Implement the session create method
257     @param ctx The method context.
258     @return -1 upon error; zero if successful, and if a STATUS message has 
259     been sent to the client to indicate completion; a positive integer if 
260     successful but no such STATUS message has been sent.
261
262     Method parameters:
263     - a hash with some combination of the following elements:
264         - "user_id"     -- actor.usr (au) ID for the user to cache.
265         - "org_unit"    -- actor.org_unit (aou) ID representing the physical 
266                            location / context used for timeout, etc. settings.
267         - "login_type"  -- login type (opac, staff, temp, persist)
268         - "workstation" -- workstation name
269
270 */
271 int oilsAutInternalCreateSession(osrfMethodContext* ctx) {
272     OSRF_METHOD_VERIFY_CONTEXT(ctx);
273
274     const jsonObject* args  = jsonObjectGetIndex(ctx->params, 0);
275
276     const char* user_id     = jsonObjectGetString(jsonObjectGetKeyConst(args, "user_id"));
277     const char* org_unit    = jsonObjectGetString(jsonObjectGetKeyConst(args, "org_unit"));
278     const char* login_type  = jsonObjectGetString(jsonObjectGetKeyConst(args, "login_type"));
279     const char* workstation = jsonObjectGetString(jsonObjectGetKeyConst(args, "workstation"));
280
281     if ( !(user_id && login_type && org_unit) ) {
282         return osrfAppRequestRespondException( ctx->session, ctx->request,
283             "Missing parameters for method: %s", ctx->method->name );
284     }
285
286     oilsEvent* response = NULL;
287
288     // fetch the user object
289     jsonObject* idParam = jsonNewNumberStringObject(user_id);
290     jsonObject* userObj = oilsUtilsCStoreReq(
291         "open-ils.cstore.direct.actor.user.retrieve", idParam);
292     jsonObjectFree(idParam);
293
294     if (!userObj) {
295         return osrfAppRequestRespondException(ctx->session, 
296             ctx->request, "No user found with ID %s", user_id);
297     }
298
299     // If a workstation is defined, add the workstation info
300     if (workstation) {
301         response = oilsAuthVerifyWorkstation(ctx, userObj, workstation);
302         if (response) {
303             jsonObjectFree(userObj);
304             osrfAppRespondComplete(ctx, oilsEventToJSON(response));
305             oilsEventFree(response);
306             return 0;
307         }
308
309     } else {
310         // Otherwise, use the home org as the workstation org on the user
311         char* orgid = oilsFMGetString(userObj, "home_ou");
312         oilsFMSetString(userObj, "ws_ou", orgid);
313         free(orgid);
314     }
315
316     // determine the auth/cache timeout
317     long timeout = oilsAuthGetTimeout(userObj, login_type, atoi(org_unit));
318
319     char* string = va_list_to_string("%d.%ld.%ld", 
320         (long) getpid(), time(NULL), oilsFMGetObjectId(userObj));
321     char* authToken = md5sum(string);
322     char* authKey = va_list_to_string(
323         "%s%s", OILS_AUTH_CACHE_PRFX, authToken);
324
325     oilsFMSetString(userObj, "passwd", "");
326     jsonObject* cacheObj = jsonParseFmt("{\"authtime\": %ld}", timeout);
327     jsonObjectSetKey(cacheObj, "userobj", jsonObjectClone(userObj));
328
329     if( !strcmp(login_type, OILS_AUTH_PERSIST)) {
330         // Add entries for endtime and reset_interval, so that we can gracefully
331         // extend the session a bit if the user is active toward the end of the 
332         // timeout originally specified.
333         time_t endtime = time( NULL ) + timeout;
334         jsonObjectSetKey(cacheObj, "endtime", 
335             jsonNewNumberObject( (double) endtime ));
336
337         // Reset interval is hard-coded for now, but if we ever want to make it
338         // configurable, this is the place to do it:
339         jsonObjectSetKey(cacheObj, "reset_interval",
340             jsonNewNumberObject( (double) DEFAULT_RESET_INTERVAL));
341     }
342
343     osrfCachePutObject(authKey, cacheObj, (time_t) timeout);
344     jsonObjectFree(cacheObj);
345     jsonObject* payload = jsonParseFmt(
346         "{\"authtoken\": \"%s\", \"authtime\": %ld}", authToken, timeout);
347
348     response = oilsNewEvent2(OSRF_LOG_MARK, OILS_EVENT_SUCCESS, payload);
349     free(string); free(authToken); free(authKey);
350     jsonObjectFree(payload);
351
352     jsonObjectFree(userObj);
353     osrfAppRespondComplete(ctx, oilsEventToJSON(response));
354     oilsEventFree(response);
355
356     return 0;
357 }
358
359
360 int oilsAutInternalValidate(osrfMethodContext* ctx) {
361     OSRF_METHOD_VERIFY_CONTEXT(ctx);
362
363     const jsonObject* args  = jsonObjectGetIndex(ctx->params, 0);
364
365     const char* user_id     = jsonObjectGetString(jsonObjectGetKeyConst(args, "user_id"));
366     const char* barcode     = jsonObjectGetString(jsonObjectGetKeyConst(args, "barcode"));
367     const char* org_unit    = jsonObjectGetString(jsonObjectGetKeyConst(args, "org_unit"));
368     const char* login_type  = jsonObjectGetString(jsonObjectGetKeyConst(args, "login_type"));
369
370     if ( !(user_id && login_type && org_unit) ) {
371         return osrfAppRequestRespondException( ctx->session, ctx->request,
372             "Missing parameters for method: %s", ctx->method->name );
373     }
374
375     oilsEvent* response = NULL;
376     jsonObject *userObj = NULL, *params = NULL;
377     char* tmp_str = NULL;
378     int user_exists = 0, user_active = 0, 
379         user_barred = 0, user_deleted = 0;
380
381     // Confirm user exists, active=true, barred=false, deleted=false
382     params = jsonNewNumberStringObject(user_id);
383     userObj = oilsUtilsCStoreReq(
384         "open-ils.cstore.direct.actor.user.retrieve", params);
385     jsonObjectFree(params);
386
387     if (userObj && userObj->type != JSON_NULL) {
388         user_exists = 1;
389
390         tmp_str = oilsFMGetString(userObj, "active");
391         user_active = oilsUtilsIsDBTrue(tmp_str);
392         free(tmp_str);
393
394         tmp_str = oilsFMGetString(userObj, "barred");
395         user_barred = oilsUtilsIsDBTrue(tmp_str);
396         free(tmp_str);
397
398         tmp_str = oilsFMGetString(userObj, "deleted");
399         user_deleted = oilsUtilsIsDBTrue(tmp_str);
400         free(tmp_str);
401     }
402
403     if (!user_exists || user_barred || user_deleted) {
404         response = oilsNewEvent(OILS_LOG_MARK_SAFE, OILS_EVENT_AUTH_FAILED);
405     }
406
407     if (!response && !user_active) {
408         // In some cases, it's useful for the caller to know if the
409         // patron was unable to login becuase the account is inactive.
410         // Return a specific event for this.
411         response = oilsNewEvent(OILS_LOG_MARK_SAFE, "PATRON_INACTIVE");
412     }
413
414     if (!response && barcode) {
415         // Caller provided a barcode.  Ensure it exists and is active.
416
417         int card_ok = 0;
418         params = jsonParseFmt("{\"barcode\":\"%s\"}", barcode);
419         jsonObject* card = oilsUtilsCStoreReq(
420             "open-ils.cstore.direct.actor.card.search", params);
421         jsonObjectFree(params);
422
423         if (card && card->type != JSON_NULL) {
424             tmp_str = oilsFMGetString(card, "active");
425             card_ok = oilsUtilsIsDBTrue(tmp_str);
426             free(tmp_str);
427         }
428
429         jsonObjectFree(card); // card=NULL OK here.
430
431         if (!card_ok) {
432             response = oilsNewEvent(
433                 OILS_LOG_MARK_SAFE, "PATRON_CARD_INACTIVE");
434         }
435     }
436
437     if (!response) { // Still OK
438         // Confirm user has permission to login w/ the requested type.
439         response = oilsAuthCheckLoginPerm(
440             ctx, atoi(user_id), atoi(org_unit), login_type);
441     }
442
443
444     if (!response) {
445         // No tests failed.  Return SUCCESS.
446         response = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_SUCCESS);
447     }
448
449
450     jsonObjectFree(userObj); // userObj=NULL OK here.
451     osrfAppRespondComplete(ctx, oilsEventToJSON(response));
452     oilsEventFree(response);
453
454     return 0;
455 }
456