Skip to content

Commit

Permalink
Refactoring, quality control
Browse files Browse the repository at this point in the history
  • Loading branch information
jpic committed May 18, 2012
1 parent 3ce31ed commit 130da20
Show file tree
Hide file tree
Showing 9 changed files with 118 additions and 89 deletions.
2 changes: 1 addition & 1 deletion README.rst
@@ -1,4 +1,4 @@
.. image:: https://secure.travis-ci.org/yourlabs/django-session-security.png?branch=master
.. .. image:: https://secure.travis-ci.org/yourlabs/django-session-security.png?branch=master
This app provides a mechanism to logout inactive authenticated users. An
inactive browser should be logged out automatically if the user left his
Expand Down
4 changes: 2 additions & 2 deletions docs/source/conf.py
Expand Up @@ -68,9 +68,9 @@
# built documents.
#
# The short X.Y version.
version = '0.3'
version = '0.1'
# The full version, including alpha/beta/rc tags.
release = '0.3alpha'
release = '0.1rc1'

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
Expand Down
6 changes: 5 additions & 1 deletion session_security/middleware.py
Expand Up @@ -37,11 +37,12 @@ def process_request(self, request):
return

now = datetime.datetime.now()

data = request.session.get('session_security', {
'LOGOUT_URL': LOGOUT_URL,
'LOGIN_URL': LOGIN_URL,
'EXPIRE_AFTER': EXPIRE_AFTER,
'WARN_BEFORE': WARN_BEFORE,
'WARN_AFTER': WARN_AFTER,
'last_activity': now,
})

Expand All @@ -52,4 +53,7 @@ def process_request(self, request):
'%s?next=%s' % (LOGIN_URL, request.path_info))

data['last_activity'] = now
if 'session_security' in request.session.keys():
print 'was', request.session['session_security']['last_activity']
request.session['session_security'] = data
print 'is', request.session['session_security']['last_activity']
11 changes: 6 additions & 5 deletions session_security/settings.py
Expand Up @@ -7,12 +7,12 @@
leaves his browser during 10 minutes, the session will expire. Overridable
through settings.SESSION_SECURITY_EXPIRE_AFTER
WARN_BEFORE
WARN_AFTER
The number of seconds before session expiry that should trigger the warning
dialog. Default is 30, so if the user opens a page, and leaves his browser,
a dialog will show up 30 seconds before the session expires, allowing the
user to extend his session. Overridable through
settings.SESSION_SECURITY_WARN_BEFORE.
settings.SESSION_SECURITY_WARN_AFTER.
PASSIVE_URLS
Urls that should not count as activity when hit. For example, an ajax
Expand All @@ -38,21 +38,22 @@
from django.core import urlresolvers
from django.conf import settings

__all__ = ['EXPIRE_AFTER', 'WARN_BEFORE', 'LOGIN_URL', 'LOGOUT_URL', 'PASSIVE_URLS']
__all__ = ['EXPIRE_AFTER', 'WARN_AFTER', 'LOGIN_URL', 'LOGOUT_URL', 'PASSIVE_URLS', 'SKEW_MARGIN']

EXPIRE_AFTER = getattr(settings, 'SESSION_SECURITY_EXPIRE_AFTER', 600)

WARN_BEFORE = getattr(settings, 'SESSION_SECURITY_WARN_BEFORE', 30)
WARN_AFTER = getattr(settings, 'SESSION_SECURITY_WARN_AFTER', 30)

LOGIN_URL = settings.LOGIN_URL

LOGOUT_URL = getattr(settings, 'LOGOUT_URL', False)

SKEW_MARGIN = getattr(settings, 'SESSION_SECURITY_SKEW_MARGIN', 3)

PASSIVE_URLS = getattr(settings, 'SESSION_SECURITY_PASSIVE_URLS', [])
PASSIVE_URLS += [
urlresolvers.reverse('session_security_ping'),
LOGOUT_URL,
LOGIN_URL,
]

