]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/c-apps/oils_utils.c
LP#1468422 Auth efficiency improvements
[Evergreen.git] / Open-ILS / src / c-apps / oils_utils.c
1 #include <ctype.h>
2 #include <limits.h>
3 #include "openils/oils_utils.h"
4 #include "openils/oils_idl.h"
5
6 osrfHash* oilsInitIDL(const char* idl_filename) {
7
8         char* freeable_filename = NULL;
9         const char* filename;
10
11         if(idl_filename)
12                 filename = idl_filename;
13         else {
14                 freeable_filename = osrf_settings_host_value("/IDL");
15                 filename = freeable_filename;
16         }
17
18         if (!filename) {
19                 osrfLogError(OSRF_LOG_MARK, "No settings config for '/IDL'");
20                 return NULL;
21         }
22
23         osrfLogInfo(OSRF_LOG_MARK, "Parsing IDL %s", filename);
24
25         if (!oilsIDLInit( filename )) {
26                 osrfLogError(OSRF_LOG_MARK, "Problem loading IDL file [%s]!", filename);
27                 if(freeable_filename)
28                         free(freeable_filename);
29                 return NULL;
30         }
31
32         if(freeable_filename)
33                 free(freeable_filename);
34         return oilsIDL();
35 }
36
37 /**
38         @brief Return a const string with the value of a specified column in a row object.
39         @param object Pointer to the row object.
40         @param field Name of the column.
41         @return Pointer to a const string representing the value of the specified column,
42                 or NULL in case of error.
43
44         The row object must be a JSON_ARRAY with a classname.  The column value must be a
45         JSON_STRING or a JSON_NUMBER.  Any other object type results in a return of NULL.
46
47         The return value points into the internal contents of the row object, which
48         retains ownership.
49 */
50 const char* oilsFMGetStringConst( const jsonObject* object, const char* field ) {
51         return jsonObjectGetString(oilsFMGetObject( object, field ));
52 }
53
54 /**
55         @brief Return a string with the value of a specified column in a row object.
56         @param object Pointer to the row object.
57         @param field Name of the column.
58         @return Pointer to a newly allocated string representing the value of the specified column,
59                 or NULL in case of error.
60
61         The row object must be a JSON_ARRAY with a classname.  The column value must be a
62         JSON_STRING or a JSON_NUMBER.  Any other object type results in a return of NULL.
63
64         The calling code is responsible for freeing the returned string by calling free().
65  */
66 char* oilsFMGetString( const jsonObject* object, const char* field ) {
67         return jsonObjectToSimpleString(oilsFMGetObject( object, field ));
68 }
69
70 /**
71         @brief Return a pointer to the value of a specified column in a row object.
72         @param object Pointer to the row object.
73         @param field Name of the column.
74         @return Pointer to the jsonObject representing the value of the specified column, or NULL
75                 in case of error.
76
77         The row object must be a JSON_ARRAY with a classname.
78
79         The return value may point to a JSON_NULL, JSON_STRING, JSON_NUMBER, or JSON_ARRAY.  It
80         points into the internal contents of the row object, which retains ownership.
81 */
82 const jsonObject* oilsFMGetObject( const jsonObject* object, const char* field ) {
83         if(!(object && field)) return NULL;
84         if( object->type != JSON_ARRAY || !object->classname ) return NULL;
85         int pos = fm_ntop(object->classname, field);
86         if( pos > -1 )
87                 return jsonObjectGetIndex( object, pos );
88         return NULL;
89 }
90
91
92 int oilsFMSetString( jsonObject* object, const char* field, const char* string ) {
93         if(!(object && field && string)) return -1;
94         osrfLogInternal(OSRF_LOG_MARK, "oilsFMSetString(): Collecing position for field %s", field);
95         int pos = fm_ntop(object->classname, field);
96         if( pos > -1 ) {
97                 osrfLogInternal(OSRF_LOG_MARK, "oilsFMSetString(): Setting string "
98                                 "%s at field %s [position %d]", string, field, pos );
99                 jsonObjectSetIndex( object, pos, jsonNewObject(string) );
100                 return 0;
101         }
102         return -1;
103 }
104
105
106 int oilsUtilsIsDBTrue( const char* val ) {
107         if( val && strcasecmp(val, "f") && strcmp(val, "0") ) return 1;
108         return 0;
109 }
110
111
112 long oilsFMGetObjectId( const jsonObject* obj ) {
113         long id = -1;
114         if(!obj) return id;
115         char* ids = oilsFMGetString( obj, "id" );
116         if(ids) {
117                 id = atol(ids);
118                 free(ids);
119         }
120         return id;
121 }
122
123 int oilsUtilsTrackUserActivity(long usr, const char* ewho, const char* ewhat, const char* ehow) {
124     if (!usr && !(ewho || ewhat || ehow)) return 0;
125     int rowcount = 0;
126
127     jsonObject* params = jsonParseFmt(
128         "{\"from\":[\"actor.insert_usr_activity\", %ld, \"%s\", \"%s\", \"%s\"]}",
129         usr, 
130         (NULL == ewho)  ? "" : ewho, 
131         (NULL == ewhat) ? "" : ewhat, 
132         (NULL == ehow)  ? "" : ehow
133     );
134
135         osrfAppSession* session = osrfAppSessionClientInit("open-ils.cstore");
136     osrfAppSessionConnect(session);
137     int reqid = osrfAppSessionSendRequest(session, NULL, "open-ils.cstore.transaction.begin", 1);
138         osrfMessage* omsg = osrfAppSessionRequestRecv(session, reqid, 60);
139
140     if(omsg) {
141         osrfMessageFree(omsg);
142         reqid = osrfAppSessionSendRequest(session, params, "open-ils.cstore.json_query", 1);
143             omsg = osrfAppSessionRequestRecv(session, reqid, 60);
144
145         if(omsg) {
146             const jsonObject* rows = osrfMessageGetResult(omsg);
147             if (rows) rowcount = rows->size;
148             osrfMessageFree(omsg); // frees rows
149             if (rowcount) {
150                 reqid = osrfAppSessionSendRequest(session, NULL, "open-ils.cstore.transaction.commit", 1);
151                     omsg = osrfAppSessionRequestRecv(session, reqid, 60);
152                 osrfMessageFree(omsg);
153             } else {
154                 reqid = osrfAppSessionSendRequest(session, NULL, "open-ils.cstore.transaction.rollback", 1);
155                     omsg = osrfAppSessionRequestRecv(session, reqid, 60);
156                 osrfMessageFree(omsg);
157             }
158         }
159     }
160
161     osrfAppSessionFree(session); // calls disconnect internally
162     jsonObjectFree(params);
163     return rowcount;
164 }
165
166
167 static int rootOrgId = 0; // cache the ID of the root org unit.
168 int oilsUtilsGetRootOrgId() {
169
170     // return the cached value if we have it.
171     if (rootOrgId > 0) return rootOrgId;
172
173     jsonObject* where_clause = jsonParse("{\"parent_ou\":null}");
174     jsonObject* org = oilsUtilsQuickReq(
175         "open-ils.cstore",
176         "open-ils.cstore.direct.actor.org_unit.search",
177         where_clause
178     );
179
180     rootOrgId = (int) 
181         jsonObjectGetNumber(oilsFMGetObject(org, "id"));
182
183     jsonObjectFree(where_clause);
184     jsonObjectFree(org);
185
186     return rootOrgId;
187 }
188
189 oilsEvent* oilsUtilsCheckPerms( int userid, int orgid, char* permissions[], int size ) {
190     if (!permissions) return NULL;
191     int i;
192
193     // Check perms against the root org unit if no org unit is provided.
194     if (orgid == -1)
195         orgid = oilsUtilsGetRootOrgId();
196
197     for( i = 0; i < size && permissions[i]; i++ ) {
198         oilsEvent* evt = NULL;
199         char* perm = permissions[i];
200
201         jsonObject* params = jsonParseFmt(
202             "{\"from\":[\"permission.usr_has_perm\",\"%d\",\"%s\",\"%d\"]}",
203             userid, perm, orgid
204         );
205
206         // Execute the query
207         jsonObject* result = oilsUtilsCStoreReq(
208             "open-ils.cstore.json_query", params);
209
210         const jsonObject* hasPermStr = 
211             jsonObjectGetKeyConst(result, "permission.usr_has_perm");
212
213         if (!oilsUtilsIsDBTrue(jsonObjectGetString(hasPermStr))) {
214             evt = oilsNewEvent3(
215                 OSRF_LOG_MARK, OILS_EVENT_PERM_FAILURE, perm, orgid);
216         }
217
218         jsonObjectFree(params);
219         jsonObjectFree(result);
220
221         // return first failed permission check.
222         if (evt) return evt;
223     }
224
225     return NULL; // all perm checks succeeded
226 }
227
228 /**
229         @brief Perform a remote procedure call.
230         @param service The name of the service to invoke.
231         @param method The name of the method to call.
232         @param params The parameters to be passed to the method, if any.
233         @return A copy of whatever the method returns as a result, or a JSON_NULL if the method
234         doesn't return anything.
235
236         If the @a params parameter points to a JSON_ARRAY, pass each element of the array
237         as a separate parameter.  If it points to any other kind of jsonObject, pass it as a
238         single parameter.  If it is NULL, pass no parameters.
239
240         The calling code is responsible for freeing the returned object by calling jsonObjectFree().
241 */
242 jsonObject* oilsUtilsQuickReq( const char* service, const char* method,
243                 const jsonObject* params ) {
244         if(!(service && method)) return NULL;
245
246         osrfLogDebug(OSRF_LOG_MARK, "oilsUtilsQuickReq(): %s - %s", service, method );
247
248         // Open an application session with the service, and send the request
249         osrfAppSession* session = osrfAppSessionClientInit( service );
250         int reqid = osrfAppSessionSendRequest( session, params, method, 1 );
251
252         // Get the response
253         osrfMessage* omsg = osrfAppSessionRequestRecv( session, reqid, 60 );
254         jsonObject* result = jsonObjectClone( osrfMessageGetResult(omsg) );
255
256         // Clean up
257         osrfMessageFree(omsg);
258         osrfAppSessionFree(session);
259         return result;
260 }
261
262 /**
263         @brief Call a method of the open-ils.storage service.
264         @param method Name of the method.
265         @param params Parameters to be passed to the method, if any.
266         @return A copy of whatever the method returns as a result, or a JSON_NULL if the method
267         doesn't return anything.
268
269         If the @a params parameter points to a JSON_ARRAY, pass each element of the array
270         as a separate parameter.  If it points to any other kind of jsonObject, pass it as a
271         single parameter.  If it is NULL, pass no parameters.
272
273         The calling code is responsible for freeing the returned object by calling jsonObjectFree().
274 */
275 jsonObject* oilsUtilsStorageReq( const char* method, const jsonObject* params ) {
276         return oilsUtilsQuickReq( "open-ils.storage", method, params );
277 }
278
279 /**
280         @brief Call a method of the open-ils.cstore service.
281         @param method Name of the method.
282         @param params Parameters to be passed to the method, if any.
283         @return A copy of whatever the method returns as a result, or a JSON_NULL if the method
284         doesn't return anything.
285
286         If the @a params parameter points to a JSON_ARRAY, pass each element of the array
287         as a separate parameter.  If it points to any other kind of jsonObject, pass it as a
288         single parameter.  If it is NULL, pass no parameters.
289
290         The calling code is responsible for freeing the returned object by calling jsonObjectFree().
291 */
292 jsonObject* oilsUtilsCStoreReq( const char* method, const jsonObject* params ) {
293         return oilsUtilsQuickReq("open-ils.cstore", method, params);
294 }
295
296
297
298 /**
299         @brief Given a username, fetch the corresponding row from the actor.usr table, if any.
300         @param name The username for which to search.
301         @return A Fieldmapper object for the relevant row in the actor.usr table, if it exists;
302         or a JSON_NULL if it doesn't.
303
304         The calling code is responsible for freeing the returned object by calling jsonObjectFree().
305 */
306 jsonObject* oilsUtilsFetchUserByUsername( const char* name ) {
307         if(!name) return NULL;
308         jsonObject* params = jsonParseFmt("{\"usrname\":\"%s\"}", name);
309         jsonObject* user = oilsUtilsQuickReq(
310                 "open-ils.cstore", "open-ils.cstore.direct.actor.user.search", params );
311
312         jsonObjectFree(params);
313         long id = oilsFMGetObjectId(user);
314         osrfLogDebug(OSRF_LOG_MARK, "Fetched user %s:%ld", name, id);
315         return user;
316 }
317
318 /**
319         @brief Given a barcode, fetch the corresponding row from the actor.usr table, if any.
320         @param name The barcode for which to search.
321         @return A Fieldmapper object for the relevant row in the actor.usr table, if it exists;
322         or a JSON_NULL if it doesn't.
323
324         Look up the barcode in actor.card.  Follow a foreign key from there to get a row in
325         actor.usr.
326
327         The calling code is responsible for freeing the returned object by calling jsonObjectFree().
328 */
329 jsonObject* oilsUtilsFetchUserByBarcode(const char* barcode) {
330         if(!barcode) return NULL;
331
332         osrfLogInfo(OSRF_LOG_MARK, "Fetching user by barcode %s", barcode);
333
334         jsonObject* params = jsonParseFmt("{\"barcode\":\"%s\"}", barcode);
335         jsonObject* card = oilsUtilsQuickReq(
336                 "open-ils.cstore", "open-ils.cstore.direct.actor.card.search", params );
337         jsonObjectFree(params);
338
339         if(!card)
340                 return NULL;   // No such card
341
342         // Get the user's id as a double
343         char* usr = oilsFMGetString(card, "usr");
344         jsonObjectFree(card);
345         if(!usr)
346                 return NULL;   // No user id (shouldn't happen)
347         double iusr = strtod(usr, NULL);
348         free(usr);
349
350         // Look up the user in actor.usr
351         params = jsonParseFmt("[%f]", iusr);
352         jsonObject* user = oilsUtilsQuickReq(
353                 "open-ils.cstore", "open-ils.cstore.direct.actor.user.retrieve", params);
354
355         jsonObjectFree(params);
356         return user;
357 }
358
359 char* oilsUtilsFetchOrgSetting( int orgid, const char* setting ) {
360         if(!setting) return NULL;
361
362         jsonObject* params = jsonParseFmt("[%d, \"%s\"]", orgid, setting );
363
364         jsonObject* set = oilsUtilsQuickReq(
365                 "open-ils.actor",
366                 "open-ils.actor.ou_setting.ancestor_default", params);
367
368         char* value = jsonObjectToSimpleString( jsonObjectGetKeyConst( set, "value" ));
369         jsonObjectFree(params);
370         jsonObjectFree(set);
371         osrfLogDebug(OSRF_LOG_MARK, "Fetched org [%d] setting: %s => %s", orgid, setting, value);
372         return value;
373 }
374
375
376
377 char* oilsUtilsLogin( const char* uname, const char* passwd, const char* type, int orgId ) {
378         if(!(uname && passwd)) return NULL;
379
380         osrfLogDebug(OSRF_LOG_MARK, "Logging in with username %s", uname );
381         char* token = NULL;
382
383         jsonObject* params = jsonParseFmt("[\"%s\"]", uname);
384
385         jsonObject* o = oilsUtilsQuickReq(
386                 "open-ils.auth", "open-ils.auth.authenticate.init", params );
387
388         const char* seed = jsonObjectGetString(o);
389         char* passhash = md5sum(passwd);
390         char buf[256];
391         snprintf(buf, sizeof(buf), "%s%s", seed, passhash);
392         char* fullhash = md5sum(buf);
393
394         jsonObjectFree(o);
395         jsonObjectFree(params);
396         free(passhash);
397
398         params = jsonParseFmt( "[\"%s\", \"%s\", \"%s\", \"%d\"]", uname, fullhash, type, orgId );
399         o = oilsUtilsQuickReq( "open-ils.auth",
400                 "open-ils.auth.authenticate.complete", params );
401
402         if(o) {
403                 const char* tok = jsonObjectGetString(
404                         jsonObjectGetKeyConst( jsonObjectGetKey( o,"payload" ), "authtoken" ));
405                 if( tok )
406                         token = strdup( tok );
407         }
408
409         free(fullhash);
410         jsonObjectFree(params);
411         jsonObjectFree(o);
412
413         return token;
414 }
415
416
417 jsonObject* oilsUtilsFetchWorkstation( long id ) {
418         jsonObject* p = jsonParseFmt("[%ld]", id);
419         jsonObject* r = oilsUtilsQuickReq(
420                 "open-ils.storage",
421                 "open-ils.storage.direct.actor.workstation.retrieve", p );
422         jsonObjectFree(p);
423         return r;
424 }
425
426 jsonObject* oilsUtilsFetchWorkstationByName( const char* name ) {
427         jsonObject* p = jsonParseFmt("{\"name\":\"%s\"}", name);
428         jsonObject* r = oilsUtilsCStoreReq(
429                 "open-ils.cstore.direct.actor.workstation.search", p);
430         jsonObjectFree(p);
431         return r;
432 }
433
434 /**
435         @brief Convert a string to a number representing a time interval in seconds.
436         @param interval Pointer to string, e.g. "420" or "2 weeks".
437         @return If successful, the number of seconds that the string represents; otherwise -1.
438
439         If the string is all digits (apart from any leading or trailing white space), convert
440         it directly.  Otherwise pass it to PostgreSQL for translation.
441
442         The result is the same as if we were to pass every string to PostgreSQL, except that,
443         depending on the value of LONG_MAX, we return values for some strings that represent
444         intervals too long for PostgreSQL to represent (i.e. more than 2147483647 seconds).
445
446         WARNING: a valid interval of -1 second will be indistinguishable from an error.  If
447         such an interval is a plausible possibility, don't use this function.
448 */
449 long oilsUtilsIntervalToSeconds( const char* s ) {
450
451         if( !s ) {
452                 osrfLogWarning( OSRF_LOG_MARK, "String to be converted is NULL" );
453                 return -1;
454         }
455
456         // Skip leading white space
457         while( isspace( (unsigned char) *s ))
458                 ++s;
459
460         if( '\0' == *s ) {
461                 osrfLogWarning( OSRF_LOG_MARK, "String to be converted is empty or all white space" );
462                 return -1;
463         }
464
465         // See if the string is a raw number, i.e. all digits
466         // (apart from any leading or trailing white space)
467
468         const char* p = s;   // For traversing and examining the remaining string
469         if( isdigit( (unsigned char) *p )) {
470                 // Looks like a number so far...skip over the digits
471                 do {
472                         ++p;
473                 } while( isdigit( (unsigned char) *p ));
474                 // Skip over any following white space
475                 while( isspace( (unsigned char) *p ))
476                         ++p;
477                 if( '\0' == *p ) {
478                         // This string is a raw number.  Convert it directly.
479                         long n = strtol( s, NULL, 10 );
480                         if( LONG_MAX == n ) {
481                                 // numeric overflow
482                                 osrfLogWarning( OSRF_LOG_MARK,
483                                         "String \"%s\"represents a number too big for a long", s );
484                                 return -1;
485                         } else
486                                 return n;
487                 }
488         }
489
490         // If we get to this point, the string is not all digits.  Pass it to PostgreSQL.
491
492         // Build the query
493         jsonObject* query_obj = jsonParseFmt(
494                 "{\"from\":[\"config.interval_to_seconds\",\"%s\"]}", s );
495
496         // Execute the query
497         jsonObject* result = oilsUtilsCStoreReq(
498                 "open-ils.cstore.json_query", query_obj );
499         jsonObjectFree( query_obj );
500
501         // Get the results
502         const jsonObject* seconds_obj = jsonObjectGetKeyConst( result, "config.interval_to_seconds" );
503         long seconds = -1;
504         if( seconds_obj && JSON_NUMBER == seconds_obj->type )
505                 seconds = (long) jsonObjectGetNumber( seconds_obj );
506         else
507                 osrfLogError( OSRF_LOG_MARK,
508                         "Error calling json_query to convert \"%s\" to seconds", s );
509
510         jsonObjectFree( result );
511         return seconds;
512 }