]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/web/js/ui/default/staff/services/auth.js
LP#1706124: Make include inactive patrons checkbox sticky
[working/Evergreen.git] / Open-ILS / web / js / ui / default / staff / services / auth.js
1 /* Core Sevice - egAuth
2  *
3  * Manages login and auth session retrieval.
4  */
5
6 angular.module('egCoreMod')
7
8 .factory('egAuth', 
9        ['$q','$timeout','$rootScope','$window','$location','egNet','egHatch',
10 function($q , $timeout , $rootScope , $window , $location , egNet , egHatch) {
11
12     var service = {
13         // the currently active user (au) object
14         user : function(u) {
15             if (u) {
16                 this._user = u;
17             }
18             return this._user;
19         },
20
21         // the user hidden by an operator change
22         OCuser : function(u) {
23             if (u) {
24                 this._OCuser = u;
25             }
26             return this._OCuser;
27         },
28
29         // the Op Change hidden auth token string
30         OCtoken : function() {
31             return egHatch.getLoginSessionItem('eg.auth.token.oc');
32         },
33
34         // Op Change hidden authtime in seconds
35         OCauthtime : function() {
36             return egHatch.getLoginSessionItem('eg.auth.time.oc');
37         },
38
39         // the currently active auth token string
40         token : function() {
41             return egHatch.getLoginSessionItem('eg.auth.token');
42         },
43
44         // authtime in seconds
45         authtime : function() {
46             return egHatch.getLoginSessionItem('eg.auth.time');
47         },
48
49         // the currently active workstation name
50         // For ws_ou or wsid(), see egAuth.user().ws_ou(), etc.
51         workstation : function() {
52             return this.ws;
53         }
54     };
55
56     /* Returns a promise, which is resolved if valid
57      * authtoken is found, otherwise rejected */
58     service.testAuthToken = function() {
59         var deferred = $q.defer();
60         var token = service.token();
61
62         if (token) {
63
64             if (lf.isOffline && !$location.path().match(/\/session/) ) {
65                 // Just stop here if we're in the offline interface but not on the session tab
66                 $timeout(function(){deferred.resolve()});
67             } else if (lf.isOffline && $location.path().match(/\/session/) && !$window.navigator.onLine) {
68                 // Likewise, if we're in the offline interface on the session tab and the network is down.
69                 // The session tab itself will redirect appropriately due to no network.
70                 $timeout(function(){deferred.resolve()});
71             } else {
72                 // Otherwise, check the token.  This will freeze all other interfaces, which is what we want.
73                 egNet.request(
74                     'open-ils.auth',
75                     'open-ils.auth.session.retrieve', token)
76     
77                 .then(function(user) {
78                     if (user && user.classname) {
79                         // authtoken test succeeded
80                         service.user(user);
81                         service.poll();
82                         service.check_workstation(deferred);
83     
84                     } else {
85                         // authtoken test failed
86                         egHatch.clearLoginSessionItems();
87                         deferred.reject(); 
88                     }
89                 });
90             }
91
92         } else {
93             // no authtoken to test
94             deferred.reject('No authtoken found');
95         }
96
97         return deferred.promise;
98     };
99
100     service.check_workstation = function(deferred) {
101
102         var user = service.user();
103         var ws_path = '/admin/workstation/workstations';
104
105         return egHatch.getItem('eg.workstation.all')
106         .then(function(workstations) { 
107             if (!workstations) workstations = [];
108
109             // If the user is authenticated with a workstation, get the
110             // name from the locally registered version of the workstation.
111
112             if (user.wsid()) {
113
114                 var ws = workstations.filter(
115                     function(w) {return w.id == user.wsid()})[0];
116
117                 if (ws) { // success
118                     service.ws = ws.name;
119                     deferred.resolve();
120                     return;
121                 }
122             }
123
124             if ($location.path() == ws_path) {
125                 // User is on the workstation admin page.  No need
126                 // to redirect.
127                 deferred.resolve();
128                 return;
129             }
130
131             // At this point, the user is trying to access a page
132             // besides the workstation admin page without a valid
133             // registered workstation.  Send them back to the 
134             // workstation admin page.
135
136             // NOTE: egEnv also defines basePath, but we cannot import
137             // egEnv here becuase it creates a circular reference.
138             $window.location.href = '/eg/staff' + ws_path;
139             deferred.resolve();
140         });
141     }
142
143     /**
144      * Returns a promise, which is resolved on successful 
145      * login and rejected on failed login.
146      */
147     service.login = function(args, ops) {
148         // avoid modifying the caller's data structure.
149         args = angular.copy(args);
150
151         if (!ops) { // only set on redo attempts.
152             ops = {deferred : $q.defer()};
153
154             // Clear old LoginSession keys that were left in localStorage
155             // when the previous user closed the browser without logging
156             // out.  Under normal circumstance, LoginSession data would
157             // have been cleared by now, either during logout or cookie
158             // expiration.  But, if for some reason the user manually
159             // removed the auth token cookie w/o closing the browser
160             // (say, for testing), then this serves double duty to ensure
161             // LoginSession data cannot persist across logins.
162             egHatch.clearLoginSessionItems();
163         }
164
165         service.login_api(args).then(function(evt) {
166
167             if (evt.textcode == 'SUCCESS') {
168                 service.handle_login_ok(args, evt);
169                 ops.deferred.resolve({
170                     invalid_workstation : ops.invalid_workstation
171                 });
172
173             } else if (evt.textcode == 'WORKSTATION_NOT_FOUND') {
174                 ops.invalid_workstation = true;
175                 delete args.workstation;
176                 service.login(args, ops); // redo w/o workstation
177
178             } else {
179                 // note: the likely outcome here is a NO_SESION
180                 // server event, which results in broadcasting an 
181                 // egInvalidAuth by egNet. 
182                 console.error('login failed ' + js2JSON(evt));
183                 ops.deferred.reject();
184             }
185         });
186
187         return ops.deferred.promise;
188     }
189
190     /**
191      * Returns a promise, which is resolved on successful 
192      * login and rejected on failed login.
193      */
194     service.opChange = function(args) {
195         // avoid modifying the caller's data structure.
196         args = angular.copy(args);
197         args.workstation = service.workstation();
198
199         var deferred = $q.defer();
200
201         service.login_api(args).then(function(evt) {
202
203             if (evt.textcode == 'SUCCESS') {
204                 if (args.type != 'persist') {
205                     egHatch.setLoginSessionItem('eg.auth.token.oc', service.token());
206                     egHatch.setLoginSessionItem('eg.auth.time.oc', service.authtime());
207                     service.OCuser(service.user());
208                 }
209                 service.handle_login_ok(args, evt);
210                 service.testAuthToken().then(
211                     deferred.resolve,
212                     function () { service.opChangeUndo().then(deferred.reject)  }
213                 );
214             } else {
215                 // note: the likely outcome here is a NO_SESION
216                 // server event, which results in broadcasting an 
217                 // egInvalidAuth by egNet. 
218                 console.error('operator change failed ' + js2JSON(evt));
219                 deferred.reject();
220             }
221         });
222
223         return deferred.promise;
224     }
225
226     service.opChangeUndo = function() {
227         if (service.OCtoken()) {
228             service.user(service.OCuser());
229             egHatch.setLoginSessionItem('eg.auth.token', service.OCtoken());
230             egHatch.setLoginSessionItem('eg.auth.time', service.OCauthtime());
231             egHatch.removeLoginSessionItem('eg.auth.token.oc');
232             egHatch.removeLoginSessionItem('eg.auth.time.oc');
233         }
234         return service.testAuthToken();
235     }
236
237     service.login_api = function(args) {
238         return egNet.request(
239             'open-ils.auth',
240             'open-ils.auth.authenticate.init', args.username)
241         .then(function(seed) {
242                 // avoid clobbering the bare password in case
243                 // we need it for a login redo attempt.
244                 var login_args = angular.copy(args);
245                 login_args.password = hex_md5(seed + hex_md5(args.password));
246
247                 return egNet.request(
248                     'open-ils.auth',
249                     'open-ils.auth.authenticate.complete', login_args)
250             }
251         );
252     }
253
254     service.handle_login_ok = function(args, evt) {
255         service.ws = args.workstation; 
256         egHatch.setLoginSessionItem('eg.auth.token', evt.payload.authtoken);
257         egHatch.setLoginSessionItem('eg.auth.time', evt.payload.authtime);
258         service.poll();
259     }
260
261     /**
262      * Force-check the validity of the authtoken on occasion. 
263      * This allows us to redirect an idle staff client back to the login
264      * page after the session times out.  Otherwise, the UI would stay
265      * open with potentially sensitive data visible.
266      * TODO: What is the practical difference (for a browser) between 
267      * checking auth validity and the ui.general.idle_timeout setting?
268      * Does that setting serve a purpose in a browser environment?
269      */
270     service.poll = function() {
271         if (!service.authtime()) return;
272
273         $timeout(
274             function() {
275                 if (!service.authtime()) return;
276                 egNet.request(                                                     
277                     'open-ils.auth',                                               
278                     'open-ils.auth.session.retrieve', service.token())   
279                 .then(function(user) {
280                     if (user && user.classname) { // all good
281                         service.poll();
282                     } else {
283                         $rootScope.$broadcast('egAuthExpired') 
284                     }
285                 })
286             },
287             // add a 5 second delay to give the token plenty of time
288             // to expire on the server.
289             service.authtime() * 1000 + 5000
290         );
291     }
292
293     service.logout = function() {
294         if (service.token()) {
295             egNet.request(
296                 'open-ils.auth', 
297                 'open-ils.auth.session.delete', 
298                 service.token()); // fire and forget
299             egHatch.clearLoginSessionItems();
300         }
301         service._user = null;
302     };
303
304     return service;
305 }])
306
307
308 /**
309  * Service for testing user permissions.
310  * Note: this cannot live within egAuth, because it creates a circular
311  * dependency of egOrg -> egEnv -> egAuth -> egOrg
312  */
313 .factory('egPerm', 
314        ['$q','egNet','egAuth','egOrg',
315 function($q , egNet , egAuth , egOrg) {
316     var service = {};
317
318     /*
319      * Returns the full list of org unit objects at which the currently
320      * logged in user has the selected permissions.
321      * @permList - list or string.  If a list, the response object is a
322      * hash of perm => orgList maps.  If a string, the response is the
323      * org list for the requested perm.
324      */
325     service.hasPermAt = function(permList, asId) {
326         var deferred = $q.defer();
327         var isArray = true;
328         if (!angular.isArray(permList)) {
329             isArray = false;
330             permList = [permList];
331         }
332         // as called, this method will return the top-most org unit of the
333         // sub-tree at which this user has the selected permission.
334         // From there, flesh the descendant orgs locally.
335         egNet.request(
336             'open-ils.actor',
337             'open-ils.actor.user.has_work_perm_at.batch',
338             egAuth.token(), permList
339         ).then(function(resp) {
340             var answer = {};
341             angular.forEach(permList, function(perm) {
342                 var all = [];
343                 angular.forEach(resp[perm], function(oneOrg) {
344                     all = all.concat(egOrg.descendants(oneOrg, asId));
345                 });
346                 answer[perm] = all;
347             });
348             if (!isArray) answer = answer[permList[0]];
349             deferred.resolve(answer);
350         });
351        return deferred.promise;
352     };
353
354
355     /**
356      * Returns a hash of perm => hasPermBool for each requested permission.
357      * If the authenticated user has no workstation, no checks are made
358      * and all permissions return false.
359      */
360     service.hasPermHere = function(permList) {
361         var response = {};
362
363         var isArray = true;
364         if (!angular.isArray(permList)) {
365             isArray = false;
366             permList = [permList];
367         }
368
369         // no workstation, all are false
370         if (egAuth.user().wsid() === null) {
371             console.warn("egPerm.hasPermHere() called with no workstation");
372             if (isArray) {
373                 response = permList.map(function(perm) {
374                     return response[perm] = false;
375                 });
376             } else {
377                 response = false;
378             }
379             return $q.when(response);
380         }
381
382         ws_ou = Number(egAuth.user().ws_ou()); // from string
383
384         return service.hasPermAt(permList, true)
385         .then(function(orgMap) {
386             angular.forEach(orgMap, function(orgIds, perm) {
387                 // each permission is mapped to a flat list of org unit ids,
388                 // including descendants.  See if our workstation org unit
389                 // is in the list.
390                 response[perm] = orgIds.indexOf(ws_ou) > -1;
391             });
392             if (!isArray) response = response[permList[0]];
393             return response;
394         });
395     }
396
397     return service;
398 }])
399
400