]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/c-apps/oils_auth_internal.c
LP#1468422 New open-ils.auth_internal service
[Evergreen.git] / Open-ILS / src / c-apps / oils_auth_internal.c
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"
9
10 #define OILS_AUTH_CACHE_PRFX "oils_auth_"
11 #define OILS_AUTH_COUNT_SFFX "_count"
12
13 #define MODULENAME "open-ils.auth_internal"
14
15 #define OILS_AUTH_OPAC "opac"
16 #define OILS_AUTH_STAFF "staff"
17 #define OILS_AUTH_TEMP "temp"
18 #define OILS_AUTH_PERSIST "persist"
19
20 // Default time for extending a persistent session: ten minutes
21 #define DEFAULT_RESET_INTERVAL 10 * 60
22
23 int osrfAppInitialize();
24 int osrfAppChildInit();
25
26 static long _oilsAuthOPACTimeout = 0;
27 static long _oilsAuthStaffTimeout = 0;
28 static long _oilsAuthOverrideTimeout = 0;
29 static long _oilsAuthPersistTimeout = 0;
30
31 /**
32     @brief Initialize the application by registering functions for method calls.
33     @return Zero on success, 1 on error.
34 */
35 int osrfAppInitialize() {
36
37     osrfLogInfo(OSRF_LOG_MARK, "Initializing Auth Internal Server...");
38
39     /* load and parse the IDL */
40     /* return non-zero to indicate error */
41     if (!oilsInitIDL(NULL)) return 1; 
42
43     osrfAppRegisterMethod(
44         MODULENAME,
45         "open-ils.auth_internal.session.create",
46         "oilsAutInternalCreateSession",
47         "Adds a user to the authentication cache to indicate "
48         "the user is authenticated", 1, 0 
49     );
50
51     return 0;
52 }
53
54 /**
55     @brief Dummy placeholder for initializing a server drone.
56
57     There is nothing to do, so do nothing.
58 */
59 int osrfAppChildInit() {
60     return 0;
61 }
62
63
64 /**
65     @brief Determine the login timeout.
66     @param userObj Pointer to an object describing the user.
67     @param type Pointer to one of four possible character strings identifying the login type.
68     @param orgloc Org unit to use for settings lookups (negative or zero means unspecified)
69     @return The length of the timeout, in seconds.
70
71     The default timeout value comes from the configuration file, and
72     depends on the login type.
73
74     The default may be overridden by a corresponding org unit setting.
75     The @a orgloc parameter says what org unit to use for the lookup.
76     If @a orgloc <= 0, or if the lookup for @a orgloc yields no result,
77     we look up the setting for the user's home org unit instead (except
78     that if it's the same as @a orgloc we don't bother repeating the
79     lookup).
80
81     Whether defined in the config file or in an org unit setting, a
82     timeout value may be expressed as a raw number (i.e. all digits,
83     possibly with leading and/or trailing white space) or as an interval
84     string to be translated into seconds by PostgreSQL.
85 */
86 static long oilsAuthGetTimeout(
87     const jsonObject* userObj, const char* type, int orgloc) {
88
89     if(!_oilsAuthOPACTimeout) { /* Load the default timeouts */
90
91         jsonObject* value_obj;
92
93         value_obj = osrf_settings_host_value_object(
94             "/apps/open-ils.auth/app_settings/default_timeout/opac" );
95         _oilsAuthOPACTimeout = oilsUtilsIntervalToSeconds( jsonObjectGetString( value_obj ));
96         jsonObjectFree(value_obj);
97         if( -1 == _oilsAuthOPACTimeout ) {
98             osrfLogWarning( OSRF_LOG_MARK, "Invalid default timeout for OPAC logins" );
99             _oilsAuthOPACTimeout = 0;
100         }
101
102         value_obj = osrf_settings_host_value_object(
103             "/apps/open-ils.auth/app_settings/default_timeout/staff" );
104         _oilsAuthStaffTimeout = oilsUtilsIntervalToSeconds( jsonObjectGetString( value_obj ));
105         jsonObjectFree(value_obj);
106         if( -1 == _oilsAuthStaffTimeout ) {
107             osrfLogWarning( OSRF_LOG_MARK, "Invalid default timeout for staff logins" );
108             _oilsAuthStaffTimeout = 0;
109         }
110
111         value_obj = osrf_settings_host_value_object(
112             "/apps/open-ils.auth/app_settings/default_timeout/temp" );
113         _oilsAuthOverrideTimeout = oilsUtilsIntervalToSeconds( jsonObjectGetString( value_obj ));
114         jsonObjectFree(value_obj);
115         if( -1 == _oilsAuthOverrideTimeout ) {
116             osrfLogWarning( OSRF_LOG_MARK, "Invalid default timeout for temp logins" );
117             _oilsAuthOverrideTimeout = 0;
118         }
119
120         value_obj = osrf_settings_host_value_object(
121             "/apps/open-ils.auth/app_settings/default_timeout/persist" );
122         _oilsAuthPersistTimeout = oilsUtilsIntervalToSeconds( jsonObjectGetString( value_obj ));
123         jsonObjectFree(value_obj);
124         if( -1 == _oilsAuthPersistTimeout ) {
125             osrfLogWarning( OSRF_LOG_MARK, "Invalid default timeout for persist logins" );
126             _oilsAuthPersistTimeout = 0;
127         }
128
129         osrfLogInfo(OSRF_LOG_MARK, "Set default auth timeouts: "
130             "opac => %ld : staff => %ld : temp => %ld : persist => %ld",
131             _oilsAuthOPACTimeout, _oilsAuthStaffTimeout,
132             _oilsAuthOverrideTimeout, _oilsAuthPersistTimeout );
133     }
134
135     int home_ou = (int) jsonObjectGetNumber( oilsFMGetObject( userObj, "home_ou" ));
136     if(orgloc < 1)
137         orgloc = home_ou;
138
139     char* setting = NULL;
140     long default_timeout = 0;
141
142     if( !strcmp( type, OILS_AUTH_OPAC )) {
143         setting = OILS_ORG_SETTING_OPAC_TIMEOUT;
144         default_timeout = _oilsAuthOPACTimeout;
145     } else if( !strcmp( type, OILS_AUTH_STAFF )) {
146         setting = OILS_ORG_SETTING_STAFF_TIMEOUT;
147         default_timeout = _oilsAuthStaffTimeout;
148     } else if( !strcmp( type, OILS_AUTH_TEMP )) {
149         setting = OILS_ORG_SETTING_TEMP_TIMEOUT;
150         default_timeout = _oilsAuthOverrideTimeout;
151     } else if( !strcmp( type, OILS_AUTH_PERSIST )) {
152         setting = OILS_ORG_SETTING_PERSIST_TIMEOUT;
153         default_timeout = _oilsAuthPersistTimeout;
154     }
155
156     // Get the org unit setting, if there is one.
157     char* timeout = oilsUtilsFetchOrgSetting( orgloc, setting );
158     if(!timeout) {
159         if( orgloc != home_ou ) {
160             osrfLogDebug(OSRF_LOG_MARK, "Auth timeout not defined for org %d, "
161                 "trying home_ou %d", orgloc, home_ou );
162             timeout = oilsUtilsFetchOrgSetting( home_ou, setting );
163         }
164     }
165
166     if(!timeout)
167         return default_timeout;   // No override from org unit setting
168
169     // Translate the org unit setting to a number
170     long t;
171     if( !*timeout ) {
172         osrfLogWarning( OSRF_LOG_MARK,
173             "Timeout org unit setting is an empty string for %s login; using default",
174             timeout, type );
175         t = default_timeout;
176     } else {
177         // Treat timeout string as an interval, and convert it to seconds
178         t = oilsUtilsIntervalToSeconds( timeout );
179         if( -1 == t ) {
180             // Unable to convert; possibly an invalid interval string
181             osrfLogError( OSRF_LOG_MARK,
182                 "Unable to convert timeout interval \"%s\" for %s login; using default",
183                 timeout, type );
184             t = default_timeout;
185         }
186     }
187
188     free(timeout);
189     return t;
190 }
191
192 /**
193  * Verify workstation exists and stuff it into the user object to be cached
194  */
195 static oilsEvent* oilsAuthVerifyWorkstation(
196         const osrfMethodContext* ctx, jsonObject* userObj, const char* ws ) {
197
198     jsonObject* workstation = oilsUtilsFetchWorkstationByName(ws);
199
200     if(!workstation || workstation->type == JSON_NULL) {
201         jsonObjectFree(workstation);
202         return oilsNewEvent(OSRF_LOG_MARK, "WORKSTATION_NOT_FOUND");
203     }
204
205     long wsid = oilsFMGetObjectId(workstation);
206     LONG_TO_STRING(wsid);
207     char* orgid = oilsFMGetString(workstation, "owning_lib");
208     oilsFMSetString(userObj, "wsid", LONGSTR);
209     oilsFMSetString(userObj, "ws_ou", orgid);
210     free(orgid);
211     jsonObjectFree(workstation);
212     return NULL;
213 }
214
215
216 /**
217     @brief Implement the session create method
218     @param ctx The method context.
219     @return -1 upon error; zero if successful, and if a STATUS message has 
220     been sent to the client to indicate completion; a positive integer if 
221     successful but no such STATUS message has been sent.
222
223     Method parameters:
224     - a hash with some combination of the following elements:
225         - "user_id"     -- actor.usr (au) ID for the user to cache.
226         - "org_unit"    -- actor.org_unit (aou) ID representing the physical 
227                            location / context used for timeout, etc. settings.
228         - "login_type"  -- login type (opac, staff, temp, persist)
229         - "workstation" -- workstation name
230
231 */
232 int oilsAutInternalCreateSession(osrfMethodContext* ctx) {
233     OSRF_METHOD_VERIFY_CONTEXT(ctx);
234
235     const jsonObject* args  = jsonObjectGetIndex(ctx->params, 0);
236
237     const char* user_id     = jsonObjectGetString(jsonObjectGetKeyConst(args, "user_id"));
238     const char* org_unit    = jsonObjectGetString(jsonObjectGetKeyConst(args, "org_unit"));
239     const char* login_type  = jsonObjectGetString(jsonObjectGetKeyConst(args, "login_type"));
240     const char* workstation = jsonObjectGetString(jsonObjectGetKeyConst(args, "workstation"));
241
242     if ( !(user_id && login_type && org_unit) ) {
243         return osrfAppRequestRespondException( ctx->session, ctx->request,
244             "Missing parameters for method: %s", ctx->method->name );
245     }
246
247         oilsEvent* response = NULL;
248
249     // fetch the user object
250     jsonObject* idParam = jsonNewNumberStringObject(user_id);
251     jsonObject* userObj = oilsUtilsCStoreReq(
252         "open-ils.cstore.direct.actor.user.retrieve", idParam);
253     jsonObjectFree(idParam);
254
255     if (!userObj) {
256         return osrfAppRequestRespondException(ctx->session, 
257             ctx->request, "No user found with ID %s", user_id);
258     }
259
260     // If a workstation is defined, add the workstation info
261     if (workstation) {
262         response = oilsAuthVerifyWorkstation(ctx, userObj, workstation);
263         if (response) {
264             jsonObjectFree(userObj);
265             osrfAppRespondComplete(ctx, oilsEventToJSON(response));
266             oilsEventFree(response);
267             return 0;
268         }
269
270     } else {
271         // Otherwise, use the home org as the workstation org on the user
272         char* orgid = oilsFMGetString(userObj, "home_ou");
273         oilsFMSetString(userObj, "ws_ou", orgid);
274         free(orgid);
275     }
276
277     // determine the auth/cache timeout
278     long timeout = oilsAuthGetTimeout(userObj, login_type, atoi(org_unit));
279
280     char* string = va_list_to_string("%d.%ld.%ld", 
281         (long) getpid(), time(NULL), oilsFMGetObjectId(userObj));
282     char* authToken = md5sum(string);
283     char* authKey = va_list_to_string(
284         "%s%s", OILS_AUTH_CACHE_PRFX, authToken);
285
286     oilsFMSetString(userObj, "passwd", "");
287     jsonObject* cacheObj = jsonParseFmt("{\"authtime\": %ld}", timeout);
288     jsonObjectSetKey(cacheObj, "userobj", jsonObjectClone(userObj));
289
290     if( !strcmp(login_type, OILS_AUTH_PERSIST)) {
291         // Add entries for endtime and reset_interval, so that we can gracefully
292         // extend the session a bit if the user is active toward the end of the 
293         // timeout originally specified.
294         time_t endtime = time( NULL ) + timeout;
295         jsonObjectSetKey(cacheObj, "endtime", 
296             jsonNewNumberObject( (double) endtime ));
297
298         // Reset interval is hard-coded for now, but if we ever want to make it
299         // configurable, this is the place to do it:
300         jsonObjectSetKey(cacheObj, "reset_interval",
301             jsonNewNumberObject( (double) DEFAULT_RESET_INTERVAL));
302     }
303
304     osrfCachePutObject(authKey, cacheObj, (time_t) timeout);
305     jsonObjectFree(cacheObj);
306     jsonObject* payload = jsonParseFmt(
307         "{\"authtoken\": \"%s\", \"authtime\": %ld}", authToken, timeout);
308
309     response = oilsNewEvent2(OSRF_LOG_MARK, OILS_EVENT_SUCCESS, payload);
310     free(string); free(authToken); free(authKey);
311     jsonObjectFree(payload);
312
313     jsonObjectFree(userObj);
314     osrfAppRespondComplete(ctx, oilsEventToJSON(response));
315     oilsEventFree(response);
316
317     return 0;
318 }
319