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 ) {
237 if (!strcasecmp(type, OILS_AUTH_OPAC)) {
238 perms[0] = "OPAC_LOGIN";
240 } else if (!strcasecmp(type, OILS_AUTH_STAFF)) {
241 perms[0] = "STAFF_LOGIN";
243 } else if (!strcasecmp(type, OILS_AUTH_TEMP)) {
244 perms[0] = "STAFF_LOGIN";
246 } else if (!strcasecmp(type, OILS_AUTH_PERSIST)) {
247 perms[0] = "PERSISTENT_LOGIN";
250 return oilsUtilsCheckPerms(user_id, org_id, perms, 1);
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.
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
271 int oilsAutInternalCreateSession(osrfMethodContext* ctx) {
272 OSRF_METHOD_VERIFY_CONTEXT(ctx);
274 const jsonObject* args = jsonObjectGetIndex(ctx->params, 0);
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"));
281 if ( !(user_id && login_type && org_unit) ) {
282 return osrfAppRequestRespondException( ctx->session, ctx->request,
283 "Missing parameters for method: %s", ctx->method->name );
286 oilsEvent* response = NULL;
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);
295 return osrfAppRequestRespondException(ctx->session,
296 ctx->request, "No user found with ID %s", user_id);
299 // If a workstation is defined, add the workstation info
301 response = oilsAuthVerifyWorkstation(ctx, userObj, workstation);
303 jsonObjectFree(userObj);
304 osrfAppRespondComplete(ctx, oilsEventToJSON(response));
305 oilsEventFree(response);
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);
316 // determine the auth/cache timeout
317 long timeout = oilsAuthGetTimeout(userObj, login_type, atoi(org_unit));
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);
325 oilsFMSetString(userObj, "passwd", "");
326 jsonObject* cacheObj = jsonParseFmt("{\"authtime\": %ld}", timeout);
327 jsonObjectSetKey(cacheObj, "userobj", jsonObjectClone(userObj));
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 ));
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));
343 osrfCachePutObject(authKey, cacheObj, (time_t) timeout);
344 jsonObjectFree(cacheObj);
345 jsonObject* payload = jsonParseFmt(
346 "{\"authtoken\": \"%s\", \"authtime\": %ld}", authToken, timeout);
348 response = oilsNewEvent2(OSRF_LOG_MARK, OILS_EVENT_SUCCESS, payload);
349 free(string); free(authToken); free(authKey);
350 jsonObjectFree(payload);
352 jsonObjectFree(userObj);
353 osrfAppRespondComplete(ctx, oilsEventToJSON(response));
354 oilsEventFree(response);
360 int oilsAutInternalValidate(osrfMethodContext* ctx) {
361 OSRF_METHOD_VERIFY_CONTEXT(ctx);
363 const jsonObject* args = jsonObjectGetIndex(ctx->params, 0);
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"));
370 if ( !(user_id && login_type && org_unit) ) {
371 return osrfAppRequestRespondException( ctx->session, ctx->request,
372 "Missing parameters for method: %s", ctx->method->name );
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;
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);
387 if (userObj && userObj->type != JSON_NULL) {
390 tmp_str = oilsFMGetString(userObj, "active");
391 user_active = oilsUtilsIsDBTrue(tmp_str);
394 tmp_str = oilsFMGetString(userObj, "barred");
395 user_barred = oilsUtilsIsDBTrue(tmp_str);
398 tmp_str = oilsFMGetString(userObj, "deleted");
399 user_deleted = oilsUtilsIsDBTrue(tmp_str);
403 if (!user_exists || user_barred || user_deleted) {
404 response = oilsNewEvent(OILS_LOG_MARK_SAFE, OILS_EVENT_AUTH_FAILED);
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");
414 if (!response && barcode) {
415 // Caller provided a barcode. Ensure it exists and is active.
418 params = jsonParseFmt("{\"barcode\":\"%s\"}", barcode);
419 jsonObject* card = oilsUtilsCStoreReq(
420 "open-ils.cstore.direct.actor.card.search", params);
421 jsonObjectFree(params);
423 if (card && card->type != JSON_NULL) {
424 tmp_str = oilsFMGetString(card, "active");
425 card_ok = oilsUtilsIsDBTrue(tmp_str);
429 jsonObjectFree(card); // card=NULL OK here.
432 response = oilsNewEvent(
433 OILS_LOG_MARK_SAFE, "PATRON_CARD_INACTIVE");
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);
445 // No tests failed. Return SUCCESS.
446 response = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_SUCCESS);
450 jsonObjectFree(userObj); // userObj=NULL OK here.
451 osrfAppRespondComplete(ctx, oilsEventToJSON(response));
452 oilsEventFree(response);