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 "oilsAuthInternalCreateSession",
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 "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
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 oilsAuthInternalCreateSession(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* login_type = jsonObjectGetString(jsonObjectGetKeyConst(args, "login_type"));
282 const char* workstation = jsonObjectGetString(jsonObjectGetKeyConst(args, "workstation"));
283 int org_unit = jsonObjectGetNumber(jsonObjectGetKeyConst(args, "org_unit"));
285 if ( !(user_id && login_type) ) {
286 return osrfAppRequestRespondException( ctx->session, ctx->request,
287 "Missing parameters for method: %s", ctx->method->name );
290 // default to the root org unit if none is provided.
292 org_unit = oilsUtilsGetRootOrgId();
294 oilsEvent* response = NULL;
296 // fetch the user object
297 jsonObject* idParam = jsonNewNumberStringObject(user_id);
298 jsonObject* userObj = oilsUtilsCStoreReq(
299 "open-ils.cstore.direct.actor.user.retrieve", idParam);
300 jsonObjectFree(idParam);
303 return osrfAppRequestRespondException(ctx->session,
304 ctx->request, "No user found with ID %s", user_id);
307 // If a workstation is defined, add the workstation info
309 response = oilsAuthVerifyWorkstation(ctx, userObj, workstation);
311 jsonObjectFree(userObj);
312 osrfAppRespondComplete(ctx, oilsEventToJSON(response));
313 oilsEventFree(response);
318 // Otherwise, use the home org as the workstation org on the user
319 char* orgid = oilsFMGetString(userObj, "home_ou");
320 oilsFMSetString(userObj, "ws_ou", orgid);
324 // determine the auth/cache timeout
325 long timeout = oilsAuthGetTimeout(userObj, login_type, org_unit);
327 char* string = va_list_to_string("%d.%ld.%ld",
328 (long) getpid(), time(NULL), oilsFMGetObjectId(userObj));
329 char* authToken = md5sum(string);
330 char* authKey = va_list_to_string(
331 "%s%s", OILS_AUTH_CACHE_PRFX, authToken);
333 oilsFMSetString(userObj, "passwd", "");
334 jsonObject* cacheObj = jsonParseFmt("{\"authtime\": %ld}", timeout);
335 jsonObjectSetKey(cacheObj, "userobj", jsonObjectClone(userObj));
337 if( !strcmp(login_type, OILS_AUTH_PERSIST)) {
338 // Add entries for endtime and reset_interval, so that we can gracefully
339 // extend the session a bit if the user is active toward the end of the
340 // timeout originally specified.
341 time_t endtime = time( NULL ) + timeout;
342 jsonObjectSetKey(cacheObj, "endtime",
343 jsonNewNumberObject( (double) endtime ));
345 // Reset interval is hard-coded for now, but if we ever want to make it
346 // configurable, this is the place to do it:
347 jsonObjectSetKey(cacheObj, "reset_interval",
348 jsonNewNumberObject( (double) DEFAULT_RESET_INTERVAL));
351 osrfCachePutObject(authKey, cacheObj, (time_t) timeout);
352 jsonObjectFree(cacheObj);
353 jsonObject* payload = jsonParseFmt(
354 "{\"authtoken\": \"%s\", \"authtime\": %ld}", authToken, timeout);
356 response = oilsNewEvent2(OSRF_LOG_MARK, OILS_EVENT_SUCCESS, payload);
357 free(string); free(authToken); free(authKey);
358 jsonObjectFree(payload);
360 jsonObjectFree(userObj);
361 osrfAppRespondComplete(ctx, oilsEventToJSON(response));
362 oilsEventFree(response);
368 int oilsAuthInternalValidate(osrfMethodContext* ctx) {
369 OSRF_METHOD_VERIFY_CONTEXT(ctx);
371 const jsonObject* args = jsonObjectGetIndex(ctx->params, 0);
373 const char* user_id = jsonObjectGetString(jsonObjectGetKeyConst(args, "user_id"));
374 const char* barcode = jsonObjectGetString(jsonObjectGetKeyConst(args, "barcode"));
375 const char* login_type = jsonObjectGetString(jsonObjectGetKeyConst(args, "login_type"));
376 int org_unit = jsonObjectGetNumber(jsonObjectGetKeyConst(args, "org_unit"));
378 if ( !(user_id && login_type) ) {
379 return osrfAppRequestRespondException( ctx->session, ctx->request,
380 "Missing parameters for method: %s", ctx->method->name );
383 // default to the root org unit if none is provided.
385 org_unit = oilsUtilsGetRootOrgId();
387 oilsEvent* response = NULL;
388 jsonObject *userObj = NULL, *params = NULL;
389 char* tmp_str = NULL;
390 int user_exists = 0, user_active = 0,
391 user_barred = 0, user_deleted = 0;
393 // Confirm user exists, active=true, barred=false, deleted=false
394 params = jsonNewNumberStringObject(user_id);
395 userObj = oilsUtilsCStoreReq(
396 "open-ils.cstore.direct.actor.user.retrieve", params);
397 jsonObjectFree(params);
399 if (userObj && userObj->type != JSON_NULL) {
402 tmp_str = oilsFMGetString(userObj, "active");
403 user_active = oilsUtilsIsDBTrue(tmp_str);
406 tmp_str = oilsFMGetString(userObj, "barred");
407 user_barred = oilsUtilsIsDBTrue(tmp_str);
410 tmp_str = oilsFMGetString(userObj, "deleted");
411 user_deleted = oilsUtilsIsDBTrue(tmp_str);
415 if (!user_exists || user_barred || user_deleted) {
416 response = oilsNewEvent(OILS_LOG_MARK_SAFE, OILS_EVENT_AUTH_FAILED);
419 if (!response && !user_active) {
420 // In some cases, it's useful for the caller to know if the
421 // patron was unable to login becuase the account is inactive.
422 // Return a specific event for this.
423 response = oilsNewEvent(OILS_LOG_MARK_SAFE, "PATRON_INACTIVE");
426 if (!response && barcode) {
427 // Caller provided a barcode. Ensure it exists and is active.
430 params = jsonParseFmt("{\"barcode\":\"%s\"}", barcode);
431 jsonObject* card = oilsUtilsCStoreReq(
432 "open-ils.cstore.direct.actor.card.search", params);
433 jsonObjectFree(params);
435 if (card && card->type != JSON_NULL) {
436 tmp_str = oilsFMGetString(card, "active");
437 card_ok = oilsUtilsIsDBTrue(tmp_str);
441 jsonObjectFree(card); // card=NULL OK here.
444 response = oilsNewEvent(
445 OILS_LOG_MARK_SAFE, "PATRON_CARD_INACTIVE");
449 if (!response) { // Still OK
450 // Confirm user has permission to login w/ the requested type.
451 response = oilsAuthCheckLoginPerm(
452 ctx, atoi(user_id), org_unit, login_type);
457 // No tests failed. Return SUCCESS.
458 response = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_SUCCESS);
462 jsonObjectFree(userObj); // userObj=NULL OK here.
463 osrfAppRespondComplete(ctx, oilsEventToJSON(response));
464 oilsEventFree(response);