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_internal/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_internal/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_internal/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_internal/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 oilsEvent* response = NULL;
292 // fetch the user object
293 jsonObject* idParam = jsonNewNumberStringObject(user_id);
294 jsonObject* userObj = oilsUtilsCStoreReqCtx(
295 ctx, "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 if (response) { // invalid workstation.
308 jsonObjectFree(userObj);
309 osrfAppRespondComplete(ctx, oilsEventToJSON(response));
310 oilsEventFree(response);
313 } else { // workstation OK.
315 // The worksation org unit supersedes any org unit value
316 // provided via the API. oilsAuthVerifyWorkstation() sets the
317 // ws_ou value to the WS owning lib. A value is guaranteed.
318 org_unit = atoi(oilsFMGetStringConst(userObj, "ws_ou"));
321 } else { // no workstation
323 // For backwards compatibility, when no workstation is provided, use
324 // the users's home org as its workstation org unit, regardless of
325 // any API-level org unit value provided.
326 const char* orgid = oilsFMGetStringConst(userObj, "home_ou");
327 oilsFMSetString(userObj, "ws_ou", orgid);
329 // The context org unit defaults to the user's home library when
330 // no workstation is used and no API-level value is provided.
331 if (org_unit < 1) org_unit = atoi(orgid);
334 // determine the auth/cache timeout
335 long timeout = oilsAuthGetTimeout(userObj, login_type, org_unit);
337 char* string = va_list_to_string("%d.%ld.%ld",
338 (long) getpid(), time(NULL), oilsFMGetObjectId(userObj));
339 char* authToken = md5sum(string);
340 char* authKey = va_list_to_string(
341 "%s%s", OILS_AUTH_CACHE_PRFX, authToken);
343 oilsFMSetString(userObj, "passwd", "");
344 jsonObject* cacheObj = jsonParseFmt("{\"authtime\": %ld}", timeout);
345 jsonObjectSetKey(cacheObj, "userobj", jsonObjectClone(userObj));
347 if( !strcmp(login_type, OILS_AUTH_PERSIST)) {
348 // Add entries for endtime and reset_interval, so that we can gracefully
349 // extend the session a bit if the user is active toward the end of the
350 // timeout originally specified.
351 time_t endtime = time( NULL ) + timeout;
352 jsonObjectSetKey(cacheObj, "endtime",
353 jsonNewNumberObject( (double) endtime ));
355 // Reset interval is hard-coded for now, but if we ever want to make it
356 // configurable, this is the place to do it:
357 jsonObjectSetKey(cacheObj, "reset_interval",
358 jsonNewNumberObject( (double) DEFAULT_RESET_INTERVAL));
361 osrfCachePutObject(authKey, cacheObj, (time_t) timeout);
362 jsonObjectFree(cacheObj);
363 jsonObject* payload = jsonParseFmt(
364 "{\"authtoken\": \"%s\", \"authtime\": %ld}", authToken, timeout);
366 response = oilsNewEvent2(OSRF_LOG_MARK, OILS_EVENT_SUCCESS, payload);
367 free(string); free(authToken); free(authKey);
368 jsonObjectFree(payload);
370 jsonObjectFree(userObj);
371 osrfAppRespondComplete(ctx, oilsEventToJSON(response));
372 oilsEventFree(response);
378 int oilsAuthInternalValidate(osrfMethodContext* ctx) {
379 OSRF_METHOD_VERIFY_CONTEXT(ctx);
381 const jsonObject* args = jsonObjectGetIndex(ctx->params, 0);
383 const char* user_id = jsonObjectGetString(jsonObjectGetKeyConst(args, "user_id"));
384 const char* barcode = jsonObjectGetString(jsonObjectGetKeyConst(args, "barcode"));
385 const char* login_type = jsonObjectGetString(jsonObjectGetKeyConst(args, "login_type"));
386 int org_unit = jsonObjectGetNumber(jsonObjectGetKeyConst(args, "org_unit"));
388 if ( !(user_id && login_type) ) {
389 return osrfAppRequestRespondException( ctx->session, ctx->request,
390 "Missing parameters for method: %s", ctx->method->name );
393 oilsEvent* response = NULL;
394 jsonObject *userObj = NULL, *params = NULL;
395 char* tmp_str = NULL;
396 int user_exists = 0, user_active = 0,
397 user_barred = 0, user_deleted = 0;
399 // Confirm user exists, active=true, barred=false, deleted=false
400 params = jsonNewNumberStringObject(user_id);
401 userObj = oilsUtilsCStoreReqCtx(
402 ctx, "open-ils.cstore.direct.actor.user.retrieve", params);
403 jsonObjectFree(params);
405 if (userObj && userObj->type != JSON_NULL) {
408 tmp_str = oilsFMGetString(userObj, "active");
409 user_active = oilsUtilsIsDBTrue(tmp_str);
412 tmp_str = oilsFMGetString(userObj, "barred");
413 user_barred = oilsUtilsIsDBTrue(tmp_str);
416 tmp_str = oilsFMGetString(userObj, "deleted");
417 user_deleted = oilsUtilsIsDBTrue(tmp_str);
421 if (!user_exists || user_barred || user_deleted) {
422 response = oilsNewEvent(OILS_LOG_MARK_SAFE, OILS_EVENT_AUTH_FAILED);
425 if (!response && !user_active) {
426 // In some cases, it's useful for the caller to know if the
427 // patron was unable to login becuase the account is inactive.
428 // Return a specific event for this.
429 response = oilsNewEvent(OILS_LOG_MARK_SAFE, "PATRON_INACTIVE");
432 if (!response && barcode) {
433 // Caller provided a barcode. Ensure it exists and is active.
436 params = jsonParseFmt("{\"barcode\":\"%s\"}", barcode);
437 jsonObject* card = oilsUtilsCStoreReqCtx(
438 ctx, "open-ils.cstore.direct.actor.card.search", params);
439 jsonObjectFree(params);
441 if (card && card->type != JSON_NULL) {
442 tmp_str = oilsFMGetString(card, "active");
443 card_ok = oilsUtilsIsDBTrue(tmp_str);
447 jsonObjectFree(card); // card=NULL OK here.
450 response = oilsNewEvent(
451 OILS_LOG_MARK_SAFE, "PATRON_CARD_INACTIVE");
455 // XXX: login permission checks are always global (see
456 // oilsAuthCheckLoginPerm()). No need to extract the
457 // workstation org unit here.
459 if (!response) { // Still OK
460 // Confirm user has permission to login w/ the requested type.
461 response = oilsAuthCheckLoginPerm(
462 ctx, atoi(user_id), org_unit, login_type);
467 // No tests failed. Return SUCCESS.
468 response = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_SUCCESS);
472 jsonObjectFree(userObj); // userObj=NULL OK here.
473 osrfAppRespondComplete(ctx, oilsEventToJSON(response));
474 oilsEventFree(response);