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