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_"
12 #define MODULENAME "open-ils.auth"
14 #define OILS_AUTH_OPAC "opac"
15 #define OILS_AUTH_STAFF "staff"
16 #define OILS_AUTH_TEMP "temp"
18 int osrfAppInitialize();
19 int osrfAppChildInit();
21 static int _oilsAuthOPACTimeout = 0;
22 static int _oilsAuthStaffTimeout = 0;
23 static int _oilsAuthOverrideTimeout = 0;
27 @brief Initialize the application by registering functions for method calls.
28 @return Zero in all cases.
30 int osrfAppInitialize() {
32 osrfLogInfo(OSRF_LOG_MARK, "Initializing Auth Server...");
34 /* load and parse the IDL */
35 if (!oilsInitIDL(NULL)) return 1; /* return non-zero to indicate error */
37 osrfAppRegisterMethod(
39 "open-ils.auth.authenticate.init",
41 "Start the authentication process and returns the intermediate authentication seed"
42 " PARAMS( username )", 1, 0 );
44 osrfAppRegisterMethod(
46 "open-ils.auth.authenticate.complete",
48 "Completes the authentication process. Returns an object like so: "
49 "{authtoken : <token>, authtime:<time>}, where authtoken is the login "
50 "token and authtime is the number of seconds the session will be active"
51 "PARAMS(username, md5sum( seed + md5sum( password ) ), type, org_id ) "
52 "type can be one of 'opac','staff', or 'temp' and it defaults to 'staff' "
53 "org_id is the location at which the login should be considered "
54 "active for login timeout purposes", 1, 0 );
56 osrfAppRegisterMethod(
58 "open-ils.auth.session.retrieve",
59 "oilsAuthSessionRetrieve",
60 "Pass in the auth token and this retrieves the user object. The auth "
61 "timeout is reset when this call is made "
62 "Returns the user object (password blanked) for the given login session "
63 "PARAMS( authToken )", 1, 0 );
65 osrfAppRegisterMethod(
67 "open-ils.auth.session.delete",
68 "oilsAuthSessionDelete",
69 "Destroys the given login session "
70 "PARAMS( authToken )", 1, 0 );
72 osrfAppRegisterMethod(
74 "open-ils.auth.session.reset_timeout",
75 "oilsAuthResetTimeout",
76 "Resets the login timeout for the given session "
77 "Returns an ILS Event with payload = session_timeout of session "
78 "if found, otherwise returns the NO_SESSION event"
79 "PARAMS( authToken )", 1, 0 );
85 @brief Dummy placeholder for initializing a server drone.
87 There is nothing to do, so do nothing.
89 int osrfAppChildInit() {
94 @brief Implement the init method.
95 @param ctx The method context.
96 @return Zero if successful, or -1 if not.
101 Return to client: Intermediate authentication seed.
103 Combine the username with a timestamp and process ID, and take an md5 hash of the result.
104 Store the hash in memcache, with a key based on the username. Then return the hash to
107 However: if the username includes one or more embedded blank spaces, return a dummy
108 hash without storing anything in memcache. The dummy will never match a stored hash, so
109 any attempt to authenticate with it will fail.
111 int oilsAuthInit( osrfMethodContext* ctx ) {
112 OSRF_METHOD_VERIFY_CONTEXT(ctx);
114 char* username = jsonObjectToSimpleString( jsonObjectGetIndex(ctx->params, 0) );
119 if( strchr( username, ' ' ) ) {
121 // Embedded spaces are not allowed in a username. Use "x" as a dummy
122 // seed. It will never be a valid seed because 'x' is not a hex digit.
123 resp = jsonNewObject( "x" );
127 // Build a key and a seed; store them in memcache.
128 char* key = va_list_to_string( "%s%s", OILS_AUTH_CACHE_PRFX, username );
129 char* seed = md5sum( "%d.%ld.%s", (int) time(NULL), (long) getpid(), username );
130 osrfCachePutString( key, seed, 30 );
132 osrfLogDebug( OSRF_LOG_MARK, "oilsAuthInit(): has seed %s and key %s", seed, key );
134 // Build a returnable object containing the seed.
135 resp = jsonNewObject( seed );
141 // Return the seed to the client.
142 osrfAppRespondComplete( ctx, resp );
144 jsonObjectFree(resp);
149 return -1; // Error: no username parameter
153 Verifies that the user has permission to login with the
154 given type. If the permission fails, an oilsEvent is returned
156 @return -1 if the permission check failed, 0 if the permission
159 static int oilsAuthCheckLoginPerm(
160 osrfMethodContext* ctx, const jsonObject* userObj, const char* type ) {
162 if(!(userObj && type)) return -1;
163 oilsEvent* perm = NULL;
165 if(!strcasecmp(type, OILS_AUTH_OPAC)) {
166 char* permissions[] = { "OPAC_LOGIN" };
167 perm = oilsUtilsCheckPerms( oilsFMGetObjectId( userObj ), -1, permissions, 1 );
169 } else if(!strcasecmp(type, OILS_AUTH_STAFF)) {
170 char* permissions[] = { "STAFF_LOGIN" };
171 perm = oilsUtilsCheckPerms( oilsFMGetObjectId( userObj ), -1, permissions, 1 );
173 } else if(!strcasecmp(type, OILS_AUTH_TEMP)) {
174 char* permissions[] = { "STAFF_LOGIN" };
175 perm = oilsUtilsCheckPerms( oilsFMGetObjectId( userObj ), -1, permissions, 1 );
179 osrfAppRespondComplete( ctx, oilsEventToJSON(perm) );
188 Returns 1 if the password provided matches the user's real password
192 static int oilsAuthVerifyPassword( const osrfMethodContext* ctx,
193 const jsonObject* userObj, const char* uname, const char* password ) {
196 char* realPassword = oilsFMGetString( userObj, "passwd" ); /**/
197 char* seed = osrfCacheGetString( "%s%s", OILS_AUTH_CACHE_PRFX, uname ); /**/
201 return osrfAppRequestRespondException( ctx->session,
202 ctx->request, "No authentication seed found. "
203 "open-ils.auth.authenticate.init must be called first");
206 osrfLogInternal(OSRF_LOG_MARK, "oilsAuth retrieved real password: [%s]", realPassword);
207 osrfLogDebug(OSRF_LOG_MARK, "oilsAuth retrieved seed from cache: %s", seed );
208 char* maskedPw = md5sum( "%s%s", seed, realPassword );
215 osrfLogDebug(OSRF_LOG_MARK, "oilsAuth generated masked password %s. "
216 "Testing against provided password %s", maskedPw, password );
218 if( !strcmp( maskedPw, password ) ) ret = 1;
226 Calculates the login timeout
227 1. If orgloc is 1 or greater and has a timeout specified as an
228 org unit setting, it is used
229 2. If orgloc is not valid, we check the org unit auth timeout
230 setting for the home org unit of the user logging in
231 3. If that setting is not defined, we use the configured defaults
233 static double oilsAuthGetTimeout( const jsonObject* userObj, const char* type, double orgloc ) {
235 if(!_oilsAuthOPACTimeout) { /* Load the default timeouts */
237 jsonObject* value_obj;
239 value_obj = osrf_settings_host_value_object(
240 "/apps/open-ils.auth/app_settings/default_timeout/opac" );
241 _oilsAuthOPACTimeout = jsonObjectGetNumber(value_obj);
242 jsonObjectFree(value_obj);
244 value_obj = osrf_settings_host_value_object(
245 "/apps/open-ils.auth/app_settings/default_timeout/staff" );
246 _oilsAuthStaffTimeout = jsonObjectGetNumber(value_obj);
247 jsonObjectFree(value_obj);
249 value_obj = osrf_settings_host_value_object(
250 "/apps/open-ils.auth/app_settings/default_timeout/temp" );
251 _oilsAuthOverrideTimeout = jsonObjectGetNumber(value_obj);
252 jsonObjectFree(value_obj);
255 osrfLogInfo(OSRF_LOG_MARK,
256 "Set default auth timeouts: opac => %d : staff => %d : temp => %d",
257 _oilsAuthOPACTimeout, _oilsAuthStaffTimeout, _oilsAuthOverrideTimeout );
260 char* setting = NULL;
262 double home_ou = jsonObjectGetNumber( oilsFMGetObject( userObj, "home_ou" ) );
263 if(orgloc < 1) orgloc = (int) home_ou;
265 if(!strcmp(type, OILS_AUTH_OPAC))
266 setting = OILS_ORG_SETTING_OPAC_TIMEOUT;
267 else if(!strcmp(type, OILS_AUTH_STAFF))
268 setting = OILS_ORG_SETTING_STAFF_TIMEOUT;
269 else if(!strcmp(type, OILS_AUTH_TEMP))
270 setting = OILS_ORG_SETTING_TEMP_TIMEOUT;
272 char* timeout = oilsUtilsFetchOrgSetting( orgloc, setting );
275 if( orgloc != home_ou ) {
276 osrfLogDebug(OSRF_LOG_MARK, "Auth timeout not defined for org %d, "
277 "trying home_ou %d", orgloc, home_ou );
278 timeout = oilsUtilsFetchOrgSetting( (int) home_ou, setting );
281 if(!strcmp(type, OILS_AUTH_STAFF)) return _oilsAuthStaffTimeout;
282 if(!strcmp(type, OILS_AUTH_TEMP)) return _oilsAuthOverrideTimeout;
283 return _oilsAuthOPACTimeout;
287 double t = atof(timeout);
293 Adds the authentication token to the user cache. The timeout for the
294 auth token is based on the type of login as well as (if type=='opac')
296 Returns the event that should be returned to the user.
299 static oilsEvent* oilsAuthHandleLoginOK( jsonObject* userObj, const char* uname,
300 const char* type, double orgloc, const char* workstation ) {
305 char* wsorg = jsonObjectToSimpleString(oilsFMGetObject(userObj, "ws_ou"));
306 if(wsorg) { /* if there is a workstation, use it for the timeout */
307 osrfLogDebug( OSRF_LOG_MARK,
308 "Auth session trying workstation id %d for auth timeout", atoi(wsorg));
309 timeout = oilsAuthGetTimeout( userObj, type, atoi(wsorg) );
312 osrfLogDebug( OSRF_LOG_MARK,
313 "Auth session trying org from param [%d] for auth timeout", orgloc );
314 timeout = oilsAuthGetTimeout( userObj, type, orgloc );
316 osrfLogDebug(OSRF_LOG_MARK, "Auth session timeout for %s: %f", uname, timeout );
318 char* string = va_list_to_string(
319 "%d.%ld.%s", (long) getpid(), time(NULL), uname );
320 char* authToken = md5sum(string);
321 char* authKey = va_list_to_string(
322 "%s%s", OILS_AUTH_CACHE_PRFX, authToken );
324 const char* ws = (workstation) ? workstation : "";
325 osrfLogActivity(OSRF_LOG_MARK,
326 "successful login: username=%s, authtoken=%s, workstation=%s", uname, authToken, ws );
328 oilsFMSetString( userObj, "passwd", "" );
329 jsonObject* cacheObj = jsonParseFmt("{\"authtime\": %f}", timeout);
330 jsonObjectSetKey( cacheObj, "userobj", jsonObjectClone(userObj));
332 osrfCachePutObject( authKey, cacheObj, timeout );
333 jsonObjectFree(cacheObj);
334 osrfLogInternal(OSRF_LOG_MARK, "oilsAuthHandleLoginOK(): Placed user object into cache");
335 jsonObject* payload = jsonParseFmt(
336 "{ \"authtoken\": \"%s\", \"authtime\": %f }", authToken, timeout );
338 response = oilsNewEvent2( OSRF_LOG_MARK, OILS_EVENT_SUCCESS, payload );
339 free(string); free(authToken); free(authKey);
340 jsonObjectFree(payload);
345 static oilsEvent* oilsAuthVerifyWorkstation(
346 const osrfMethodContext* ctx, jsonObject* userObj, const char* ws ) {
347 osrfLogInfo(OSRF_LOG_MARK, "Attaching workstation to user at login: %s", ws);
348 jsonObject* workstation = oilsUtilsFetchWorkstationByName(ws);
349 if(!workstation || workstation->type == JSON_NULL) {
350 jsonObjectFree(workstation);
351 return oilsNewEvent(OSRF_LOG_MARK, "WORKSTATION_NOT_FOUND");
353 long wsid = oilsFMGetObjectId(workstation);
354 LONG_TO_STRING(wsid);
355 char* orgid = oilsFMGetString(workstation, "owning_lib");
356 oilsFMSetString(userObj, "wsid", LONGSTR);
357 oilsFMSetString(userObj, "ws_ou", orgid);
359 jsonObjectFree(workstation);
365 /* see if the card used to login is marked as barred */
366 static oilsEvent* oilsAuthCheckCard( const char* barcode ) {
367 if(!barcode) return NULL;
368 osrfLogDebug(OSRF_LOG_MARK, "Checking to see if barcode %s is active", barcode);
370 jsonObject* params = jsonParseFmt("{\"barcode\":\"%s\"}", barcode);
371 jsonObject* card = oilsUtilsQuickReq(
372 "open-ils.cstore", "open-ils.cstore.direct.actor.card.search", params );
373 jsonObjectFree(params);
375 char* active = oilsFMGetString(card, "active");
376 jsonObjectFree(card);
378 oilsEvent* return_event = NULL;
379 if( ! oilsUtilsIsDBTrue(active) ) {
380 osrfLogInfo(OSRF_LOG_MARK, "barcode %s is not active, returning event", barcode);
381 return_event = oilsNewEvent(OSRF_LOG_MARK, "PATRON_CARD_INACTIVE");
390 int oilsAuthComplete( osrfMethodContext* ctx ) {
391 OSRF_METHOD_VERIFY_CONTEXT(ctx);
393 const jsonObject* args = jsonObjectGetIndex(ctx->params, 0);
395 const char* uname = jsonObjectGetString(jsonObjectGetKeyConst(args, "username"));
396 const char* password = jsonObjectGetString(jsonObjectGetKeyConst(args, "password"));
397 const char* type = jsonObjectGetString(jsonObjectGetKeyConst(args, "type"));
398 double orgloc = jsonObjectGetNumber(jsonObjectGetKeyConst(args, "org"));
399 const char* workstation = jsonObjectGetString(jsonObjectGetKeyConst(args, "workstation"));
400 const char* barcode = jsonObjectGetString(jsonObjectGetKeyConst(args, "barcode"));
402 const char* ws = (workstation) ? workstation : "";
405 if(!type) type = OILS_AUTH_STAFF;
407 if( !( (uname || barcode) && password) ) {
408 return osrfAppRequestRespondException( ctx->session, ctx->request,
409 "username/barcode and password required for method: %s", ctx->method->name );
412 oilsEvent* response = NULL;
413 jsonObject* userObj = NULL;
416 userObj = oilsUtilsFetchUserByUsername( uname );
417 if( userObj && JSON_NULL == userObj->type ) {
418 jsonObjectFree( userObj );
419 userObj = NULL; // username not found
423 userObj = oilsUtilsFetchUserByBarcode( barcode );
426 response = oilsNewEvent( OSRF_LOG_MARK, OILS_EVENT_AUTH_FAILED );
427 osrfLogInfo(OSRF_LOG_MARK, "failed login: username=%s, barcode=%s, workstation=%s",
428 uname, (barcode ? barcode : "(none)"), ws );
429 osrfAppRespondComplete( ctx, oilsEventToJSON(response) );
430 oilsEventFree(response);
434 /* first let's see if they have the right credentials */
437 passOK = oilsAuthVerifyPassword( ctx, userObj, uname, password );
439 passOK = oilsAuthVerifyPassword( ctx, userObj, barcode, password );
442 jsonObjectFree(userObj);
446 /* first see if their account is inactive */
447 char* active = oilsFMGetString(userObj, "active");
448 if( !oilsUtilsIsDBTrue(active) ) {
450 response = oilsNewEvent( OSRF_LOG_MARK, "PATRON_INACTIVE" );
452 response = oilsNewEvent( OSRF_LOG_MARK, OILS_EVENT_AUTH_FAILED );
454 osrfAppRespondComplete( ctx, oilsEventToJSON(response) );
455 oilsEventFree(response);
456 jsonObjectFree(userObj);
462 /* then see if the barcode they used is active */
463 if( barcode && ctx && userObj && (response = oilsAuthCheckCard( barcode )) ) {
464 osrfAppRespondComplete( ctx, oilsEventToJSON(response) );
465 oilsEventFree(response);
466 jsonObjectFree(userObj);
471 /* check to see if the user is even allowed to login */
472 if( oilsAuthCheckLoginPerm( ctx, userObj, type ) == -1 ) {
473 jsonObjectFree(userObj);
478 /* if a workstation is defined, flesh the user with the workstation info */
479 if( workstation != NULL ) {
480 osrfLogDebug(OSRF_LOG_MARK, "Workstation is %s", workstation);
481 response = oilsAuthVerifyWorkstation( ctx, userObj, workstation );
483 jsonObjectFree(userObj);
484 osrfAppRespondComplete( ctx, oilsEventToJSON(response) );
485 oilsEventFree(response);
490 /* otherwise, use the home org as the workstation org on the user */
491 char* orgid = oilsFMGetString(userObj, "home_ou");
492 oilsFMSetString(userObj, "ws_ou", orgid);
496 char* freeable_uname = NULL;
498 uname = freeable_uname = oilsFMGetString( userObj, "usrname" );
502 response = oilsAuthHandleLoginOK( userObj, uname, type, orgloc, workstation );
505 response = oilsNewEvent( OSRF_LOG_MARK, OILS_EVENT_AUTH_FAILED );
506 osrfLogInfo(OSRF_LOG_MARK, "failed login: username=%s, barcode=%s, workstation=%s",
507 uname, (barcode ? barcode : "(none)"), ws );
510 jsonObjectFree(userObj);
511 osrfAppRespondComplete( ctx, oilsEventToJSON(response) );
512 oilsEventFree(response);
515 free(freeable_uname);
522 int oilsAuthSessionDelete( osrfMethodContext* ctx ) {
523 OSRF_METHOD_VERIFY_CONTEXT(ctx);
525 const char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0) );
526 jsonObject* resp = NULL;
529 osrfLogDebug(OSRF_LOG_MARK, "Removing auth session: %s", authToken );
530 char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken ); /**/
531 osrfCacheRemove(key);
532 resp = jsonNewObject(authToken); /**/
536 osrfAppRespondComplete( ctx, resp );
537 jsonObjectFree(resp);
542 Resets the auth login timeout
543 @return The event object, OILS_EVENT_SUCCESS, or OILS_EVENT_NO_SESSION
545 static oilsEvent* _oilsAuthResetTimeout( const char* authToken ) {
546 if(!authToken) return NULL;
548 oilsEvent* evt = NULL;
551 osrfLogDebug(OSRF_LOG_MARK, "Resetting auth timeout for session %s", authToken);
552 char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken );
553 jsonObject* cacheObj = osrfCacheGetObject( key );
556 osrfLogInfo(OSRF_LOG_MARK, "No user in the cache exists with key %s", key);
557 evt = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_NO_SESSION);
561 timeout = jsonObjectGetNumber( jsonObjectGetKeyConst( cacheObj, "authtime"));
562 osrfCacheSetExpire( timeout, key );
563 jsonObject* payload = jsonNewNumberObject(timeout);
564 evt = oilsNewEvent2(OSRF_LOG_MARK, OILS_EVENT_SUCCESS, payload);
565 jsonObjectFree(payload);
566 jsonObjectFree(cacheObj);
573 int oilsAuthResetTimeout( osrfMethodContext* ctx ) {
574 OSRF_METHOD_VERIFY_CONTEXT(ctx);
575 const char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0));
576 oilsEvent* evt = _oilsAuthResetTimeout(authToken);
577 osrfAppRespondComplete( ctx, oilsEventToJSON(evt) );
583 int oilsAuthSessionRetrieve( osrfMethodContext* ctx ) {
584 OSRF_METHOD_VERIFY_CONTEXT(ctx);
586 const char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0));
587 jsonObject* cacheObj = NULL;
588 oilsEvent* evt = NULL;
592 evt = _oilsAuthResetTimeout(authToken);
594 if( evt && strcmp(evt->event, OILS_EVENT_SUCCESS) ) {
595 osrfAppRespondComplete( ctx, oilsEventToJSON(evt) );
599 osrfLogDebug(OSRF_LOG_MARK, "Retrieving auth session: %s", authToken);
600 char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken );
601 cacheObj = osrfCacheGetObject( key );
603 osrfAppRespondComplete( ctx, jsonObjectGetKeyConst( cacheObj, "userobj"));
604 jsonObjectFree(cacheObj);
606 oilsEvent* evt2 = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_NO_SESSION);
607 osrfAppRespondComplete( ctx, oilsEventToJSON(evt2) ); /* should be event.. */
615 evt = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_NO_SESSION);
616 osrfAppRespondComplete( ctx, oilsEventToJSON(evt) );