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;
26 int osrfAppInitialize() {
28 osrfLogInfo(OSRF_LOG_MARK, "Initializing Auth Server...");
30 /* load and parse the IDL */
31 if (!oilsInitIDL(NULL)) return 1; /* return non-zero to indicate error */
33 osrfAppRegisterMethod(
35 "open-ils.auth.authenticate.init",
37 "Start the authentication process and returns the intermediate authentication seed"
38 " PARAMS( username )", 1, 0 );
40 osrfAppRegisterMethod(
42 "open-ils.auth.authenticate.complete",
44 "Completes the authentication process. Returns an object like so: "
45 "{authtoken : <token>, authtime:<time>}, where authtoken is the login "
46 "token and authtime is the number of seconds the session will be active"
47 "PARAMS(username, md5sum( seed + md5sum( password ) ), type, org_id ) "
48 "type can be one of 'opac','staff', or 'temp' and it defaults to 'staff' "
49 "org_id is the location at which the login should be considered "
50 "active for login timeout purposes", 1, 0 );
52 osrfAppRegisterMethod(
54 "open-ils.auth.session.retrieve",
55 "oilsAuthSessionRetrieve",
56 "Pass in the auth token and this retrieves the user object. The auth "
57 "timeout is reset when this call is made "
58 "Returns the user object (password blanked) for the given login session "
59 "PARAMS( authToken )", 1, 0 );
61 osrfAppRegisterMethod(
63 "open-ils.auth.session.delete",
64 "oilsAuthSessionDelete",
65 "Destroys the given login session "
66 "PARAMS( authToken )", 1, 0 );
68 osrfAppRegisterMethod(
70 "open-ils.auth.session.reset_timeout",
71 "oilsAuthResetTimeout",
72 "Resets the login timeout for the given session "
73 "Returns an ILS Event with payload = session_timeout of session "
74 "if found, otherwise returns the NO_SESSION event"
75 "PARAMS( authToken )", 1, 0 );
80 int osrfAppChildInit() {
84 int oilsAuthInit( osrfMethodContext* ctx ) {
85 OSRF_METHOD_VERIFY_CONTEXT(ctx);
89 char* username = NULL;
94 if( (username = jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, 0))) ) {
96 if( strchr( username, ' ' ) ) {
98 /* spaces are not allowed */
99 resp = jsonNewObject("x"); /* 'x' will never be a valid seed */
100 osrfAppRespondComplete( ctx, resp );
104 seed = va_list_to_string( "%d.%ld.%s", time(NULL), (long) getpid(), username );
105 key = va_list_to_string( "%s%s", OILS_AUTH_CACHE_PRFX, username );
107 md5seed = md5sum(seed);
108 osrfCachePutString( key, md5seed, 30 );
110 osrfLogDebug( OSRF_LOG_MARK, "oilsAuthInit(): has seed %s and key %s", md5seed, key );
112 resp = jsonNewObject(md5seed);
113 osrfAppRespondComplete( ctx, resp );
120 jsonObjectFree(resp);
129 Verifies that the user has permission to login with the
130 given type. If the permission fails, an oilsEvent is returned
132 @return -1 if the permission check failed, 0 if the permission
135 static int oilsAuthCheckLoginPerm(
136 osrfMethodContext* ctx, const jsonObject* userObj, const char* type ) {
138 if(!(userObj && type)) return -1;
139 oilsEvent* perm = NULL;
141 if(!strcasecmp(type, OILS_AUTH_OPAC)) {
142 char* permissions[] = { "OPAC_LOGIN" };
143 perm = oilsUtilsCheckPerms( oilsFMGetObjectId( userObj ), -1, permissions, 1 );
145 } else if(!strcasecmp(type, OILS_AUTH_STAFF)) {
146 char* permissions[] = { "STAFF_LOGIN" };
147 perm = oilsUtilsCheckPerms( oilsFMGetObjectId( userObj ), -1, permissions, 1 );
149 } else if(!strcasecmp(type, OILS_AUTH_TEMP)) {
150 char* permissions[] = { "STAFF_LOGIN" };
151 perm = oilsUtilsCheckPerms( oilsFMGetObjectId( userObj ), -1, permissions, 1 );
155 osrfAppRespondComplete( ctx, oilsEventToJSON(perm) );
164 Returns 1 if the password provided matches the user's real password
168 static int oilsAuthVerifyPassword( const osrfMethodContext* ctx,
169 const jsonObject* userObj, const char* uname, const char* password ) {
172 char* realPassword = oilsFMGetString( userObj, "passwd" ); /**/
173 char* seed = osrfCacheGetString( "%s%s", OILS_AUTH_CACHE_PRFX, uname ); /**/
177 return osrfAppRequestRespondException( ctx->session,
178 ctx->request, "No authentication seed found. "
179 "open-ils.auth.authenticate.init must be called first");
182 osrfLogInternal(OSRF_LOG_MARK, "oilsAuth retrieved real password: [%s]", realPassword);
183 osrfLogDebug(OSRF_LOG_MARK, "oilsAuth retrieved seed from cache: %s", seed );
184 char* maskedPw = md5sum( "%s%s", seed, realPassword );
191 osrfLogDebug(OSRF_LOG_MARK, "oilsAuth generated masked password %s. "
192 "Testing against provided password %s", maskedPw, password );
194 if( !strcmp( maskedPw, password ) ) ret = 1;
202 Calculates the login timeout
203 1. If orgloc is 1 or greater and has a timeout specified as an
204 org unit setting, it is used
205 2. If orgloc is not valid, we check the org unit auth timeout
206 setting for the home org unit of the user logging in
207 3. If that setting is not defined, we use the configured defaults
209 static double oilsAuthGetTimeout( const jsonObject* userObj, const char* type, double orgloc ) {
211 if(!_oilsAuthOPACTimeout) { /* Load the default timeouts */
213 jsonObject* value_obj;
215 value_obj = osrf_settings_host_value_object(
216 "/apps/open-ils.auth/app_settings/default_timeout/opac" );
217 _oilsAuthOPACTimeout = jsonObjectGetNumber(value_obj);
218 jsonObjectFree(value_obj);
220 value_obj = osrf_settings_host_value_object(
221 "/apps/open-ils.auth/app_settings/default_timeout/staff" );
222 _oilsAuthStaffTimeout = jsonObjectGetNumber(value_obj);
223 jsonObjectFree(value_obj);
225 value_obj = osrf_settings_host_value_object(
226 "/apps/open-ils.auth/app_settings/default_timeout/temp" );
227 _oilsAuthOverrideTimeout = jsonObjectGetNumber(value_obj);
228 jsonObjectFree(value_obj);
231 osrfLogInfo(OSRF_LOG_MARK,
232 "Set default auth timeouts: opac => %d : staff => %d : temp => %d",
233 _oilsAuthOPACTimeout, _oilsAuthStaffTimeout, _oilsAuthOverrideTimeout );
236 char* setting = NULL;
238 double home_ou = jsonObjectGetNumber( oilsFMGetObject( userObj, "home_ou" ) );
239 if(orgloc < 1) orgloc = (int) home_ou;
241 if(!strcmp(type, OILS_AUTH_OPAC))
242 setting = OILS_ORG_SETTING_OPAC_TIMEOUT;
243 else if(!strcmp(type, OILS_AUTH_STAFF))
244 setting = OILS_ORG_SETTING_STAFF_TIMEOUT;
245 else if(!strcmp(type, OILS_AUTH_TEMP))
246 setting = OILS_ORG_SETTING_TEMP_TIMEOUT;
248 char* timeout = oilsUtilsFetchOrgSetting( orgloc, setting );
251 if( orgloc != home_ou ) {
252 osrfLogDebug(OSRF_LOG_MARK, "Auth timeout not defined for org %d, "
253 "trying home_ou %d", orgloc, home_ou );
254 timeout = oilsUtilsFetchOrgSetting( (int) home_ou, setting );
257 if(!strcmp(type, OILS_AUTH_STAFF)) return _oilsAuthStaffTimeout;
258 if(!strcmp(type, OILS_AUTH_TEMP)) return _oilsAuthOverrideTimeout;
259 return _oilsAuthOPACTimeout;
263 double t = atof(timeout);
269 Adds the authentication token to the user cache. The timeout for the
270 auth token is based on the type of login as well as (if type=='opac')
272 Returns the event that should be returned to the user.
275 static oilsEvent* oilsAuthHandleLoginOK( jsonObject* userObj, const char* uname,
276 const char* type, double orgloc, const char* workstation ) {
281 char* wsorg = jsonObjectToSimpleString(oilsFMGetObject(userObj, "ws_ou"));
282 if(wsorg) { /* if there is a workstation, use it for the timeout */
283 osrfLogDebug( OSRF_LOG_MARK,
284 "Auth session trying workstation id %d for auth timeout", atoi(wsorg));
285 timeout = oilsAuthGetTimeout( userObj, type, atoi(wsorg) );
288 osrfLogDebug( OSRF_LOG_MARK,
289 "Auth session trying org from param [%d] for auth timeout", orgloc );
290 timeout = oilsAuthGetTimeout( userObj, type, orgloc );
292 osrfLogDebug(OSRF_LOG_MARK, "Auth session timeout for %s: %f", uname, timeout );
294 char* string = va_list_to_string(
295 "%d.%ld.%s", (long) getpid(), time(NULL), uname );
296 char* authToken = md5sum(string);
297 char* authKey = va_list_to_string(
298 "%s%s", OILS_AUTH_CACHE_PRFX, authToken );
300 const char* ws = (workstation) ? workstation : "";
301 osrfLogActivity(OSRF_LOG_MARK,
302 "successful login: username=%s, authtoken=%s, workstation=%s", uname, authToken, ws );
304 oilsFMSetString( userObj, "passwd", "" );
305 jsonObject* cacheObj = jsonParseFmt("{\"authtime\": %f}", timeout);
306 jsonObjectSetKey( cacheObj, "userobj", jsonObjectClone(userObj));
308 osrfCachePutObject( authKey, cacheObj, timeout );
309 jsonObjectFree(cacheObj);
310 osrfLogInternal(OSRF_LOG_MARK, "oilsAuthHandleLoginOK(): Placed user object into cache");
311 jsonObject* payload = jsonParseFmt(
312 "{ \"authtoken\": \"%s\", \"authtime\": %f }", authToken, timeout );
314 response = oilsNewEvent2( OSRF_LOG_MARK, OILS_EVENT_SUCCESS, payload );
315 free(string); free(authToken); free(authKey);
316 jsonObjectFree(payload);
321 static oilsEvent* oilsAuthVerifyWorkstation(
322 const osrfMethodContext* ctx, jsonObject* userObj, const char* ws ) {
323 osrfLogInfo(OSRF_LOG_MARK, "Attaching workstation to user at login: %s", ws);
324 jsonObject* workstation = oilsUtilsFetchWorkstationByName(ws);
325 if(!workstation || workstation->type == JSON_NULL) {
326 jsonObjectFree(workstation);
327 return oilsNewEvent(OSRF_LOG_MARK, "WORKSTATION_NOT_FOUND");
329 long wsid = oilsFMGetObjectId(workstation);
330 LONG_TO_STRING(wsid);
331 char* orgid = oilsFMGetString(workstation, "owning_lib");
332 oilsFMSetString(userObj, "wsid", LONGSTR);
333 oilsFMSetString(userObj, "ws_ou", orgid);
335 jsonObjectFree(workstation);
341 /* see if the card used to login is marked as barred */
342 static oilsEvent* oilsAuthCheckCard( const char* barcode ) {
343 if(!barcode) return NULL;
344 osrfLogDebug(OSRF_LOG_MARK, "Checking to see if barcode %s is active", barcode);
346 jsonObject* params = jsonParseFmt("{\"barcode\":\"%s\"}", barcode);
347 jsonObject* card = oilsUtilsQuickReq(
348 "open-ils.cstore", "open-ils.cstore.direct.actor.card.search", params );
349 jsonObjectFree(params);
351 char* active = oilsFMGetString(card, "active");
352 jsonObjectFree(card);
354 oilsEvent* return_event = NULL;
355 if( ! oilsUtilsIsDBTrue(active) ) {
356 osrfLogInfo(OSRF_LOG_MARK, "barcode %s is not active, returning event", barcode);
357 return_event = oilsNewEvent(OSRF_LOG_MARK, "PATRON_CARD_INACTIVE");
366 int oilsAuthComplete( osrfMethodContext* ctx ) {
367 OSRF_METHOD_VERIFY_CONTEXT(ctx);
369 const jsonObject* args = jsonObjectGetIndex(ctx->params, 0);
371 const char* uname = jsonObjectGetString(jsonObjectGetKeyConst(args, "username"));
372 const char* password = jsonObjectGetString(jsonObjectGetKeyConst(args, "password"));
373 const char* type = jsonObjectGetString(jsonObjectGetKeyConst(args, "type"));
374 double orgloc = jsonObjectGetNumber(jsonObjectGetKeyConst(args, "org"));
375 const char* workstation = jsonObjectGetString(jsonObjectGetKeyConst(args, "workstation"));
376 const char* barcode = jsonObjectGetString(jsonObjectGetKeyConst(args, "barcode"));
378 const char* ws = (workstation) ? workstation : "";
381 if(!type) type = OILS_AUTH_STAFF;
383 if( !( (uname || barcode) && password) ) {
384 return osrfAppRequestRespondException( ctx->session, ctx->request,
385 "username/barcode and password required for method: %s", ctx->method->name );
388 oilsEvent* response = NULL;
389 jsonObject* userObj = NULL;
392 userObj = oilsUtilsFetchUserByUsername( uname );
393 if( userObj && JSON_NULL == userObj->type ) {
394 jsonObjectFree( userObj );
395 userObj = NULL; // username not found
399 userObj = oilsUtilsFetchUserByBarcode( barcode );
402 response = oilsNewEvent( OSRF_LOG_MARK, OILS_EVENT_AUTH_FAILED );
403 osrfLogInfo(OSRF_LOG_MARK, "failed login: username=%s, barcode=%s, workstation=%s",
404 uname, (barcode ? barcode : "(none)"), ws );
405 osrfAppRespondComplete( ctx, oilsEventToJSON(response) );
406 oilsEventFree(response);
410 /* first let's see if they have the right credentials */
413 passOK = oilsAuthVerifyPassword( ctx, userObj, uname, password );
415 passOK = oilsAuthVerifyPassword( ctx, userObj, barcode, password );
418 jsonObjectFree(userObj);
422 /* first see if their account is inactive */
423 char* active = oilsFMGetString(userObj, "active");
424 if( !oilsUtilsIsDBTrue(active) ) {
426 response = oilsNewEvent( OSRF_LOG_MARK, "PATRON_INACTIVE" );
428 response = oilsNewEvent( OSRF_LOG_MARK, OILS_EVENT_AUTH_FAILED );
430 osrfAppRespondComplete( ctx, oilsEventToJSON(response) );
431 oilsEventFree(response);
432 jsonObjectFree(userObj);
438 /* then see if the barcode they used is active */
439 if( barcode && ctx && userObj && (response = oilsAuthCheckCard( barcode )) ) {
440 osrfAppRespondComplete( ctx, oilsEventToJSON(response) );
441 oilsEventFree(response);
442 jsonObjectFree(userObj);
447 /* check to see if the user is even allowed to login */
448 if( oilsAuthCheckLoginPerm( ctx, userObj, type ) == -1 ) {
449 jsonObjectFree(userObj);
454 /* if a workstation is defined, flesh the user with the workstation info */
455 if( workstation != NULL ) {
456 osrfLogDebug(OSRF_LOG_MARK, "Workstation is %s", workstation);
457 response = oilsAuthVerifyWorkstation( ctx, userObj, workstation );
459 jsonObjectFree(userObj);
460 osrfAppRespondComplete( ctx, oilsEventToJSON(response) );
461 oilsEventFree(response);
466 /* otherwise, use the home org as the workstation org on the user */
467 char* orgid = oilsFMGetString(userObj, "home_ou");
468 oilsFMSetString(userObj, "ws_ou", orgid);
472 char* freeable_uname = NULL;
474 uname = freeable_uname = oilsFMGetString( userObj, "usrname" );
478 response = oilsAuthHandleLoginOK( userObj, uname, type, orgloc, workstation );
481 response = oilsNewEvent( OSRF_LOG_MARK, OILS_EVENT_AUTH_FAILED );
482 osrfLogInfo(OSRF_LOG_MARK, "failed login: username=%s, barcode=%s, workstation=%s",
483 uname, (barcode ? barcode : "(none)"), ws );
486 jsonObjectFree(userObj);
487 osrfAppRespondComplete( ctx, oilsEventToJSON(response) );
488 oilsEventFree(response);
491 free(freeable_uname);
498 int oilsAuthSessionDelete( osrfMethodContext* ctx ) {
499 OSRF_METHOD_VERIFY_CONTEXT(ctx);
501 const char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0) );
502 jsonObject* resp = NULL;
505 osrfLogDebug(OSRF_LOG_MARK, "Removing auth session: %s", authToken );
506 char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken ); /**/
507 osrfCacheRemove(key);
508 resp = jsonNewObject(authToken); /**/
512 osrfAppRespondComplete( ctx, resp );
513 jsonObjectFree(resp);
518 Resets the auth login timeout
519 @return The event object, OILS_EVENT_SUCCESS, or OILS_EVENT_NO_SESSION
521 static oilsEvent* _oilsAuthResetTimeout( const char* authToken ) {
522 if(!authToken) return NULL;
524 oilsEvent* evt = NULL;
527 osrfLogDebug(OSRF_LOG_MARK, "Resetting auth timeout for session %s", authToken);
528 char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken );
529 jsonObject* cacheObj = osrfCacheGetObject( key );
532 osrfLogInfo(OSRF_LOG_MARK, "No user in the cache exists with key %s", key);
533 evt = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_NO_SESSION);
537 timeout = jsonObjectGetNumber( jsonObjectGetKeyConst( cacheObj, "authtime"));
538 osrfCacheSetExpire( timeout, key );
539 jsonObject* payload = jsonNewNumberObject(timeout);
540 evt = oilsNewEvent2(OSRF_LOG_MARK, OILS_EVENT_SUCCESS, payload);
541 jsonObjectFree(payload);
542 jsonObjectFree(cacheObj);
549 int oilsAuthResetTimeout( osrfMethodContext* ctx ) {
550 OSRF_METHOD_VERIFY_CONTEXT(ctx);
551 const char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0));
552 oilsEvent* evt = _oilsAuthResetTimeout(authToken);
553 osrfAppRespondComplete( ctx, oilsEventToJSON(evt) );
559 int oilsAuthSessionRetrieve( osrfMethodContext* ctx ) {
560 OSRF_METHOD_VERIFY_CONTEXT(ctx);
562 const char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0));
563 jsonObject* cacheObj = NULL;
564 oilsEvent* evt = NULL;
568 evt = _oilsAuthResetTimeout(authToken);
570 if( evt && strcmp(evt->event, OILS_EVENT_SUCCESS) ) {
571 osrfAppRespondComplete( ctx, oilsEventToJSON(evt) );
575 osrfLogDebug(OSRF_LOG_MARK, "Retrieving auth session: %s", authToken);
576 char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken );
577 cacheObj = osrfCacheGetObject( key );
579 osrfAppRespondComplete( ctx, jsonObjectGetKeyConst( cacheObj, "userobj"));
580 jsonObjectFree(cacheObj);
582 oilsEvent* evt2 = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_NO_SESSION);
583 osrfAppRespondComplete( ctx, oilsEventToJSON(evt2) ); /* should be event.. */
591 evt = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_NO_SESSION);
592 osrfAppRespondComplete( ctx, oilsEventToJSON(evt) );