Merge pull request #454 from jaredevantabor/session_fixes

Session timeout after idle session
This commit is contained in:
jaredevantabor 2015-10-02 00:20:52 -04:00
commit 4570535477
15 changed files with 174 additions and 32 deletions

View File

@ -55,7 +55,8 @@ import {InventoriesList, InventoriesAdd, InventoriesEdit, InventoriesManage} fro
import {AdminsList} from './controllers/Admins';
import {UsersList, UsersAdd, UsersEdit} from './controllers/Users';
import {TeamsList, TeamsAdd, TeamsEdit} from './controllers/Teams';
import './shared/RestServices';
import RestServices from './rest/main';
import './shared/api-loader';
import './shared/form-generator';
import './shared/Modal';
@ -78,7 +79,7 @@ var tower = angular.module('Tower', [
'ngRoute',
'ngSanitize',
'ngCookies',
'RestServices',
RestServices.name,
routeExtensions.name,
browserData.name,
breadcrumbs.name,
@ -878,7 +879,6 @@ var tower = angular.module('Tower', [
$rootScope.breadcrumbs = [];
$rootScope.crumbCache = [];
$rootScope.sessionTimer = Timer.init();
if ($rootScope.removeOpenSocket) {
$rootScope.removeOpenSocket();
@ -887,7 +887,7 @@ var tower = angular.module('Tower', [
// Listen for job changes and issue callbacks to initiate
// DOM updates
function openSocket() {
var schedule_socket;
var schedule_socket, control_socket;
sock = Socket({ scope: $rootScope, endpoint: "jobs" });
sock.init();
@ -936,6 +936,16 @@ var tower = angular.module('Tower', [
$log.debug('Schedule ' + data.unified_job_id + ' status changed to ' + data.status);
$rootScope.$emit('ScheduleStatusChange', data);
});
control_socket = Socket({
scope: $rootScope,
endpoint: "control"
});
control_socket.init();
control_socket.on("limit_reached", function(data) {
$log.debug(data.reason);
Timer.expireSession('session_limit');
});
}
openSocket();
@ -976,13 +986,13 @@ var tower = angular.module('Tower', [
}
if (Authorization.isUserLoggedIn() === false) {
if (next.templateUrl !== (urlPrefix + 'partials/login.html')) {
if (next.templateUrl !== (urlPrefix + 'login/loginBackDrop.partial.html')) {
$location.path('/login');
}
} else if ($rootScope.sessionTimer.isExpired()) {
// gets here on timeout
if (next.templateUrl !== (urlPrefix + 'partials/login.html')) {
$rootScope.sessionTimer.expireSession();
if (next.templateUrl !== (urlPrefix + 'login/loginBackDrop.partial.html')) {
$rootScope.sessionTimer.expireSession('idle');
if (sock) {
sock.socket.socket.disconnect();
}
@ -1010,6 +1020,7 @@ var tower = angular.module('Tower', [
$rootScope.user_is_superuser = Authorization.getUserInfo('is_superuser');
// when the user refreshes we want to open the socket, except if the user is on the login page, which should happen after the user logs in (see the AuthService module for that call to OpenSocket)
if($location.$$url !== '/login'){
$rootScope.sessionTimer = Timer.init();
$rootScope.$emit('OpenSocket');
pendoService.issuePendoIdentity();
}

View File

@ -40,10 +40,6 @@
password_hasSymbol: false, // require one of these symbols to be
// in the password: -!$%^&*()_+|~=`{}[]:";'<>?,./
session_timeout: 1800, // Number of seconds before an inactive session is automatically timed out and forced to log in again.
// Separate from time out value set in API.
variable_edit_modes: { // Options we pass to ControlMirror for editing YAML/JSON variables
yaml: {
mode:"text/x-yaml",

View File

@ -144,7 +144,7 @@ export function JobTemplatesList($scope, $rootScope, $location, $log, $routePara
CreateDialog({
id: 'copy-job-modal' ,
id: 'copy-job-modal',
title: "Copy",
scope: $scope,
buttons: buttons,

View File

@ -46,7 +46,7 @@ angular.module('LoadConfigHelper', ['Utilities'])
$rootScope.$emit('ConfigReady');
}
}, function(response) {
}, function() {
//local_settings.json not found
$log.info('local_settings.json not found');
$rootScope.$emit('ConfigReady');

View File

@ -94,6 +94,8 @@ export default
$rootScope.token_expires = null;
$rootScope.login_username = null;
$rootScope.login_password = null;
clearTimeout($rootScope.idleTimer);
clearTimeout($rootScope.endTimer);
},
getLicense: function () {
@ -109,7 +111,7 @@ export default
setLicense: function (data) {
var license = data.license_info;
license.analytics_status = data.analytics_status;
license.analytics_status = data.analytics_status;
license.version = data.version;
license.tested = false;
Store('license', license);
@ -138,7 +140,8 @@ export default
method: 'GET',
url: '/api/v1/me/',
headers: {
'Authorization': 'Token ' + this.getToken()
'Authorization': 'Token ' + this.getToken(),
"X-Auth-Token": 'Token ' + this.getToken()
}
});
},

View File

@ -22,8 +22,8 @@
* @description
*/
export default
['$rootScope', '$cookieStore', '$location', 'GetBasePath', 'Empty',
function ($rootScope, $cookieStore) {
['$rootScope', '$cookieStore', 'transitionTo', 'CreateDialog', 'Authorization',
function ($rootScope, $cookieStore, transitionTo, CreateDialog, Authorization) {
return {
sessionTime: null,
@ -46,10 +46,32 @@ export default
}
},
expireSession: function () {
isIdle: function() {
var stime = this.getSessionTime()/1000,
now = new Date().getTime()/1000,
diff = stime-now;
if(diff < 61){
return true;
}
else {
return false;
}
},
expireSession: function (reason) {
if(reason === 'session_limit'){
$rootScope.sessionLimitExpired = true;
$rootScope.sessionExpired = false;
}
else if(reason === 'idle'){
$rootScope.sessionExpired = true;
$rootScope.sessionLimitExpired = false;
}
this.sessionTime = 0;
$rootScope.sessionExpired = true;
this.clearTimers();
$cookieStore.put('sessionExpired', true);
transitionTo('signOut');
},
moveForward: function () {
@ -60,6 +82,70 @@ export default
$cookieStore.put('sessionTime', t);
$rootScope.sessionExpired = false;
$cookieStore.put('sessionExpired', false);
this.startTimers();
},
startTimers: function() {
var that = this,
tm = ($AnsibleConfig) ? $AnsibleConfig.session_timeout : 1800,
t = tm - 60;
this.clearTimers();
// make a timeout that will go off in 30 mins to log them out
// unless they extend their time
$rootScope.endTimer = setTimeout(function(){
that.expireSession('idle');
}, tm * 1000);
// notify the user a minute before the end of their session that
// their session is about to expire
if($rootScope.idleTimer){
clearTimeout($rootScope.idleTimer);
}
$rootScope.idleTimer = setTimeout(function() {
if(that.isIdle() === true){
var buttons = [{
"label": "Continue",
"onClick": function() {
// make a rest call here to force the API to
// move the session time forward
Authorization.getUser();
that.moveForward();
$(this).dialog('close');
},
"class": "btn btn-primary",
"id": "idle-modal-button"
}];
if ($rootScope.removeIdleDialogReady) {
$rootScope.removeIdleDialogReady();
}
$rootScope.removeIdleDialogReady = $rootScope.$on('IdleDialogReady', function() {
$('#idle-modal').show();
$('#idle-modal').dialog('open');
});
CreateDialog({
id: 'idle-modal' ,
title: "Idle Session",
scope: $rootScope,
buttons: buttons,
width: 470,
height: 240,
minWidth: 200,
callback: 'IdleDialogReady'
});
}
}, t * 1000);
},
clearTimers: function(){
clearTimeout($rootScope.idleTimer);
clearTimeout($rootScope.endTimer);
},
init: function () {

View File

@ -6,13 +6,17 @@
ng-src="/static/assets/{{ customLogo }}" >
</div>
<div class="modal-body">
<div class="LoginModal-alert" ng-show="!sessionExpired">
<div class="LoginModal-alert" ng-show="!sessionExpired && !sessionLimitExpired">
Welcome to Ansible Tower! &nbsp;Please sign in.
</div>
<div class="LoginModal-alert" ng-show="sessionExpired">
Your session timed out due to inactivity. Please
sign in.
</div>
<div class="LoginModal-alert" ng-show="sessionLimitExpired">
Maximum per-user sessions reached. Please
sign in.
</div>
<form id="login-form"
name="loginForm"
class="form-horizontal"

View File

@ -13,10 +13,5 @@ export default {
Authorization.logout();
$location.path('/login');
}],
templateUrl: '/static/partials/blank.html',
resolve: {
features: ['FeaturesService', function(FeaturesService) {
return FeaturesService.get();
}]
}
templateUrl: '/static/partials/blank.html'
};

View File

@ -0,0 +1,31 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
export default
[ '$rootScope',
function ($rootScope) {
return {
response: function(config) {
if(config.headers('auth-token-timeout') !== null){
$AnsibleConfig.session_timeout = Number(config.headers('auth-token-timeout'));
}
return config;
},
responseError: function(rejection){
if(rejection.data.detail && rejection.data.detail === "Maximum per-user sessions reached"){
$rootScope.sessionTimer.expireSession('session_limit');
return rejection;
}
return rejection;
}
};
}];

View File

@ -0,0 +1,16 @@
/*************************************************
* Copyright (c) 2015 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import restServicesFactory from './restServices.factory';
import interceptors from './interceptors.service';
export default
angular.module('RestServices', [])
.config(['$httpProvider', function($httpProvider) {
$httpProvider.interceptors.push('RestInterceptor');
}])
.factory('Rest', restServicesFactory)
.service('RestInterceptor', interceptors);

View File

@ -55,8 +55,7 @@
*/
export default
angular.module('RestServices', ['ngCookies'])
.factory('Rest', ['$http', '$rootScope', '$cookieStore', '$q', 'Authorization',
['$http', '$rootScope', '$cookieStore', '$q', 'Authorization',
function ($http, $rootScope, $cookieStore, $q, Authorization) {
return {
@ -269,4 +268,4 @@ angular.module('RestServices', ['ngCookies'])
}
};
}
]);
];

View File

@ -158,7 +158,7 @@ angular.module('SocketIO', ['Utilities'])
}
else {
// encountered expired token, redirect to login page
$rootScope.sessionTimer.expireSession();
$rootScope.sessionTimer.expireSession('idle');
$location.url('/login');
}
},

View File

@ -200,7 +200,7 @@ angular.module('Utilities', ['RestServices', 'Utilities', 'sanitizeFilter'])
} else if ((status === 'Token is expired') || (status === 401 && data.detail && data.detail === 'Token is expired') ||
(status === 401 && data.detail && data.detail === 'Invalid token')) {
if ($rootScope.sessionTimer) {
$rootScope.sessionTimer.expireSession();
$rootScope.sessionTimer.expireSession('idle');
}
$location.url('/login');
} else if (data.non_field_errors) {

View File

@ -4,7 +4,7 @@ import '../support/node';
import {describeModule} from '../support/describe-module';
import 'shared/Utilities';
import 'shared/RestServices';
import JobStatusGraph from 'dashboard/graphs/job-status/main';
var resizeHandler = sinon.spy();

View File

@ -58,6 +58,7 @@
<!-- Password Dialog -->
<div id="password-modal" style="display: none;"></div>
<div id="idle-modal" style="display:none">Your session will expire in 1 minute, would you like to continue?</div>
<!-- Generic Form dialog -->
<div id="form-modal" class="modal fade">