1 #include "opensrf/osrf_app_session.h"
2 #include "opensrf/osrf_application.h"
3 #include "opensrf/osrf_settings.h"
4 #include "objson/object.h"
5 #include "opensrf/log.h"
6 #include "oils_utils.h"
7 #include "oils_constants.h"
8 #include "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 int __oilsAuthOPACTimeout = 0;
22 int __oilsAuthStaffTimeout = 0;
23 int __oilsAuthOverrideTimeout = 0;
26 int osrfAppInitialize() {
28 osrfLogInfo(OSRF_LOG_MARK, "Initializing Auth Server...");
30 char* idl_filename = osrf_settings_host_value("/IDL");
31 if (!oilsInitIDL( idl_filename )) exit(1);
34 osrfAppRegisterMethod(
36 "open-ils.auth.authenticate.init",
38 "Start the authentication process and returns the intermediate authentication seed"
39 " PARAMS( username )", 1, 0 );
41 osrfAppRegisterMethod(
43 "open-ils.auth.authenticate.complete",
45 "Completes the authentication process. Returns an object like so: "
46 "{authtoken : <token>, authtime:<time>}, where authtoken is the login "
47 "tokena and authtime is the number of seconds the session will be active"
48 "PARAMS(username, md5sum( seed + password ), type, org_id ) "
49 "type can be one of 'opac','staff', or 'temp' and it defaults to 'staff' "
50 "org_id is the location at which the login should be considered "
51 "active for login timeout purposes" , 1, 0 );
53 osrfAppRegisterMethod(
55 "open-ils.auth.session.retrieve",
56 "oilsAuthSessionRetrieve",
57 "Pass in the auth token and this retrieves the user object. The auth "
58 "timeout is reset when this call is made "
59 "Returns the user object (password blanked) for the given login session "
60 "PARAMS( authToken )", 1, 0 );
62 osrfAppRegisterMethod(
64 "open-ils.auth.session.delete",
65 "oilsAuthSessionDelete",
66 "Destroys the given login session "
67 "PARAMS( authToken )", 1, 0 );
69 osrfAppRegisterMethod(
71 "open-ils.auth.session.reset_timeout",
72 "oilsAuthResetTimeout",
73 "Resets the login timeout for the given session "
74 "Returns an ILS Event with payload = session_timeout of session "
75 "is found, otherwise returns the NO_SESSION event"
76 "PARAMS( authToken )", 1, 0 );
81 int osrfAppChildInit() {
85 int oilsAuthInit( osrfMethodContext* ctx ) {
86 OSRF_METHOD_VERIFY_CONTEXT(ctx);
90 char* username = NULL;
95 if( (username = jsonObjectToSimpleString(jsonObjectGetIndex(ctx->params, 0))) ) {
97 if( strchr( username, ' ' ) ) {
99 /* spaces are not allowed */
100 resp = jsonNewObject("x"); /* 'x' will never be a valid seed */
101 osrfAppRespondComplete( ctx, resp );
105 seed = va_list_to_string( "%d.%d.%s", time(NULL), getpid(), username );
106 key = va_list_to_string( "%s%s", OILS_AUTH_CACHE_PRFX, username );
108 md5seed = md5sum(seed);
109 osrfCachePutString( key, md5seed, 30 );
111 osrfLogDebug( OSRF_LOG_MARK, "oilsAuthInit(): has seed %s and key %s", md5seed, key );
113 resp = jsonNewObject(md5seed);
114 osrfAppRespondComplete( ctx, resp );
122 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 ther permission
135 int oilsAuthCheckLoginPerm(
136 osrfMethodContext* ctx, jsonObject* userObj, 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
165 * Returns 0 otherwise
166 * Returns -1 on error
168 int oilsAuthVerifyPassword(
169 osrfMethodContext* ctx, jsonObject* userObj, char* uname, char* password ) {
172 char* realPassword = oilsFMGetString( userObj, "passwd" ); /**/
173 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 osrfLogDebug(OSRF_LOG_MARK, "oilsAuth retrieved seed from cache: %s", seed );
182 char* maskedPw = md5sum( "%s%s", seed, realPassword );
183 if(!maskedPw) return -1;
184 osrfLogDebug(OSRF_LOG_MARK, "oilsAuth generated masked password %s. "
185 "Testing against provided password %s", maskedPw, password );
187 if( !strcmp( maskedPw, password ) ) ret = 1;
197 * Calculates the login timeout
198 * 1. If orgloc is 1 or greater and has a timeout specified as an
199 * org unit setting, it is used
200 * 2. If orgloc is not valid, we check the org unit auth timeout
201 * setting for the home org unit of the user logging in
202 * 3. If that setting is not defined, we use the configured defaults
204 double oilsAuthGetTimeout( jsonObject* userObj, char* type, double orgloc ) {
206 if(!__oilsAuthOPACTimeout) { /* Load the default timeouts */
208 __oilsAuthOPACTimeout =
210 osrf_settings_host_value_object(
211 "/apps/open-ils.auth/app_settings/default_timeout/opac"));
213 __oilsAuthStaffTimeout =
215 osrf_settings_host_value_object(
216 "/apps/open-ils.auth/app_settings/default_timeout/staff" ));
218 __oilsAuthOverrideTimeout =
220 osrf_settings_host_value_object(
221 "/apps/open-ils.auth/app_settings/default_timeout/temp" ));
224 osrfLogInfo(OSRF_LOG_MARK, "Set default auth timetouts: opac => %d : staff => %d : temp => %d",
225 __oilsAuthOPACTimeout, __oilsAuthStaffTimeout, __oilsAuthOverrideTimeout );
228 char* setting = NULL;
230 double home_ou = jsonObjectGetNumber( oilsFMGetObject( userObj, "home_ou" ) );
231 if(orgloc < 1) orgloc = (int) home_ou;
233 if(!strcmp(type, OILS_AUTH_OPAC))
234 setting = OILS_ORG_SETTING_OPAC_TIMEOUT;
235 else if(!strcmp(type, OILS_AUTH_STAFF))
236 setting = OILS_ORG_SETTING_STAFF_TIMEOUT;
237 else if(!strcmp(type, OILS_AUTH_TEMP))
238 setting = OILS_ORG_SETTING_TEMP_TIMEOUT;
240 char* timeout = oilsUtilsFetchOrgSetting( orgloc, setting );
243 if( orgloc != home_ou ) {
244 osrfLogDebug(OSRF_LOG_MARK, "Auth timeout not defined for org %d, "
245 "trying home_ou %d", orgloc, home_ou );
246 timeout = oilsUtilsFetchOrgSetting( (int) home_ou, setting );
249 if(!strcmp(type, OILS_AUTH_STAFF)) return __oilsAuthStaffTimeout;
250 if(!strcmp(type, OILS_AUTH_TEMP)) return __oilsAuthOverrideTimeout;
251 return __oilsAuthOPACTimeout;
255 double t = atof(timeout);
260 /* Adds the authentication token to the user cache. The timeout for the
261 * auth token is based on the type of login as well as (if type=='opac')
262 * the org location id.
263 * Returns the event that should be returned to the user.
264 * Event must be freed
266 oilsEvent* oilsAuthHandleLoginOK(
267 jsonObject* userObj, char* uname, char* type, double orgloc, char* workstation ) {
272 char* wsorg = jsonObjectToSimpleString(oilsFMGetObject(userObj, "ws_ou"));
273 if(wsorg) { /* if there is a workstation, use it for the timeout */
274 osrfLogDebug( OSRF_LOG_MARK,
275 "Auth session trying workstation id %d for auth timeout", atoi(wsorg));
276 timeout = oilsAuthGetTimeout( userObj, type, atoi(wsorg) );
279 osrfLogDebug( OSRF_LOG_MARK,
280 "Auth session trying org from param [%d] for auth timeout", orgloc );
281 timeout = oilsAuthGetTimeout( userObj, type, orgloc );
283 osrfLogDebug(OSRF_LOG_MARK, "Auth session timeout for %s: %lf", uname, timeout );
285 char* string = va_list_to_string(
286 "%d.%d.%s", getpid(), time(NULL), uname );
287 char* authToken = md5sum(string);
288 char* authKey = va_list_to_string(
289 "%s%s", OILS_AUTH_CACHE_PRFX, authToken );
291 char* ws = (workstation) ? workstation : "";
292 osrfLogActivity(OSRF_LOG_MARK,
293 "successful login: username=%s, authtoken=%s, workstation=%s", uname, authToken, ws );
295 oilsFMSetString( userObj, "passwd", "" );
296 jsonObject* cacheObj = jsonParseStringFmt("{\"authtime\": %lf}", timeout);
297 jsonObjectSetKey( cacheObj, "userobj", jsonObjectClone(userObj));
299 osrfCachePutObject( authKey, cacheObj, timeout );
300 jsonObjectFree(cacheObj);
301 osrfLogInternal(OSRF_LOG_MARK, "oilsAuthComplete(): Placed user object into cache");
302 jsonObject* payload = jsonParseStringFmt(
303 "{ \"authtoken\": \"%s\", \"authtime\": %lf }", authToken, timeout );
305 response = oilsNewEvent2( OSRF_LOG_MARK, OILS_EVENT_SUCCESS, payload );
306 free(string); free(authToken); free(authKey);
307 jsonObjectFree(payload);
312 oilsEvent* oilsAuthVerifyWorkstation(
313 osrfMethodContext* ctx, jsonObject* userObj, char* ws ) {
314 osrfLogInfo(OSRF_LOG_MARK, "Attaching workstation to user at login: %s", ws);
315 jsonObject* workstation = oilsUtilsFetchWorkstationByName(ws);
316 if(!workstation) return oilsNewEvent(OSRF_LOG_MARK, "WORKSTATION_NOT_FOUND");
317 long wsid = oilsFMGetObjectId(workstation);
318 LONG_TO_STRING(wsid);
319 char* orgid = oilsFMGetString(workstation, "owning_lib");
320 oilsFMSetString(userObj, "wsid", LONGSTR);
321 oilsFMSetString(userObj, "ws_ou", orgid);
328 /* see if the card used to login is marked as barred */
329 static oilsEvent* oilsAuthCheckCard( osrfMethodContext* ctx, jsonObject* userObj, char* barcode) {
330 if(!(ctx && userObj && barcode)) return NULL;
331 osrfLogDebug(OSRF_LOG_MARK, "Checking to see if barcode %s is active", barcode);
333 jsonObject* params = jsonParseStringFmt("{\"barcode\":\"%s\"}", barcode);
334 jsonObject* card = oilsUtilsQuickReq(
335 "open-ils.cstore", "open-ils.cstore.direct.actor.card.search", params );
337 char* active = oilsFMGetString(card, "active");
338 if( ! oilsUtilsIsDBTrue(active) ) {
340 osrfLogInfo(OSRF_LOG_MARK, "barcode %s is not active, returning event", barcode);
341 return oilsNewEvent(OSRF_LOG_MARK, "PATRON_CARD_INACTIVE");
350 int oilsAuthComplete( osrfMethodContext* ctx ) {
351 OSRF_METHOD_VERIFY_CONTEXT(ctx);
353 jsonObject* args = jsonObjectGetIndex(ctx->params, 0);
355 char* uname = jsonObjectGetString(jsonObjectGetKey(args, "username"));
356 char* password = jsonObjectGetString(jsonObjectGetKey(args, "password"));
357 char* type = jsonObjectGetString(jsonObjectGetKey(args, "type"));
358 double orgloc = jsonObjectGetNumber(jsonObjectGetKey(args, "org"));
359 char* workstation = jsonObjectGetString(jsonObjectGetKey(args, "workstation"));
360 char* barcode = jsonObjectToSimpleString(jsonObjectGetKey(args, "barcode"));
362 char* ws = (workstation) ? workstation : "";
365 if(!type) type = OILS_AUTH_STAFF;
367 if( !( (uname || barcode) && password) ) {
369 return osrfAppRequestRespondException( ctx->session, ctx->request,
370 "username/barocode and password required for method: %s", ctx->method->name );
373 oilsEvent* response = NULL;
374 jsonObject* userObj = NULL;
376 if(uname) userObj = oilsUtilsFetchUserByUsername( uname );
377 else if(barcode) userObj = oilsUtilsFetchUserByBarcode( barcode );
380 response = oilsNewEvent( OSRF_LOG_MARK, OILS_EVENT_AUTH_FAILED );
381 osrfLogInfo(OSRF_LOG_MARK, "failed login: username=%s, barcode=%s, workstation=%s", uname, barcode, ws );
382 osrfAppRespondComplete( ctx, oilsEventToJSON(response) );
383 oilsEventFree(response);
388 /* first let's see if they have the right credentials */
390 if(uname) passOK = oilsAuthVerifyPassword( ctx, userObj, uname, password );
392 passOK = oilsAuthVerifyPassword( ctx, userObj, barcode, password );
399 /* first see if their account is inactive */
400 char* active = oilsFMGetString(userObj, "active");
401 if( !oilsUtilsIsDBTrue(active) ) {
402 response = oilsNewEvent(OSRF_LOG_MARK, "PATRON_INACTIVE");
403 osrfAppRespondComplete( ctx, oilsEventToJSON(response) );
404 oilsEventFree(response);
411 /* then see if the barcode they used is active */
412 if( barcode && (response = oilsAuthCheckCard( ctx, userObj, barcode )) ) {
413 osrfAppRespondComplete( ctx, oilsEventToJSON(response) );
414 oilsEventFree(response);
420 /* check to see if the user is even allowed to login */
421 if( oilsAuthCheckLoginPerm( ctx, userObj, type ) == -1 ) {
422 jsonObjectFree(userObj);
428 /* if a workstation is defined, flesh the user with the workstation info */
429 if( workstation != NULL ) {
430 osrfLogDebug(OSRF_LOG_MARK, "Workstation is %s", workstation);
431 response = oilsAuthVerifyWorkstation( ctx, userObj, workstation );
433 jsonObjectFree(userObj);
434 osrfAppRespondComplete( ctx, oilsEventToJSON(response) );
435 oilsEventFree(response);
441 /* otherwise, use the home org as the workstation org on the user */
442 char* orgid = oilsFMGetString(userObj, "home_ou");
443 oilsFMSetString(userObj, "ws_ou", orgid);
449 uname = oilsFMGetString( userObj, "usrname" );
454 response = oilsAuthHandleLoginOK( userObj, uname, type, orgloc, workstation );
457 response = oilsNewEvent( OSRF_LOG_MARK, OILS_EVENT_AUTH_FAILED );
458 osrfLogInfo(OSRF_LOG_MARK, "failed login: username=%s, barcode=%s, workstation=%s", uname, barcode, ws );
461 jsonObjectFree(userObj);
462 osrfAppRespondComplete( ctx, oilsEventToJSON(response) );
463 oilsEventFree(response);
466 if(freeuname) free(uname);
473 int oilsAuthSessionDelete( osrfMethodContext* ctx ) {
474 OSRF_METHOD_VERIFY_CONTEXT(ctx);
476 char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0) );
477 jsonObject* resp = NULL;
480 osrfLogDebug(OSRF_LOG_MARK, "Removing auth session: %s", authToken );
481 char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken ); /**/
482 osrfCacheRemove(key);
483 resp = jsonNewObject(authToken); /**/
487 osrfAppRespondComplete( ctx, resp );
488 jsonObjectFree(resp);
492 /** Resets the auth login timeout
493 * @return The event object, OILS_EVENT_SUCCESS, or OILS_EVENT_NO_SESSION
495 oilsEvent* _oilsAuthResetTimeout( char* authToken ) {
496 if(!authToken) return NULL;
498 oilsEvent* evt = NULL;
501 osrfLogDebug(OSRF_LOG_MARK, "Resetting auth timeout for session %s", authToken);
502 char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken );
503 jsonObject* cacheObj = osrfCacheGetObject( key );
506 osrfLogError(OSRF_LOG_MARK, "No user in the cache exists with key %s", key);
507 evt = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_NO_SESSION);
511 timeout = jsonObjectGetNumber( jsonObjectGetKey( cacheObj, "authtime"));
512 osrfCacheSetExpire( timeout, key );
513 jsonObject* payload = jsonNewNumberObject(timeout);
514 evt = oilsNewEvent2(OSRF_LOG_MARK, OILS_EVENT_SUCCESS, payload);
515 jsonObjectFree(payload);
516 jsonObjectFree(cacheObj);
523 int oilsAuthResetTimeout( osrfMethodContext* ctx ) {
524 OSRF_METHOD_VERIFY_CONTEXT(ctx);
525 char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0));
526 oilsEvent* evt = _oilsAuthResetTimeout(authToken);
527 osrfAppRespondComplete( ctx, oilsEventToJSON(evt) );
533 int oilsAuthSessionRetrieve( osrfMethodContext* ctx ) {
534 OSRF_METHOD_VERIFY_CONTEXT(ctx);
536 char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0));
537 jsonObject* cacheObj = NULL;
538 oilsEvent* evt = NULL;
542 evt = _oilsAuthResetTimeout(authToken);
544 if( evt && strcmp(evt->event, OILS_EVENT_SUCCESS) ) {
545 osrfAppRespondComplete( ctx, oilsEventToJSON(evt) );
550 osrfLogDebug(OSRF_LOG_MARK, "Retrieving auth session: %s", authToken);
551 char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken );
552 cacheObj = osrfCacheGetObject( key );
554 osrfAppRespondComplete( ctx, jsonObjectGetKey( cacheObj, "userobj"));
555 jsonObjectFree(cacheObj);
557 oilsEvent* evt = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_NO_SESSION);
558 osrfAppRespondComplete( ctx, oilsEventToJSON(evt) ); /* should be event.. */
566 evt = oilsNewEvent(OSRF_LOG_MARK, OILS_EVENT_NO_SESSION);
567 osrfAppRespondComplete( ctx, oilsEventToJSON(evt) );