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