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"
10 #define OILS_AUTH_CACHE_PRFX "oils_auth_"
11 #define OILS_AUTH_COUNT_SFFX "_count"
13 #define MODULENAME "open-ils.auth_internal"
15 #define OILS_AUTH_OPAC "opac"
16 #define OILS_AUTH_STAFF "staff"
17 #define OILS_AUTH_TEMP "temp"
18 #define OILS_AUTH_PERSIST "persist"
20 // Default time for extending a persistent session: ten minutes
21 #define DEFAULT_RESET_INTERVAL 10 * 60
23 int safe_line = __LINE__;
24 #define OILS_LOG_MARK_SAFE __FILE__,safe_line
26 int osrfAppInitialize();
27 int osrfAppChildInit();
29 static long _oilsAuthOPACTimeout = 0;
30 static long _oilsAuthStaffTimeout = 0;
31 static long _oilsAuthOverrideTimeout = 0;
32 static long _oilsAuthPersistTimeout = 0;
35 @brief Initialize the application by registering functions for method calls.
36 @return Zero on success, 1 on error.
38 int osrfAppInitialize() {
40 osrfLogInfo(OSRF_LOG_MARK, "Initializing Auth Internal Server...");
42 /* load and parse the IDL */
43 /* return non-zero to indicate error */
44 if (!oilsInitIDL(NULL)) return 1;
46 osrfAppRegisterMethod(
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
54 osrfAppRegisterMethod(
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
67 @brief Dummy placeholder for initializing a server drone.
69 There is nothing to do, so do nothing.
71 int osrfAppChildInit() {
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.
83 The default timeout value comes from the configuration file, and
84 depends on the login type.
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
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.
98 static long oilsAuthGetTimeout(
99 const jsonObject* userObj, const char* type, int orgloc) {
101 if(!_oilsAuthOPACTimeout) { /* Load the default timeouts */
103 jsonObject* value_obj;
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;
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;
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;
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;
141 osrfLogInfo(OSRF_LOG_MARK, "Set default auth timeouts: "
142 "opac => %ld : staff => %ld : temp => %ld : persist => %ld",
143 _oilsAuthOPACTimeout, _oilsAuthStaffTimeout,
144 _oilsAuthOverrideTimeout, _oilsAuthPersistTimeout );
147 int home_ou = (int) jsonObjectGetNumber( oilsFMGetObject( userObj, "home_ou" ));
151 char* setting = NULL;
152 long default_timeout = 0;
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;
168 // Get the org unit setting, if there is one.
169 char* timeout = oilsUtilsFetchOrgSetting( orgloc, setting );
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 );
179 return default_timeout; // No override from org unit setting
181 // Translate the org unit setting to a number
184 osrfLogWarning( OSRF_LOG_MARK,
185 "Timeout org unit setting is an empty string for %s login; using default",
189 // Treat timeout string as an interval, and convert it to seconds
190 t = oilsUtilsIntervalToSeconds( timeout );
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",
205 * Verify workstation exists and stuff it into the user object to be cached
207 static oilsEvent* oilsAuthVerifyWorkstation(
208 const osrfMethodContext* ctx, jsonObject* userObj, const char* ws ) {
210 jsonObject* workstation = oilsUtilsFetchWorkstationByName(ws);
212 if(!workstation || workstation->type == JSON_NULL) {
213 jsonObjectFree(workstation);
214 return oilsNewEvent(OSRF_LOG_MARK, "WORKSTATION_NOT_FOUND");
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);
223 jsonObjectFree(workstation);
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.
232 static oilsEvent* oilsAuthCheckLoginPerm(osrfMethodContext* ctx,
233 int user_id, int org_id, const char* type ) {
235 // For backwards compatibility, check all login permissions
236 // using the root org unit as the context org unit.
241 if (!strcasecmp(type, OILS_AUTH_OPAC)) {
242 perms[0] = "OPAC_LOGIN";
244 } else if (!strcasecmp(type, OILS_AUTH_STAFF)) {
245 perms[0] = "STAFF_LOGIN";
247 } else if (!strcasecmp(type, OILS_AUTH_TEMP)) {
248 perms[0] = "STAFF_LOGIN";
250 } else if (!strcasecmp(type, OILS_AUTH_PERSIST)) {
251 perms[0] = "PERSISTENT_LOGIN";
254 return oilsUtilsCheckPerms(user_id, org_id, perms, 1);
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.
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
275 int oilsAutInternalCreateSession(osrfMethodContext* ctx) {
276 OSRF_METHOD_VERIFY_CONTEXT(ctx);
278 const jsonObject* args = jsonObjectGetIndex(ctx->params, 0);
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"));
285 if ( !(user_id && login_type && org_unit) ) {
286 return osrfAppRequestRespondException( ctx->session, ctx->request,
287 "Missing parameters for method: %s", ctx->method->name );
290 oilsEvent* response = NULL;
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);
299 return osrfAppRequestRespondException(ctx->session,
300 ctx->request, "No user found with ID %s", user_id);
303 // If a workstation is defined, add the workstation info
305 response = oilsAuthVerifyWorkstation(ctx, userObj, workstation);
307 jsonObjectFree(userObj);
308 osrfAppRespondComplete(ctx, oilsEventToJSON(response));
309 oilsEventFree(response);
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);
320 // determine the auth/cache timeout
321 long timeout = oilsAuthGetTimeout(userObj, login_type, atoi(org_unit));
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);
329 oilsFMSetString(userObj, "passwd", "");
330 jsonObject* cacheObj = jsonParseFmt("{\"authtime\": %ld}", timeout);
331 jsonObjectSetKey(cacheObj, "userobj", jsonObjectClone(userObj));
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 ));
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));
347 osrfCachePutObject(authKey, cacheObj, (time_t) timeout);
348 jsonObjectFree(cacheObj);
349 jsonObject* payload = jsonParseFmt(
350 "{\"authtoken\": \"%s\", \"authtime\": %ld}", authToken, timeout);
352 response = oilsNewEvent2(OSRF_LOG_MARK, OILS_EVENT_SUCCESS, payload);
353 free(string); free(authToken); free(authKey);
354 jsonObjectFree(payload);
356 jsonObjectFree(userObj);
357 osrfAppRespondComplete(ctx, oilsEventToJSON(response));
358 oilsEventFree(response);
364 int oilsAutInternalValidate(osrfMethodContext* ctx) {
365 OSRF_METHOD_VERIFY_CONTEXT(ctx);
367 const jsonObject* args = jsonObjectGetIndex(ctx->params, 0);
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"));
374 if ( !(user_id && login_type && org_unit) ) {
375 return osrfAppRequestRespondException( ctx->session, ctx->request,
376 "Missing parameters for method: %s", ctx->method->name );
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;
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);
391 if (userObj && userObj->type != JSON_NULL) {
394 tmp_str = oilsFMGetString(userObj, "active");
395 user_active = oilsUtilsIsDBTrue(tmp_str);
398 tmp_str = oilsFMGetString(userObj, "barred");
399 user_barred = oilsUtilsIsDBTrue(tmp_str);
402 tmp_str = oilsFMGetString(userObj, "deleted");
403 user_deleted = oilsUtilsIsDBTrue(tmp_str);
407 if (!user_exists || user_barred || user_deleted) {
408 response = oilsNewEvent(OILS_LOG_MARK_SAFE, OILS_EVENT_AUTH_FAILED);
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");
418 if (!response && barcode) {
419 // Caller provided a barcode. Ensure it exists and is active.
422 params = jsonParseFmt("{\"barcode\":\"%s\"}", barcode);
423 jsonObject* card = oilsUtilsCStoreReq(
424 "open-ils.cstore.direct.actor.card.search", params);
425 jsonObjectFree(params);
427 if (card && card->type != JSON_NULL) {
428 tmp_str = oilsFMGetString(card, "active");
429 card_ok = oilsUtilsIsDBTrue(tmp_str);
433 jsonObjectFree(card); // card=NULL OK here.
436 response = oilsNewEvent(
437 OILS_LOG_MARK_SAFE, "PATRON_CARD_INACTIVE");
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);
449 // No tests failed. Return SUCCESS.
450 response = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_SUCCESS);
454 jsonObjectFree(userObj); // userObj=NULL OK here.
455 osrfAppRespondComplete(ctx, oilsEventToJSON(response));
456 oilsEventFree(response);