auth code now returns { authtoken : <t> , authtime : <sec> } as the payload
[Evergreen.git] / Open-ILS / src / c-apps / oils_auth.c
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"
9
10 #define OILS_AUTH_CACHE_PRFX "oils_auth_"
11
12 #define MODULENAME "open-ils.auth"
13
14 int osrfAppInitialize();
15 int osrfAppChildInit();
16
17 int __oilsAuthOPACTimeout = 0;
18 int __oilsAuthStaffTimeout = 0;
19
20
21 int osrfAppInitialize() {
22
23         osrfAppRegisterMethod( 
24                 MODULENAME, 
25                 "open-ils.auth.authenticate.init", 
26                 "oilsAuthInit", 
27                 "Start the authentication process and returns the intermediate authentication seed"
28                 " PARAMS( username )", 1, 0 );
29
30         osrfAppRegisterMethod( 
31                 MODULENAME, 
32                 "open-ils.auth.authenticate.complete", 
33                 "oilsAuthComplete", 
34                 "Completes the authentication process.  Returns an object like so: "
35                 "{authtoken : <token>, authtime:<time>}, where authtoken is the login "
36                 "tokena and authtime is the number of seconds the session will be active"
37                 "PARAMS(username, md5sum( seed + password ), type )", 2, 0 );
38
39         osrfAppRegisterMethod( 
40                 MODULENAME, 
41                 "open-ils.auth.session.retrieve", 
42                 "oilsAuthSessionRetrieve", 
43                 "Returns the user object (password blanked) for the given login session "
44                 "PARAMS( authToken )", 1, 0 );
45
46         osrfAppRegisterMethod( 
47                 MODULENAME, 
48                 "open-ils.auth.session.delete", 
49                 "oilsAuthSessionDelete", 
50                 "Destroys the given login session "
51                 "PARAMS( authToken )",  1, 0 );
52
53         return 0;
54 }
55
56 int osrfAppChildInit() {
57         return 0;
58 }
59
60 int oilsAuthInit( osrfMethodContext* ctx ) {
61         OSRF_METHOD_VERIFY_CONTEXT(ctx); 
62
63         jsonObject* resp;
64         char* username = NULL;
65         char* seed = NULL;
66         char* md5seed = NULL;
67         char* key = NULL;
68
69         if( (username = jsonObjectGetString(jsonObjectGetIndex(ctx->params, 0))) ) {
70
71                 seed = va_list_to_string( "%d.%d.%s", time(NULL), getpid(), username );
72                 key = va_list_to_string( "%s%s", OILS_AUTH_CACHE_PRFX, username );
73
74                 md5seed = md5sum(seed);
75                 osrfCachePutString( key, md5seed, 30 );
76
77                 osrfLogDebug( "oilsAuthInit(): has seed %s and key %s", md5seed, key );
78
79                 resp = jsonNewObject(md5seed);  
80                 osrfAppRespondComplete( ctx, resp );
81
82                 jsonObjectFree(resp);
83                 free(seed);
84                 free(md5seed);
85                 free(key);
86                 return 0;
87         }
88
89         return -1;
90 }
91
92 /** Verifies that the user has permission to login with the 
93  * given type.  If the permission fails, an oilsEvent is returned
94  * to the caller.
95  * @return -1 if the permission check failed, 0 if ther permission
96  * is granted
97  */
98 int oilsAuthCheckLoginPerm( 
99                 osrfMethodContext* ctx, jsonObject* userObj, char* type ) {
100
101         if(!(userObj && type)) return -1;
102         oilsEvent* perm = NULL;
103
104         if(!strcmp(type, "opac")) {
105                 char* permissions[] = { "OPAC_LOGIN" };
106                 perm = oilsUtilsCheckPerms( oilsFMGetObjectId( userObj ), -1, permissions, 1 );
107
108         } else if(!strcmp(type, "staff")) {
109                 char* permissions[] = { "STAFF_LOGIN" };
110                 perm = oilsUtilsCheckPerms( oilsFMGetObjectId( userObj ), -1, permissions, 1 );
111         }
112
113         if(perm) {
114                 osrfAppRespondComplete( ctx, oilsEventToJSON(perm) ); 
115                 oilsEventFree(perm);
116                 return -1;
117         }
118
119         return 0;
120 }
121
122 /**
123  * Returns 1 if the password provided matches the user's real password
124  * Returns 0 otherwise
125  * Returns -1 on error
126  */
127 int oilsAuthVerifyPassword( 
128                 osrfMethodContext* ctx, jsonObject* userObj, char* uname, char* password ) {
129
130         int ret = 0;
131         char* realPassword = oilsFMGetString( userObj, "passwd" ); /**/
132         char* seed = osrfCacheGetString( "%s%s", OILS_AUTH_CACHE_PRFX, uname ); /**/
133
134         if(!seed) {
135                 return osrfAppRequestRespondException( ctx->session,
136                         ctx->request, "No authentication seed found. "
137                         "open-ils.auth.authenticate.init must be called first");
138         }
139
140         osrfLogDebug( "oilsAuth retrieved seed from cache: %s", seed );
141         char* maskedPw = md5sum( "%s%s", seed, realPassword );
142         if(!maskedPw) return -1;
143         osrfLogDebug( "oilsAuth generated masked password %s. "
144                         "Testing against provided password %s", maskedPw, password );
145
146         if( !strcmp( maskedPw, password ) ) ret = 1;
147
148         free(realPassword);
149         free(seed);
150         free(maskedPw);
151
152         return ret;
153 }
154
155 /**
156  * Calculates the login timeout
157  * 1. If orgloc is 1 or greater and has a timeout specified as an 
158  * org unit setting, it is used
159  * 2. If orgloc is not valid, we check the org unit auth timeout 
160  * setting for the home org unit of the user logging in
161  * 3. If that setting is not defined, we use the configured defaults
162  */
163 double oilsAuthGetTimeout( jsonObject* userObj, char* type, double orgloc ) {
164
165         if(!__oilsAuthOPACTimeout) { /* Load the default timeouts */
166
167                 __oilsAuthOPACTimeout = 
168                         jsonObjectGetNumber( 
169                                 osrf_settings_host_value_object( 
170                                         "/apps/open-ils.auth/app_settings/default_timeout/opac"));
171
172                 __oilsAuthStaffTimeout = 
173                         jsonObjectGetNumber( 
174                                 osrf_settings_host_value_object( 
175                                         "/apps/open-ils.auth/app_settings/default_timeout/staff" ));
176
177                 osrfLogInfo("Set default auth timetouts: opac => %d : staff => %d",
178                                 __oilsAuthOPACTimeout, __oilsAuthStaffTimeout );
179         }
180
181         char* setting = NULL;
182
183         double home_ou = jsonObjectGetNumber( oilsFMGetObject( userObj, "home_ou" ) );
184         if(orgloc < 1) orgloc = (int) home_ou;
185
186         if(!strcmp(type, "opac")) 
187                 setting = OILS_ORG_SETTING_OPAC_TIMEOUT;
188         else if(!strcmp(type, "staff")) 
189                 setting = OILS_ORG_SETTING_STAFF_TIMEOUT;
190
191         char* timeout = oilsUtilsFetchOrgSetting( orgloc, setting );
192
193         if(!timeout) {
194                 if( orgloc != home_ou ) {
195                         osrfLogDebug("Auth timeout not defined for org %d, "
196                                                                 "trying home_ou %d", orgloc, home_ou );
197                         timeout = oilsUtilsFetchOrgSetting( (int) home_ou, setting );
198                 }
199                 if(!timeout) {
200                         if(!strcmp(type, "staff")) return __oilsAuthStaffTimeout;
201                         return __oilsAuthOPACTimeout;
202                 }
203         }
204         double t = atof(timeout);
205         free(timeout);
206         return t ;
207 }
208
209 /* Adds the authentication token to the user cache.  The timeout for the 
210  * auth token is based on the type of login as well as (if type=='opac') 
211  * the org location id.
212  * Returns the event that should be returned to the user.  
213  * Event must be freed
214  */
215 oilsEvent* oilsAuthHandleLoginOK( 
216                 jsonObject* userObj, char* uname, char* type, double orgloc ) { 
217                 
218         oilsEvent* response;
219         osrfLogActivity( "User %s successfully logged in", uname );
220
221         double timeout = oilsAuthGetTimeout( userObj, type, orgloc );
222         osrfLogDebug("Auth session timeout for %s: %lf", uname, timeout );
223
224         char* string = va_list_to_string( 
225                         "%d.%d.%s", getpid(), time(NULL), uname ); 
226         char* authToken = md5sum(string); 
227         char* authKey = va_list_to_string( 
228                         "%s%s", OILS_AUTH_CACHE_PRFX, authToken ); 
229
230         oilsFMSetString( userObj, "passwd", "" );
231         osrfCachePutObject( authKey, userObj, timeout ); 
232         osrfLogInternal("oilsAuthComplete(): Placed user object into cache");
233         jsonObject* payload = jsonParseString(
234                 "{ \"authtoken\": \"%s\", \"authtime\": %lf }", authToken, timeout );
235
236         response = oilsNewEvent2( OILS_EVENT_SUCCESS, payload );
237         free(string); free(authToken); free(authKey);
238         jsonObjectFree(payload);
239         return response;
240 }
241
242
243
244 int oilsAuthComplete( osrfMethodContext* ctx ) {
245         OSRF_METHOD_VERIFY_CONTEXT(ctx); 
246
247         char* uname             = jsonObjectGetString(jsonObjectGetIndex(ctx->params, 0));
248         char* password = jsonObjectGetString(jsonObjectGetIndex(ctx->params, 1));
249         char* type              = jsonObjectGetString(jsonObjectGetIndex(ctx->params, 2));
250         double orgloc   = jsonObjectGetNumber(jsonObjectGetIndex(ctx->params, 3));
251
252         if(!type) type = "staff";
253
254         if( !(uname && password) ) {
255                 return osrfAppRequestRespondException( ctx->session, ctx->request, 
256                         "username and password required for method: %s", ctx->method->name );
257         }
258
259         oilsEvent* response = NULL;
260         jsonObject* userObj = oilsUtilsFetchUserByUsername( uname ); 
261         
262         if(!userObj) { 
263                 response = oilsNewEvent( OILS_EVENT_AUTH_FAILED );
264                 osrfAppRespondComplete( ctx, oilsEventToJSON(response) ); 
265                 oilsEventFree(response);
266                 return 0;
267         }
268
269         /* check to see if the user is allowed to login */
270         if( oilsAuthCheckLoginPerm( ctx, userObj, type ) == -1 ) {
271                 jsonObjectFree(userObj);
272                 return 0;
273         }
274
275         int passOK = oilsAuthVerifyPassword( ctx, userObj, uname, password );
276         if( passOK < 0 ) return passOK;
277
278         if( passOK ) {
279                 response = oilsAuthHandleLoginOK( userObj, uname, type, orgloc );
280
281         } else {
282                 response = oilsNewEvent( OILS_EVENT_AUTH_FAILED );
283                 osrfLogInfo( "Login failed for for %s", uname );
284         }
285
286         jsonObjectFree(userObj);
287         osrfAppRespondComplete( ctx, oilsEventToJSON(response) ); 
288         oilsEventFree(response);
289
290         return 0;
291 }
292
293 int oilsAuthSessionRetrieve( osrfMethodContext* ctx ) {
294         OSRF_METHOD_VERIFY_CONTEXT(ctx); 
295
296         char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0));
297         jsonObject* userObj = NULL;
298
299         if( authToken ){
300                 char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken ); /**/
301                 userObj = osrfCacheGetObject( key ); /**/
302                 free(key);
303         }
304
305         osrfAppRespondComplete( ctx, userObj );
306         jsonObjectFree(userObj);
307         return 0;
308 }
309
310 int oilsAuthSessionDelete( osrfMethodContext* ctx ) {
311         OSRF_METHOD_VERIFY_CONTEXT(ctx); 
312
313         char* authToken = jsonObjectGetString( jsonObjectGetIndex(ctx->params, 0) );
314         jsonObject* resp = NULL;
315
316         if( authToken ) {
317                 char* key = va_list_to_string("%s%s", OILS_AUTH_CACHE_PRFX, authToken ); /**/
318                 osrfCacheRemove(key);
319                 resp = jsonNewObject(authToken); /**/
320                 free(key);
321         }
322
323         osrfAppRespondComplete( ctx, resp );
324         jsonObjectFree(resp);
325         return 0;
326 }
327
328
329