if not LOGOUT_URL:
Expand Down
127 changes: 78 additions & 49 deletions session_security/static/session_security/script.js
@@ -1,66 +1,95 @@
var SessionSecurity = function() {
// Show the dialog and create a timeout for ping() after WARN_BEFORE.
// Because this warning should happen WARN_BEFORE seconds before actual
// expiry.
this.warn = function() {
this.dialog.show();
this.timeout = setTimeout(this.ping, this.WARN_BEFORE * 1000)
function addSeconds(date, seconds) {
var sum = date.getSeconds() + seconds;

if (sum > 59) {
date.setMinutes(date.getMinutes() + 1)
date.setSeconds(sum - 60);
} else if (sum < 1) {
date.setMinutes(date.getMinutes() - 1)
date.setSeconds(sum + 60);
} else {
date.setSeconds(sum);
}

// Redirect to LOGOUT_URL?next=/current/url/
return date;
}

var SessionSecurity = function() {
// HTML element that should show to warn the user that his session will expire
this.warningElement = $('#session_security_warning');

// Callback that should display the user with a login prompt
this.expire = function() {
document.location.href = this.LOGOUT_URL + '?next=' + document.location.href;
$.get(this.LOGOUT_URL, function() {
document.location.href = sessionSecurity.LOGIN_URL + '?next=' + document.location.pathname;
});
}

// Element that contains the modal dialog, which should contain a question
// like 'Do you want to extend your session ?' and provide an element of
// class 'yes' and another of class 'no'.
this.dialog = $('#session_security_warning');
// Callback that should display the warning
this.warn = function() {
this.warningElement.fadeIn();
}

this.initialize = function() {
// When the user clicks 'Yes':
// - hide the dialog that this.warn() has displayed,
// - remove the timeout that this.warn() has setup,
// - POST to ExtendSessionView,
// - on POST success, set this.ping to run again later.
this.dialog.find('.yes').click(function() {
sessionSecurity.dialog.hide();
// Callback for activity events, mouse move, keyboard move, scroll ...
this.activity = function() {
if (sessionSecurity.warningElement.is(':visible')) {
console.log('activity forces tick');
sessionSecurity.warningElement.hide();
clearTimeout(sessionSecurity.timeout);
sessionSecurity.tick();
}
sessionSecurity.lastActivity = new Date();
}

$.post(sessionSecurity.extendUrl, function(data, textStatus, jqXHR) {
sessionSecurity.timeout = setTimeout(sessionSecurity.ping,
(sessionSecurity.EXPIRE_AFTER - sessionSecurity.WARN_BEFORE) * 1000);
});
});

// When the user clicks 'No', hide the dialog that this.warn() has displayed().
this.dialog.find('.no').click(function() {
sessionSecurity.dialog.hide();
});
// Timed callback
this.tick = function() {
var now = new Date();
var sinceActivity = Math.floor((now - sessionSecurity.lastActivity) / 1000)

// Given the seconds elapsed since last activity, ask the server how
// long to wait before another tick
$.post(
sessionSecurity.pingUrl,
{
'sinceActivity': sinceActivity
},
function(data, textStatus, jqXHR) {
var sinceActivity = parseInt(data);
sessionSecurity.lastActivity = addSeconds(new Date(), sinceActivity * -1);
console.log(sessionSecurity.lastActivity)

expireIn = sessionSecurity.EXPIRE_AFTER - sinceActivity;
warnIn = sessionSecurity.WARN_AFTER - sinceActivity;

console.log(sinceActivity, expireIn, warnIn)

// POST to PingView, which may return 3 kind of values:
// - call expire() if 'expire' string was responded,
// - call warn() if 'warn' string was responded,
// - hide the dialog and set a timeout for ping after the number of
// responded seconds.
this.ping = function() {
$.post(sessionSecurity.pingUrl, function(data) {
if (data == 'expire') {
if (expireIn <= 0) {
sessionSecurity.expire();
} else if (data == 'warn') {
} else if (warnIn <= 0) {
sessionSecurity.warn();
sessionSecurity.timeout = setTimeout(sessionSecurity.tick,
expireIn * 1000);
} else {
sessionSecurity.dialog.hide();
sessionSecurity.timeout = setTimeout(sessionSecurity.ping,
parseInt(data) * 1000)
sessionSecurity.timeout = setTimeout(sessionSecurity.tick,
warnIn * 1000);
}
});
}
}
);
}

this.initialize = function() {
// precalculate WARN_BEFORE
this.WARN_BEFORE = this.EXPIRE_AFTER - this.WARN_AFTER;

// Initiate this.lastActivity
this.lastActivity = new Date();

// try to monitor for activity in the page
$('*').scroll(this.activity);
$('*').keyup(this.activity);
$('*').click(this.activity);

// At the end of SessionSecurity constructor, set ping() to run when
// the user is supposed to be warned that his session will expire.
this.timeout = setTimeout(this.ping,
(this.EXPIRE_AFTER - this.WARN_BEFORE) * 1000);
this.timeout = setTimeout(sessionSecurity.tick, this.WARN_AFTER*1000);
}
}

3 changes: 1 addition & 2 deletions session_security/templates/session_security/all.html
Expand Up @@ -30,8 +30,7 @@
LOGIN_URL: '{{ request.session.session_security.LOGIN_URL }}',
LOGOUT_URL: '{{ request.session.session_security.LOGOUT_URL }}',
EXPIRE_AFTER: {{ request.session.session_security.EXPIRE_AFTER }},
WARN_BEFORE: {{ request.session.session_security.WARN_BEFORE }},
extendUrl: '{% url 'session_security_extend_session' %}',
WARN_AFTER: {{ request.session.session_security.WARN_AFTER }},
pingUrl: '{% url 'session_security_ping' %}',
}, sessionSecurity);

Expand Down
10 changes: 1 addition & 9 deletions session_security/urls.py
Expand Up @@ -3,25 +3,17 @@
session_security_ping
Checks the server side status of the session.
session_security_extend_session
Artificially update last_activity to postpone session expiry.
"""

from django.conf.urls.defaults import url, patterns
from django.contrib.auth.decorators import login_required

from views import ExtendSessionView, PingView
from views import PingView

urlpatterns = patterns('',
url(
'ping/$',
PingView.as_view(),
name='session_security_ping',
),
url(
'extend/$',
login_required(ExtendSessionView.as_view()),
name='session_security_extend_session',
),
)
42 changes: 23 additions & 19 deletions session_security/views.py
@@ -1,9 +1,12 @@
import datetime

from django.contrib import auth
from django.views import generic
from django import http

from settings import EXPIRE_AFTER, WARN_BEFORE
from settings import WARN_AFTER, EXPIRE_AFTER, SKEW_MARGIN

__all__ = ['PingView',]


class PingView(generic.View):
Expand All @@ -21,27 +24,28 @@ def post(self, request, *args, **kwargs):
now = datetime.datetime.now()

if 'session_security' not in request.session.keys():
return http.HttpResponse('expire')
return http.HttpResponse('-1')

last = request.session['session_security']['last_activity']
delta = now - last
client_since_activity = int(request.POST.get('sinceActivity', 0))
client_last_activity = now - datetime.timedelta(seconds=client_since_activity)

if delta.seconds > EXPIRE_AFTER:
return http.HttpResponse('expire')
elif delta.seconds > EXPIRE_AFTER - WARN_BEFORE:
return http.HttpResponse('warn')
else:
return http.HttpResponse(delta.seconds)
server_last_activity = request.session['session_security']['last_activity']
server_since_activity = (now - server_last_activity).seconds

print client_since_activity, server_since_activity

class ExtendSessionView(generic.View):
def post(self, request, *args, **kwargs):
"""
Update last activity datetime.
if server_last_activity > client_last_activity:
last_activity = server_last_activity
since_activity = server_since_activity
else:
last_activity = client_last_activity
since_activity = client_since_activity

Called when the user clicks 'yes' in the javascript dialog.
"""
request.session['session_security']['last_activity'] = client_last_activity
request.session.save()

now = datetime.datetime.now()
request.session['session_security']['last_activity'] = now
return http.HttpResponse()
if since_activity > EXPIRE_AFTER:
auth.logout(request)

print "RETURN:", since_activity
return http.HttpResponse(since_activity)
2 changes: 1 addition & 1 deletion setup.py
Expand Up @@ -10,7 +10,7 @@ def read(fname):

setup(
name='django-session-security',
version='1.0alpha',
version='0.1',
description='Let the user secure his session for usage in public computers',
author='James Pic',
author_email='jamespic@gmail.com',
Expand Down

0 comments on commit 130da20

Please sign in to comment.