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);
128 /** Verifies that the user has permission to login with the
129 * given type. If the permission fails, an oilsEvent is returned
131 * @return -1 if the permission check failed, 0 if the permission
134 static int oilsAuthCheckLoginPerm(
135 osrfMethodContext* ctx, const jsonObject* userObj, const char* type ) {
137 if(!(userObj && type)) return -1;
138 oilsEvent* perm = NULL;
140 if(!strcasecmp(type, OILS_AUTH_OPAC)) {
141 char* permissions[] = { "OPAC_LOGIN" };
142 perm = oilsUtilsCheckPerms( oilsFMGetObjectId( userObj ), -1, permissions, 1 );
144 } else if(!strcasecmp(type, OILS_AUTH_STAFF)) {
145 char* permissions[] = { "STAFF_LOGIN" };
146 perm = oilsUtilsCheckPerms( oilsFMGetObjectId( userObj ), -1, permissions, 1 );
148 } else if(!strcasecmp(type, OILS_AUTH_TEMP)) {
149 char* permissions[] = { "STAFF_LOGIN" };
150 perm = oilsUtilsCheckPerms( oilsFMGetObjectId( userObj ), -1, permissions, 1 );
154 osrfAppRespondComplete( ctx, oilsEventToJSON(perm) );
163 * Returns 1 if the password provided matches the user's real password
164 * Returns 0 otherwise
165 * Returns -1 on error
167 static int oilsAuthVerifyPassword( const osrfMethodContext* ctx,
168 const jsonObject* userObj, const char* uname, const char* password ) {
171 char* realPassword = oilsFMGetString( userObj, "passwd" ); /**/
172 char* seed = osrfCacheGetString( "%s%s", OILS_AUTH_CACHE_PRFX, uname ); /**/
176 return osrfAppRequestRespondException( ctx->session,
177 ctx->request, "No authentication seed found. "
178 "open-ils.auth.authenticate.init must be called first");
181 osrfLogInternal(OSRF_LOG_MARK, "oilsAuth retrieved real password: [%s]", realPassword);
182 osrfLogDebug(OSRF_LOG_MARK, "oilsAuth retrieved seed from cache: %s", seed );
183 char* maskedPw = md5sum( "%s%s", seed, realPassword );
190 osrfLogDebug(OSRF_LOG_MARK, "oilsAuth generated masked password %s. "
191 "Testing against provided password %s", maskedPw, password );
193 if( !strcmp( maskedPw, password ) ) ret = 1;
201 * Calculates the login timeout
202 * 1. If orgloc is 1 or greater and has a timeout specified as an
203 * org unit setting, it is used
204 * 2. If orgloc is not valid, we check the org unit auth timeout
205 * setting for the home org unit of the user logging in
206 * 3. If that setting is not defined, we use the configured defaults
208 static double oilsAuthGetTimeout( const jsonObject* userObj, const char* type, double orgloc ) {
210 if(!_oilsAuthOPACTimeout) { /* Load the default timeouts */
212 jsonObject* value_obj;
214 value_obj = osrf_settings_host_value_object(
215 "/apps/open-ils.auth/app_settings/default_timeout/opac" );
216 _oilsAuthOPACTimeout = jsonObjectGetNumber(value_obj);
217 jsonObjectFree(value_obj);
219 value_obj = osrf_settings_host_value_object(
220 "/apps/open-ils.auth/app_settings/default_timeout/staff" );
221 _oilsAuthStaffTimeout = jsonObjectGetNumber(value_obj);
222 jsonObjectFree(value_obj);
224 value_obj = osrf_settings_host_value_object(
225 "/apps/open-ils.auth/app_settings/default_timeout/temp" );
226 _oilsAuthOverrideTimeout = jsonObjectGetNumber(value_obj);
227 jsonObjectFree(value_obj);
230 osrfLogInfo(OSRF_LOG_MARK, "Set default auth timeouts: opac => %d : staff => %d : temp => %d",
231 _oilsAuthOPACTimeout, _oilsAuthStaffTimeout, _oilsAuthOverrideTimeout );
234 char* setting = NULL;
236 double home_ou = jsonObjectGetNumber( oilsFMGetObject( userObj, "home_ou" ) );
237 if(orgloc < 1) orgloc = (int) home_ou;
239 if(!strcmp(type, OILS_AUTH_OPAC))
240 setting = OILS_ORG_SETTING_OPAC_TIMEOUT;
241 else if(!strcmp(type, OILS_AUTH_STAFF))
242 setting = OILS_ORG_SETTING_STAFF_TIMEOUT;
243 else if(!strcmp(type, OILS_AUTH_TEMP))
244 setting = OILS_ORG_SETTING_TEMP_TIMEOUT;
246 char* timeout = oilsUtilsFetchOrgSetting( orgloc, setting );
249 if( orgloc != home_ou ) {
250 osrfLogDebug(OSRF_LOG_MARK, "Auth timeout not defined for org %d, "
251 "trying home_ou %d", orgloc, home_ou );
252 timeout = oilsUtilsFetchOrgSetting( (int) home_ou, setting );
255 if(!strcmp(type, OILS_AUTH_STAFF)) return _oilsAuthStaffTimeout;
256 if(!strcmp(type, OILS_AUTH_TEMP)) return _oilsAuthOverrideTimeout;
257 return _oilsAuthOPACTimeout;
261 double t = atof(timeout);
266 /* Adds the authentication token to the user cache. The timeout for the
267 * auth token is based on the type of login as well as (if type=='opac')
268 * the org location id.
269 * Returns the event that should be returned to the user.
270 * Event must be freed
272 static oilsEvent* oilsAuthHandleLoginOK( jsonObject* userObj, const char* uname,
273 const char* type, double orgloc, const char* workstation ) {
278 char* wsorg = jsonObjectToSimpleString(oilsFMGetObject(userObj, "ws_ou"));
279 if(wsorg) { /* if there is a workstation, use it for the timeout */
280 osrfLogDebug( OSRF_LOG_MARK,
281 "Auth session trying workstation id %d for auth timeout", atoi(wsorg));
282 timeout = oilsAuthGetTimeout( userObj, type, atoi(wsorg) );
285 osrfLogDebug( OSRF_LOG_MARK,
286 "Auth session trying org from param [%d] for auth timeout", orgloc );
287 timeout = oilsAuthGetTimeout( userObj, type, orgloc );
289 osrfLogDebug(OSRF_LOG_MARK, "Auth session timeout for %s: %f", uname, timeout );
291 char* string = va_list_to_string(
292 "%d.%ld.%s", (long) getpid(), time(NULL), uname );
293 char* authToken = md5sum(string);
294 char* authKey = va_list_to_string(
295 "%s%s", OILS_AUTH_CACHE_PRFX, authToken );
297 const char* ws = (workstation) ? workstation : "";
298 osrfLogActivity(OSRF_LOG_MARK,
299 "successful login: username=%s, authtoken=%s, workstation=%s", uname, authToken, ws );
301 oilsFMSetString( userObj, "passwd", "" );
302 jsonObject* cacheObj = jsonParseFmt("{\"authtime\": %f}", timeout);
303 jsonObjectSetKey( cacheObj, "userobj", jsonObjectClone(userObj));
305 osrfCachePutObject( authKey, cacheObj, timeout );
306 jsonObjectFree(cacheObj);
307 osrfLogInternal(OSRF_LOG_MARK, "oilsAuthHandleLoginOK(): Placed user object into cache");
308 jsonObject* payload = jsonParseFmt(
309 "{ \"authtoken\": \"%s\", \"authtime\": %f }", authToken, timeout );
311 response = oilsNewEvent2( OSRF_LOG_MARK, OILS_EVENT_SUCCESS, payload );
312 free(string); free(authToken); free(authKey);
313 jsonObjectFree(payload);
318 static oilsEvent* oilsAuthVerifyWorkstation(
319 const osrfMethodContext* ctx, jsonObject* userObj, const char* ws ) {
320 osrfLogInfo(OSRF_LOG_MARK, "Attaching workstation to user at login: %s", ws);
321 jsonObject* workstation = oilsUtilsFetchWorkstationByName(ws);
322 if(!workstation || workstation->type == JSON_NULL) {
323 jsonObjectFree(workstation);
324 return oilsNewEvent(OSRF_LOG_MARK, "WORKSTATION_NOT_FOUND");
326 long wsid = oilsFMGetObjectId(workstation);
327 LONG_TO_STRING(wsid);
328 char* orgid = oilsFMGetString(workstation, "owning_lib");
329 oilsFMSetString(userObj, "wsid", LONGSTR);
330 oilsFMSetString(userObj, "ws_ou", orgid);
332 jsonObjectFree(workstation);
338 /* see if the card used to login is marked as barred */
339 static oilsEvent* oilsAuthCheckCard( const char* barcode ) {
340 if(!barcode) return NULL;
341 osrfLogDebug(OSRF_LOG_MARK, "Checking to see if barcode %s is active", barcode);
343 jsonObject* params = jsonParseFmt("{\"barcode\":\"%s\"}", barcode);
344 jsonObject* card = oilsUtilsQuickReq(
345 "open-ils.cstore", "open-ils.cstore.direct.actor.card.search", params );
346 jsonObjectFree(params);
348 char* active = oilsFMGetString(card, "active");
349 jsonObjectFree(card);
351 oilsEvent* return_event = NULL;
352 if( ! oilsUtilsIsDBTrue(active) ) {
353 osrfLogInfo(OSRF_LOG_MARK, "barcode %s is not active, returning event", barcode);
354 return_event = oilsNewEvent(OSRF_LOG_MARK, "PATRON_CARD_INACTIVE");
363 int oilsAuthComplete( osrfMethodContext* ctx ) {
364 OSRF_METHOD_VERIFY_CONTEXT(ctx);
366 const jsonObject* args = jsonObjectGetIndex(ctx->params, 0);
368 const char* uname = jsonObjectGetString(jsonObjectGetKeyConst(args, "username"));
369 const char* password = jsonObjectGetString(jsonObjectGetKeyConst(args, "password"));
370 const char* type = jsonObjectGetString(jsonObjectGetKeyConst(args, "type"));
371 double orgloc = jsonObjectGetNumber(jsonObjectGetKeyConst(args, "org"));
372 const char* workstation = jsonObjectGetString(jsonObjectGetKeyConst(args, "workstation"));
373 const char* barcode = jsonObjectGetString(jsonObjectGetKeyConst(args, "barcode"));
375 const char* ws = (workstation) ? workstation : "";
378 if(!type) type = OILS_AUTH_STAFF;
380 if( !( (uname || barcode) && password) ) {
381 return osrfAppRequestRespondException( ctx->session, ctx->request,
382 "username/barcode and password required for method: %s", ctx->method->name );
385 oilsEvent* response = NULL;
386 jsonObject* userObj = NULL;
389 userObj = oilsUtilsFetchUserByUsername( uname );
390 if( userObj && JSON_NULL == userObj->type ) {
391 jsonObjectFree( userObj );
392 userObj = NULL; // username not found
396 userObj = oilsUtilsFetchUserByBarcode( barcode );
399 response = oilsNewEvent( OSRF_LOG_MARK, OILS_EVENT_AUTH_FAILED );
400 osrfLogInfo(OSRF_LOG_MARK, "failed login: username=%s, barcode=%s, workstation=%s",
401 uname, (barcode ? barcode : "(none)"), ws );
402 osrfAppRespondComplete( ctx, oilsEventToJSON(response) );
403 oilsEventFree(response);
407 /* first let's see if they have the right credentials */
409 if(uname) passOK = oilsAuthVerifyPassword( ctx, userObj, uname, password );
411 passOK = oilsAuthVerifyPassword( ctx, userObj, barcode, password );
414 jsonObjectFree(userObj);
418 /* first see if their account is inactive */
419 char* active = oilsFMGetString(userObj, "active");
420 if( !oilsUtilsIsDBTrue(active) ) {
422 response = oilsNewEvent( OSRF_LOG_MARK, "PATRON_INACTIVE" );
424 response = oilsNewEvent( OSRF_LOG_MARK, OILS_EVENT_AUTH_FAILED );
426 osrfAppRespondComplete( ctx, oilsEventToJSON(response) );
427 oilsEventFree(response);
428 jsonObjectFree(userObj);
434 /* then see if the barcode they used is active */
435 if( barcode && ctx && userObj && (response = oilsAuthCheckCard( barcode )) ) {
436 osrfAppRespondComplete( ctx, oilsEventToJSON(response) );
437 oilsEventFree(response);
438 jsonObjectFree(userObj);
443 /* check to see if the user is even allowed to login */
444 if( oilsAuthCheckLoginPerm( ctx, userObj, type ) == -1 ) {
445 jsonObjectFree(userObj);
450 /* if a workstation is defined, flesh the user with the workstation info */
451 if( workstation != NULL ) {
452 osrfLogDebug(OSRF_LOG_MARK, "Workstation is %s", workstation);
453 response = oilsAuthVerifyWorkstation( ctx, userObj, workstation );
455 jsonObjectFree(userObj);
456 osrfAppRespondComplete( ctx, oilsEventToJSON(response) );
457 oilsEventFree(response);
462 /* otherwise, use the home org as the workstation org on the user */
463 char* orgid = oilsFMGetString(userObj, "home_ou");
464 oilsFMSetString(userObj, "ws_ou", orgid);
468 char* freeable_uname = NULL;
470 uname = freeable_uname = oilsFMGetString( userObj, "usrname" );
474 response = oilsAuthHandleLoginOK( userObj, uname, type, orgloc, workstation );
477 response = oilsNewEvent( OSRF_LOG_MARK, OILS_EVENT_AUTH_FAILED );
478 osrfLogInfo(OSRF_LOG_MARK, "failed login: username=%s, barcode=%s, workstation=%s",
479 uname, (barcode ? barcode : "(none)"), ws );
482 jsonObjectFree(userObj);
483 osrfAppRespondComplete( ctx, oilsEventToJSON(response) );
484 oilsEventFree(response);
486 if(freeable_uname) free(freeable_uname);
493 int oilsAuthSessionDelete( osrfMethodContext* ctx ) {
494 OSRF_METHOD_VERIFY_CONTEXT(ctx);
496 const char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0) );
497 jsonObject* resp = NULL;
500 osrfLogDebug(OSRF_LOG_MARK, "Removing auth session: %s", authToken );
501 char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken ); /**/
502 osrfCacheRemove(key);
503 resp = jsonNewObject(authToken); /**/
507 osrfAppRespondComplete( ctx, resp );
508 jsonObjectFree(resp);
512 /** Resets the auth login timeout
513 * @return The event object, OILS_EVENT_SUCCESS, or OILS_EVENT_NO_SESSION
515 static oilsEvent* _oilsAuthResetTimeout( const char* authToken ) {
516 if(!authToken) return NULL;
518 oilsEvent* evt = NULL;
521 osrfLogDebug(OSRF_LOG_MARK, "Resetting auth timeout for session %s", authToken);
522 char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken );
523 jsonObject* cacheObj = osrfCacheGetObject( key );
526 osrfLogInfo(OSRF_LOG_MARK, "No user in the cache exists with key %s", key);
527 evt = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_NO_SESSION);
531 timeout = jsonObjectGetNumber( jsonObjectGetKeyConst( cacheObj, "authtime"));
532 osrfCacheSetExpire( timeout, key );
533 jsonObject* payload = jsonNewNumberObject(timeout);
534 evt = oilsNewEvent2(OSRF_LOG_MARK, OILS_EVENT_SUCCESS, payload);
535 jsonObjectFree(payload);
536 jsonObjectFree(cacheObj);
543 int oilsAuthResetTimeout( osrfMethodContext* ctx ) {
544 OSRF_METHOD_VERIFY_CONTEXT(ctx);
545 const char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0));
546 oilsEvent* evt = _oilsAuthResetTimeout(authToken);
547 osrfAppRespondComplete( ctx, oilsEventToJSON(evt) );
553 int oilsAuthSessionRetrieve( osrfMethodContext* ctx ) {
554 OSRF_METHOD_VERIFY_CONTEXT(ctx);
556 const char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0));
557 jsonObject* cacheObj = NULL;
558 oilsEvent* evt = NULL;
562 evt = _oilsAuthResetTimeout(authToken);
564 if( evt && strcmp(evt->event, OILS_EVENT_SUCCESS) ) {
565 osrfAppRespondComplete( ctx, oilsEventToJSON(evt) );
569 osrfLogDebug(OSRF_LOG_MARK, "Retrieving auth session: %s", authToken);
570 char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken );
571 cacheObj = osrfCacheGetObject( key );
573 osrfAppRespondComplete( ctx, jsonObjectGetKeyConst( cacheObj, "userobj"));
574 jsonObjectFree(cacheObj);
576 oilsEvent* evt2 = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_NO_SESSION);
577 osrfAppRespondComplete( ctx, oilsEventToJSON(evt2) ); /* should be event.. */
585 evt = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_NO_SESSION);
586 osrfAppRespondComplete( ctx, oilsEventToJSON(evt) );