1 /* Core Sevice - egAuth
3 * Manages login and auth session retrieval.
6 angular.module('egCoreMod')
9 ['$q','$timeout','$rootScope','$window','$location','egNet','egHatch','$injector',
10 function($q , $timeout , $rootScope , $window , $location , egNet , egHatch , $injector) {
12 var egLovefield = null;
15 // the currently active user (au) object
23 // the user hidden by an operator change
24 OCuser : function(u) {
31 // the Op Change hidden auth token string
32 OCtoken : function() {
33 return egHatch.getLoginSessionItem('eg.auth.token.oc');
36 // Op Change hidden authtime in seconds
37 OCauthtime : function() {
38 return egHatch.getLoginSessionItem('eg.auth.time.oc');
41 // the currently active auth token string
43 return egHatch.getLoginSessionItem('eg.auth.token');
46 // authtime in seconds
47 authtime : function() {
48 return egHatch.getLoginSessionItem('eg.auth.time');
51 // the currently active workstation name
52 // For ws_ou or wsid(), see egAuth.user().ws_ou(), etc.
53 workstation : function() {
57 // Listen for logout events in other tabs
58 // Current version of phantomjs (unit tests, etc.) does not
59 // support BroadcastChannel, so just dummy it up.
60 authChannel : (typeof BroadcastChannel == 'undefined') ?
61 {} : new BroadcastChannel('eg.auth')
64 /* Returns a promise, which is resolved if valid
65 * authtoken is found, otherwise rejected */
66 service.testAuthToken = function() {
67 var deferred = $q.defer();
69 // Move legacy cookies from /eg/staff to / before fetching the token.
70 egHatch.migrateAuthCookies();
72 var token = service.token();
76 if (lf.isOffline && !$location.path().match(/\/session/) ) {
77 // Just stop here if we're in the offline interface but not on the session tab
78 $timeout(function(){deferred.resolve()});
79 } else if (lf.isOffline && $location.path().match(/\/session/) && !$window.navigator.onLine) {
80 // Likewise, if we're in the offline interface on the session tab and the network is down.
81 // The session tab itself will redirect appropriately due to no network.
82 $timeout(function(){deferred.resolve()});
84 // Otherwise, check the token. This will freeze all other interfaces, which is what we want.
87 'open-ils.auth.session.retrieve', token)
89 .then(function(user) {
90 if (user && user.classname) {
91 // authtoken test succeeded
94 service.check_workstation(deferred);
97 // authtoken test failed
98 egHatch.clearLoginSessionItems();
105 // no authtoken to test
106 deferred.reject('No authtoken found');
109 return deferred.promise;
112 service.check_workstation = function(deferred) {
114 var user = service.user();
115 var ws_path = '/admin/workstation/workstations';
117 return egHatch.getItem('eg.workstation.all')
118 .then(function(workstations) {
119 if (!workstations) workstations = [];
121 // If the user is authenticated with a workstation, get the
122 // name from the locally registered version of the workstation.
126 var ws = workstations.filter(
127 function(w) {return w.id == user.wsid()})[0];
130 service.ws = ws.name;
136 if ($location.path() == ws_path) {
137 // User is on the workstation admin page. No need
143 // At this point, the user is trying to access a page
144 // besides the workstation admin page without a valid
145 // registered workstation. Send them back to the
146 // workstation admin page.
148 // NOTE: egEnv also defines basePath, but we cannot import
149 // egEnv here becuase it creates a circular reference.
150 $window.location.href = '/eg2/staff' + ws_path;
156 * Returns a promise, which is resolved on successful
157 * login and rejected on failed login.
159 service.login = function(args, ops) {
160 // avoid modifying the caller's data structure.
161 args = angular.copy(args);
163 if (!ops) { // only set on redo attempts.
164 ops = {deferred : $q.defer()};
166 // Clear old LoginSession keys that were left in localStorage
167 // when the previous user closed the browser without logging
168 // out. Under normal circumstance, LoginSession data would
169 // have been cleared by now, either during logout or cookie
170 // expiration. But, if for some reason the user manually
171 // removed the auth token cookie w/o closing the browser
172 // (say, for testing), then this serves double duty to ensure
173 // LoginSession data cannot persist across logins.
174 egHatch.clearLoginSessionItems();
177 service.login_api(args).then(function(evt) {
178 if (evt.textcode == 'SUCCESS') {
179 service.handle_login_ok(args, evt);
180 ops.deferred.resolve({
181 invalid_workstation : ops.invalid_workstation
184 } else if (evt.textcode == 'WORKSTATION_NOT_FOUND') {
185 ops.invalid_workstation = true;
186 delete args.workstation;
187 service.login(args, ops); // redo w/o workstation
190 // note: the likely outcome here is a NO_SESION
191 // server event, which results in broadcasting an
192 // egInvalidAuth by egNet.
193 console.error('login failed ' + js2JSON(evt));
194 ops.deferred.reject();
198 return ops.deferred.promise;
202 * Returns a promise, which is resolved on successful
203 * login and rejected on failed login.
205 service.opChange = function(args) {
206 // avoid modifying the caller's data structure.
207 args = angular.copy(args);
208 args.workstation = service.workstation();
210 var deferred = $q.defer();
212 service.login_api(args).then(function(evt) {
214 if (evt.textcode == 'SUCCESS') {
215 if (args.type != 'persist') {
216 egHatch.setLoginSessionItem('eg.auth.token.oc', service.token());
217 egHatch.setLoginSessionItem('eg.auth.time.oc', service.authtime());
218 service.OCuser(service.user());
220 service.handle_login_ok(args, evt);
221 service.testAuthToken().then(
223 function () { service.opChangeUndo().then(deferred.reject) }
226 // note: the likely outcome here is a NO_SESION
227 // server event, which results in broadcasting an
228 // egInvalidAuth by egNet.
229 console.error('operator change failed ' + js2JSON(evt));
234 return deferred.promise;
237 service.opChangeUndo = function() {
238 if (service.OCtoken()) {
239 service.user(service.OCuser());
240 egHatch.setLoginSessionItem('eg.auth.token', service.OCtoken());
241 egHatch.setLoginSessionItem('eg.auth.time', service.OCauthtime());
242 egHatch.removeLoginSessionItem('eg.auth.token.oc');
243 egHatch.removeLoginSessionItem('eg.auth.time.oc');
245 return service.testAuthToken();
248 service.login_via_auth_proxy = function(args) {
249 return egNet.request(
250 'open-ils.auth_proxy',
251 'open-ils.auth_proxy.login', args);
254 service.login_via_auth = function(args) {
255 return egNet.request(
257 'open-ils.auth.authenticate.init', args.username)
258 .then(function(seed) {
259 // avoid clobbering the bare password in case
260 // we need it for a login redo attempt.
261 var login_args = angular.copy(args);
262 login_args.password = hex_md5(seed + hex_md5(args.password));
264 return egNet.request(
266 'open-ils.auth.authenticate.complete', login_args)
271 service.login_api = function(args) {
273 return egNet.request(
274 'open-ils.auth_proxy',
275 'open-ils.auth_proxy.enabled')
278 console.log('proxy check returned ' + enabled);
279 if (Number(enabled) === 1) {
280 return service.login_via_auth_proxy(args);
282 return service.login_via_auth(args);
286 // request failed, likely a result of auth_proxy not running.
287 return service.login_via_auth(args);
292 service.handle_login_ok = function(args, evt) {
294 egLovefield = $injector.get('egLovefield');
296 service.ws = args.workstation;
297 egHatch.setLoginSessionItem('eg.auth.token', evt.payload.authtoken);
298 egHatch.setLoginSessionItem('eg.auth.time', evt.payload.authtime);
299 egLovefield.destroySettingsCache(); // force refresh of settings cache on login (LP#1848550)
304 * Force-check the validity of the authtoken on occasion.
305 * This allows us to redirect an idle staff client back to the login
306 * page after the session times out. Otherwise, the UI would stay
307 * open with potentially sensitive data visible.
308 * TODO: What is the practical difference (for a browser) between
309 * checking auth validity and the ui.general.idle_timeout setting?
310 * Does that setting serve a purpose in a browser environment?
312 service.poll = function() {
314 if (!service.authChannel.onmessage) {
315 // Now that we have an authtoken, listen for logout events
316 // initiated by other tabs.
317 service.authChannel.onmessage = function(e) {
318 if (e.data.action == 'logout') {
319 $rootScope.$broadcast(
320 'egAuthExpired', {startedElsewhere : true});
325 // add a 5 second delay to give the token plenty of time
326 // to expire on the server.
327 var pollTime = service.authtime() * 1000 + 5000;
329 if (pollTime < 60000) {
330 // Never poll more often than once per minute.
332 } else if (pollTime > 2147483647) {
333 // Avoid integer overflow resulting in $timeout() effectively
334 // running with timeout=0 in a loop.
335 pollTime = 2147483647;
342 'open-ils.auth.session.retrieve',
344 0, // return extra auth details, unneeded here.
345 1 // avoid extending the auth timeout
346 ).then(function(user) {
347 if (user && user.classname) { // all good
350 // NOTE: we should never get here, since egNet
351 // filters responses for NO_SESSION events.
352 $rootScope.$broadcast('egAuthExpired');
360 service.logout = function(broadcast) {
362 if (broadcast && service.authChannel.postMessage) {
363 // Tell the other tabs to shut it all down.
364 service.authChannel.postMessage({action : 'logout'});
367 if (service.token()) {
370 'open-ils.auth.session.delete',
371 service.token()); // fire and forget
372 egHatch.clearLoginSessionItems();
374 service._user = null;
382 * Service for testing user permissions.
383 * Note: this cannot live within egAuth, because it creates a circular
384 * dependency of egOrg -> egEnv -> egAuth -> egOrg
387 ['$q','egNet','egAuth','egOrg',
388 function($q , egNet , egAuth , egOrg) {
392 * Returns the full list of org unit objects at which the currently
393 * logged in user has the selected permissions.
394 * @permList - list or string. If a list, the response object is a
395 * hash of perm => orgList maps. If a string, the response is the
396 * org list for the requested perm.
398 service.hasPermAt = function(permList, asId) {
399 var deferred = $q.defer();
401 if (!angular.isArray(permList)) {
403 permList = [permList];
405 // as called, this method will return the top-most org unit of the
406 // sub-tree at which this user has the selected permission.
407 // From there, flesh the descendant orgs locally.
410 'open-ils.actor.user.has_work_perm_at.batch',
411 egAuth.token(), permList
412 ).then(function(resp) {
414 angular.forEach(permList, function(perm) {
416 angular.forEach(resp[perm], function(oneOrg) {
417 all = all.concat(egOrg.descendants(oneOrg, asId));
421 if (!isArray) answer = answer[permList[0]];
422 deferred.resolve(answer);
424 return deferred.promise;
429 * Returns a hash of perm => hasPermBool for each requested permission.
430 * If the authenticated user has no workstation, no checks are made
431 * and all permissions return false.
433 service.hasPermHere = function(permList) {
437 if (!angular.isArray(permList)) {
439 permList = [permList];
442 // no workstation, all are false
443 if (egAuth.user().wsid() === null) {
444 console.warn("egPerm.hasPermHere() called with no workstation");
446 response = permList.map(function(perm) {
447 return response[perm] = false;
452 return $q.when(response);
455 ws_ou = Number(egAuth.user().ws_ou()); // from string
457 return service.hasPermAt(permList, true)
458 .then(function(orgMap) {
459 angular.forEach(orgMap, function(orgIds, perm) {
460 // each permission is mapped to a flat list of org unit ids,
461 // including descendants. See if our workstation org unit
463 response[perm] = orgIds.indexOf(ws_ou) > -1;
465 if (!isArray) response = response[permList[0]];
471 * Returns a union of the full org path of each org unit at which the
472 * currently logged in user has the selected permissions.
473 * @permList - list or string. Unlike hasPermAt, the response object
474 * is always a list of org ids (or an empty list).
476 service.hasPermFullPathAt = function(permList) {
477 return service.hasPermAt(permList, true)
478 .then(function(orgs) {
480 if (permList.constructor != Array) {
481 orgHash[permList] = orgs;
486 angular.forEach(orgHash, function(orgList) {
487 angular.forEach(orgList, function(org) {
488 var full_path = egOrg.fullPath(org,true);
489 angular.forEach(full_path, function(org2) {
490 org_seen[org2] = true;
494 return Object.keys(org_seen).map(function(o) { return Number(o); });