Commit 8a847d8c authored by Bas Lijnse's avatar Bas Lijnse

Improved the session mechanism. It now sends session ids with every request,...

Improved the session mechanism. It now sends session ids with every request, checks if sessions are expired, and automatically logs out a user when a session error occurs. You can also explicitly do a manual logout now.

git-svn-id: https://svn.cs.ru.nl/repos/iTask-system/branches/fancyTasks@297 63da3aa8-80fd-4f01-9db8-e6ea747a3da2
parent eb5b6fba
......@@ -48,6 +48,13 @@ div.header #user {
color: #fff;
text-align: right;
}
div.header #user a {
color: #fff;
text-decoration: none;s
}
div.header #user a:hover {
text-decoration: underline;
}
div.leftpanel {
background-color: #ccc;
......
......@@ -4,28 +4,35 @@
Ext.ns('itasks');
itasks.Application = function () {
return {
//ATTRIBUTES
viewport: new Ext.Viewport({
layout: 'fit',
layout: 'card',
activeItem: 0,
layoutConfig: {
deferredRender: false
},
items: {
baseCls: 'bg',
xtype: 'panel'
}
}),
//METHODS
start: function() {
/**
* Starts the client GUI framework
*/
start: function(errorMsg) {
//Create the login window
var loginWindow = new itasks.LoginWindow();
var loginWindow = new itasks.LoginWindow({errorMsg: errorMsg});
var startPanel = this.viewport.getComponent(0);
startPanel.add(loginWindow);
loginWindow.continuation = this.loadUserInterface.createDelegate(this);
loginWindow.show();
},
},
/**
* Loads and builds the GUI
*/
loadUserInterface: function(displayName, sessionId) {
//Remove the login window
......@@ -47,8 +54,10 @@ itasks.Application = function () {
//Start building the GUI
loaderWindow.updateProgress(0.2,'Building User Interface...');
this.gui = new itasks.ApplicationPanel({displayName: displayName, sessionId: sessionId});
this.gui = new itasks.ApplicationPanel({application: this, displayName: displayName, sessionId: sessionId});
this.viewport.add(this.gui);
this.viewport.doLayout();
loaderWindow.updateProgress(0.6,'Initializing User Interface...');
this.gui.init();
......@@ -56,6 +65,9 @@ itasks.Application = function () {
loaderWindow.updateProgress(1.0,'Done.');
loaderWindow.finish();
},
/**
* Starts the main interface
*/
startUserInterface: function() {
var startPanel = this.viewport.getComponent(0);
var loaderWindow = startPanel.getComponent(0);
......@@ -66,11 +78,22 @@ itasks.Application = function () {
startPanel.remove(loaderWindow);
this.viewport.remove(startPanel);
this.viewport.add(this.gui);
this.gui.show();
this.viewport.doLayout();
}
//Switch to the main interface
this.viewport.layout.setActiveItem(1);
//Remove start panel
this.viewport.remove(0);
},
/**
* Resets the main viewport to show the start screen
*/
reset: function() {
this.viewport.remove(0);
this.viewport.add(new Ext.Panel({baseCls: 'bg'}));
this.viewport.layout.setActiveItem(0);
},
restart: function (errorMsg) {
this.reset();
this.start(errorMsg);
}
}
};
\ No newline at end of file
......@@ -5,6 +5,8 @@ Ext.ns('itasks');
itasks.ApplicationPanel = Ext.extend(Ext.Panel, {
application: undefined,
sessionId: undefined,
displayName: undefined,
......@@ -17,7 +19,7 @@ itasks.ApplicationPanel = Ext.extend(Ext.Panel, {
region: 'north',
baseCls: 'header',
height: 75,
html: '<div id="logo" ></div><div id="user">Welcome ' + this.displayName + '</div>'
html: '<div id="logo" ></div><div id="user">Welcome ' + this.displayName + ' | <a id="logout" href="#">Log out &raquo;</a></div>'
},{
id: 'leftpanel',
xtype: 'panel',
......@@ -70,6 +72,7 @@ itasks.ApplicationPanel = Ext.extend(Ext.Panel, {
itasks.ApplicationPanel.superclass.initComponent.apply(this, arguments);
},
init: function () {
//Initializing the gui...
var apppanel = this;
var worklist = this.getComponent('centerpanel').getComponent('worklist');
......@@ -96,8 +99,36 @@ itasks.ApplicationPanel = Ext.extend(Ext.Panel, {
debugpanel.getProcessTableButton().on('click',function() {
worktabs.openProcessTableTab(apppanel);
});
Ext.get('logout').on('click',function() {
apppanel.logout();
});
},
addSessionParam: function (params) {
params['session'] = this.sessionId;
return params;
},
checkSessionResponse: function (response) {
if(response.error) {
this.application.restart(response.error);
}
},
getSessionId: function() {
return this.sessionId;
},
logout: function() {
//Send logout request to the server
Ext.Ajax.request({
url: 'handlers/deauthenticate',
method: "POST",
params: this.addSessionParam({}),
scripts: false,
callback: function () {
//On return, restart the app
this.application.restart();
},
scope: this
});
}
});
\ No newline at end of file
......@@ -5,58 +5,21 @@
Ext.ns('itasks');
itasks.LoginWindow = Ext.extend(Ext.Window, {
//The error display area
errorLabel: new Ext.form.Label({
x: 55,
y: 5,
xtype: 'label',
style: 'color: red; font-weight: bold'
}),
//The embedded form
loginPanel: new Ext.form.FormPanel({
url: 'handlers/authenticate',
baseCls: 'x-plain',
layout: 'absolute',
defaultType: 'textfield',
buttonAlign: 'right',
waitMsgTarget: true,
items: [{
x: 0,
y: 35,
xtype: 'label',
text: 'Username:'
},{
x: 55,
y: 30,
name: 'username',
anchor: '100%'
},{
x: 0,
y: 65,
xtype: 'label',
text: 'Password:'
},{
x: 55,
y: 60,
name: 'password',
anchor: '100%',
inputType: 'password'
}
]
}),
//Initial error message
errorMsg: "",
//Initializion function
initComponent: function() {
//PRIVATE EVENT HANDLERS
var submitHandler = function() {
this.loginPanel.getForm().submit({waitMsg: 'Validating username and password...'});
this.getComponent(0).getForm().submit({waitMsg: 'Validating username and password...'});
};
var successHandler = function(form, action) {
//Clear the error message
this.errorLabel.setText('');
this.getComponent(0).getComponent(0).setText('');
//this.errorLabel.setText('');
//Fade out the window
......@@ -70,9 +33,9 @@ itasks.LoginWindow = Ext.extend(Ext.Window, {
var failureHandler = function(form, action) {
//Show the error and draw attention to the window
if(action.failureType == undefined) {
this.errorLabel.setText(action.result.error);
this.getComponent(0).getComponent(0).setText(action.result.error);
} else {
this.errorLabel.setText("Could not connect to server");
this.getComponent(0).getComponent(0).setText("Could not connect to server");
}
this.getEl().frame('#ff0000');
......@@ -84,7 +47,6 @@ itasks.LoginWindow = Ext.extend(Ext.Window, {
//Construct the login window
Ext.apply(this, {
id: 'loginWindow',
title: 'Login to iTasks',
y: 150,
width: 350,
......@@ -94,9 +56,49 @@ itasks.LoginWindow = Ext.extend(Ext.Window, {
bodyStyle:'padding: 5px;',
closable: false,
resizable: false,
items: this.loginPanel,
items: {
xtype: 'form',
url: 'handlers/authenticate',
baseCls: 'x-plain',
layout: 'absolute',
defaultType: 'textfield',
buttonAlign: 'right',
waitMsgTarget: true,
items: [{
x: 55,
y: 5,
xtype: 'label',
html: this.errorMsg,
style: 'color: red; font-weight: bold'
},{
x: 0,
y: 35,
xtype: 'label',
text: 'Username:'
},{
x: 55,
y: 30,
name: 'username',
anchor: '100%'
},{
x: 0,
y: 65,
xtype: 'label',
text: 'Password:'
},{
x: 55,
y: 60,
name: 'password',
anchor: '100%',
inputType: 'password'
}],
buttons: [{
text: 'Log in',
handler: submitHandler,
scope: this
}]
},
tools: [{
id: 'help',
handler: this.showHelp,
scope: this
}]
......@@ -104,24 +106,16 @@ itasks.LoginWindow = Ext.extend(Ext.Window, {
//initialize the superclass (Ext.Window)
itasks.LoginWindow.superclass.initComponent.apply(this, arguments);
//Add the error label to the form
this.loginPanel.add(this.errorLabel);
//Add a submit button to the form panel to submit the form
this.loginPanel.addButton({
text: 'Log in',
handler: submitHandler,
scope: this
});
var loginPanel = this.getComponent(0);
//Attach the success and failure event handlers
this.loginPanel.on('actioncomplete', successHandler, this);
this.loginPanel.on('actionfailed', failureHandler, this);
loginPanel.on('actioncomplete', successHandler, this);
loginPanel.on('actionfailed', failureHandler, this);
//Add a keymap to connect <enter> in the form to the submit action
this.loginPanel.on('render', function () {
new Ext.KeyMap(this.loginPanel.getEl(), {
loginPanel.on('render', function () {
new Ext.KeyMap(loginPanel.getEl(), {
key: Ext.EventObject.ENTER,
fn: submitHandler,
scope: this
......@@ -139,7 +133,7 @@ itasks.LoginWindow = Ext.extend(Ext.Window, {
});
},
focus: function() {
this.loginPanel.getForm().findField('username').focus();
this.getComponent(0).getForm().findField('username').focus();
},
continuation: function(displayName, sessionId) {
}
......
......@@ -21,8 +21,19 @@ itasks.TaskForestTabPanel = Ext.extend(Ext.Panel, {
setApplicationPanel: function (panel) {
this.applicationPanel = panel;
},
processResponse: function (el, success, response, options) {
if(response.responseText.substr(0,4) != '<div') {
this.applicationPanel.checkSessionResponse(Ext.decode(response.responseText));
}
},
refresh: function() {
this.load({method: 'GET', url: 'handlers/tasktreeforest?session=' + this.applicationPanel.getSessionId()});
this.load({
method: 'GET',
url: 'handlers/tasktreeforest',
params: this.applicationPanel.addSessionParam({}),
callback: this.processResponse,
scope: this
});
}
});
......
......@@ -6,17 +6,21 @@ Ext.ns('itasks');
itasks.WorkListPanel = Ext.extend(Ext.grid.GridPanel, {
applicationPanel: undefined,
workStore: new Ext.data.JsonStore({
url: 'handlers/worklist',
fields: [
{name: 'taskid'},
{name: 'priority'},
{name: 'processname'},
{name: 'subject'},
{name: 'delegator'},
{name: 'timestamp'}
]
workStore: new Ext.data.Store({
proxy: new Ext.data.HttpProxy({
url: 'handlers/worklist'
}),
reader: new Ext.data.JsonReader({
successProperty: 'success'
},[
{name: 'taskid'},
{name: 'priority'},
{name: 'processname'},
{name: 'subject'},
{name: 'delegator'},
{name: 'timestamp'}
])
}),
initComponent: function () {
Ext.apply(this, {
......@@ -39,29 +43,35 @@ itasks.WorkListPanel = Ext.extend(Ext.grid.GridPanel, {
enableHdMenu: false,
stripeRows: true
});
itasks.WorkListPanel.superclass.initComponent.apply(this, arguments);
//Check session error responses
this.store.on('load', function() {
this.applicationPanel.checkSessionResponse(this.store.reader.jsonData);
}, this);
},
setApplicationPanel: function(panel) {
this.applicationPanel = panel;
},
},
/*
* Return the taskid of the selected row
*/
getTaskId: function (index) {
return this.workStore.getAt(index).data.taskid;
return this.store.getAt(index).data.taskid;
},
/*
* Return all task information of the selected row
*/
getTaskInfo: function (index) {
return this.workStore.getAt(index).data;
return this.store.getAt(index).data;
},
/*
* Refresh the list
*/
refresh: function () {
this.store.load({params: {session: this.applicationPanel.getSessionId()}});
this.store.load({
params: this.applicationPanel.addSessionParam({})
});
}
});
......
......@@ -117,6 +117,9 @@ itasks.WorkTabPanel = Ext.extend(Ext.Panel, {
if(success) {
var data = Ext.decode(response.responseText);
//Check for session errors.
this.applicationPanel.checkSessionResponse(data);
//Clear the updates list
this.updates = {};
......@@ -246,30 +249,33 @@ itasks.WorkTabPanel = Ext.extend(Ext.Panel, {
} else {
this.setBusy(true);
}
//Add the state to the updates
this.updates['state'] = Ext.encode(this.state);
//The updates are the primary parameters
var params = this.updates;
//Add the state to the params
params['state'] = Ext.encode(this.state);
//Check if we need to request trace info
var traceArgs = "";
if (this.debugPanel != undefined) {
if(this.debugPanel.traceStates()) {
traceArgs += "&traceStates=1";
params['traceStates'] = 1;
}
if(this.debugPanel.traceUpdates()) {
traceArgs += "&traceUpdates=1";
params['traceUpdates'] = 1;
}
if(this.debugPanel.traceSubTrees()) {
traceArgs += "&traceSubTrees=1";
params['traceSubTrees'] = 1;
}
}
//get the session id
var sessionArg = "&session=" + this.applicationPanel.getSessionId();
//Add the session id
params = this.applicationPanel.addSessionParam(params);
//Send the data to the server
Ext.Ajax.request({
url: 'handlers/work?taskid=' + this.id + traceArgs + sessionArg,
url: 'handlers/work?taskid=' + this.id,
method: "POST",
params: this.updates,
params: params,
scripts: false,
callback: this.processTabData,
scope: this
......
......@@ -38,3 +38,7 @@ deleteIData :: !String !*HSt -> *HSt
changeLifespanIData :: !String !Lifespan !Lifespan !*HSt -> *HSt
getChangedId :: !*HSt -> ([String],!*HSt) // id's of form(s) that have been changed by user
// Storage of the Formstates
storeStates :: !*HSt -> *HSt
getPageStates :: !*HSt -> (![HtmlState], !*HSt)
......@@ -66,3 +66,13 @@ getChangedId :: !*HSt -> ([String],!*HSt) // id of form that has been changed by
getChangedId hst=:{states}
# (ids,states) = getUpdatedIds states
= (ids,{hst & states = states })
storeStates :: !*HSt -> *HSt
storeStates hst =: {states, world}
# (states,world) = storeServerStates states world
= {hst & states = states, world = world}
getPageStates :: !*HSt -> (![HtmlState], !*HSt)
getPageStates hst =: {states}
# (pagestates, states) = getHtmlStates states
= (pagestates, {hst & states = states})
......@@ -10,9 +10,9 @@ handleAuthenticationRequest :: !HTTPRequest *HSt -> (!HTTPResponse, !*HSt)
handleAuthenticationRequest req hst
= case getUserInfo (get "username" req.arg_post) (get "password" req.arg_post) of
Just (uid, roles, displayName)
# (session, hst =:{states, world}) = createSession uid roles hst
# (states, world) = storeServerStates states world
= ({http_emptyResponse & rsp_data = encodeSuccess session.sessionId displayName},{hst & states = states, world = world})
# (session, hst) = createSession uid roles hst
# hst = storeStates hst
= ({http_emptyResponse & rsp_data = encodeSuccess session.sessionId displayName},hst)
Nothing
= ({http_emptyResponse & rsp_data = encodeFailure},hst)
where
......@@ -21,7 +21,7 @@ where
| key == x1 = Just x2
= get key xs
encodeFailure = "{\"success\": false,\" error\": \"Incorrect username or password\"}"
encodeFailure = "{\"success\": false, \"error\": \"Incorrect username or password\"}"
encodeSuccess sid displayName = "{\"success\": true, \"displayName\": \"" +++ displayName +++ "\", sessionId: \"" +++ sid +++ "\"}"
//Hardcoded users
......
definition module DeauthenticationHandler //iTasks.Framework.Handlers.DeauthenticationHandler
import Http, Session
/**
* Handles the ajax requests to terminate the current session
*/
handleDeauthenticationRequest :: !HTTPRequest !Session *HSt -> (!HTTPResponse, !*HSt)
\ No newline at end of file
implementation module DeauthenticationHandler
import Http, Session
handleDeauthenticationRequest :: !HTTPRequest !Session *HSt -> (!HTTPResponse, !*HSt)
handleDeauthenticationRequest request session hst
# hst = destroySession session.sessionId hst
= ({http_emptyResponse & rsp_data = "{\"success\" : \"true\"}"}, hst)
......@@ -27,11 +27,11 @@ handleWorkTabRequest :: !(Task a) !HTTPRequest !Session *HSt -> (!HTTPResponse,
handleWorkTabRequest mainTask request session hst
# thisUserId = session.Session.userId // fetch user id from the session
# taskId = http_getValue "taskid" request.arg_get "error" // fetch task id of the tab selecetd
# (toServer, htmlTree, maybeError, maybeProcessTable, maybeThreadTable, hst =:{states,world})
# (toServer, htmlTree, maybeError, maybeProcessTable, maybeThreadTable, hst)
= calculateTaskTree thisUserId True True True mainTask hst // calculate the TaskTree given the id of the current user
# (taskDone,html,inputs) = determineTaskForTab thisUserId taskId htmlTree // filter out the code and inputs to display in this tab
# (htmlstates,states) = getHtmlStates states // Collect states that must be temporarily stored in the browser
# (states,world) = storeServerStates states world // Write states that are stored on the server
# (htmlstates,hst) = getPageStates hst // Collect states that must be temporarily stored in the browser
# hst =: {states} = storeStates hst // Write states that are stored on the server
//Tracing
# (stateTrace,states) = mbStateTrace request states
......@@ -54,25 +54,25 @@ handleWorkTabRequest mainTask request session hst
, stateTrace = stateTrace
, updateTrace = updateTrace
, subtreeTrace = subTreeTrace
} // create tab data record
= ({http_emptyResponse & rsp_data = toJSON content}, {hst & states = states, world = world}) // create the http response
} // create tab data record
= ({http_emptyResponse & rsp_data = toJSON content}, {hst & states = states}) // create the http response
where
mbStateTrace req states
| http_getValue "traceStates" req.arg_get "" == "1"
| http_getValue "traceStates" req.arg_post "" == "1"
# (trace1,states) = traceInStates states
# (trace2,states) = traceStates states
= (Just (toString (DivTag [] [trace1,trace2])), states)
| otherwise
= (Nothing, states)
mbUpdateTrace req states
| http_getValue "traceUpdates" req.arg_get "" == "1"
| http_getValue "traceUpdates" req.arg_post "" == "1"
# (trace,states) = traceUpdates states
= (Just (toString trace), states)
| otherwise
= (Nothing, states)
mbSubTreeTrace req thisUserId taskId htmlTree
| http_getValue "traceSubTrees" req.arg_get "" == "1"
| http_getValue "traceSubTrees" req.arg_post "" == "1"
= Just (toString (filterTaskTreeOfTask thisUserId taskId htmlTree))
| otherwise
= Nothing
......
......@@ -21,8 +21,11 @@ createSession :: !Int ![String] !*HSt -> (!Session,!*HSt)
* Try to restore an existing session
*
* @param session id