diff -r 4323ec7584c8 acct_mgr/admin.py --- a/acct_mgr/admin.py Tue Jun 17 19:21:53 2008 +0000 +++ b/acct_mgr/admin.py Thu Aug 07 03:29:25 2008 +0700 @@ -59,6 +59,8 @@ self.config.save() self.config.set('account-manager', 'force_passwd_change', req.args.get('force_passwd_change')) + self.config.set('account-manager', 'persistent_sessions', + req.args.get('persistent_sessions')) self.config.save() @@ -81,7 +83,8 @@ ] sections = sorted(sections, key=lambda i: i['name']) data = {'sections': sections, - 'force_passwd_change': self.account_manager.force_passwd_change} + 'force_passwd_change': self.account_manager.force_passwd_change, + 'persistent_sessions': self.account_manager.persistent_sessions} return 'admin_accountsconfig.html', data def _do_users(self, req): diff -r 4323ec7584c8 acct_mgr/api.py --- a/acct_mgr/api.py Tue Jun 17 19:21:53 2008 +0000 +++ b/acct_mgr/api.py Thu Aug 07 03:29:25 2008 +0700 @@ -88,6 +88,13 @@ force_passwd_change = BoolOption('account-manager', 'force_passwd_change', True, doc="Forge the user to change " "password when it's reset.") + persistent_sessions = BoolOption('account-manager', 'persistent_sessions', + False, doc="Allow the user to be " + "remembered across sessions without " + "needing to re-authenticate. This is, " + "user checks a \"Remember Me\" checkbox " + "and, next time he visits the site, he'll " + "be remembered") # Public API @@ -107,17 +114,17 @@ return self.password_store.check_password(user, password) def delete_user(self, user): - db = self.env.get_db_cnx() - cursor = db.cursor() - # Delete session attributes - cursor.execute("DELETE FROM session_attribute where sid=%s", (user,)) - # Delete session - cursor.execute("DELETE FROM session where sid=%s", (user,)) - # Delete any custom permissions set for the user - cursor.execute("DELETE FROM permission where username=%s", (user,)) + db = self.env.get_db_cnx() + cursor = db.cursor() + # Delete session attributes + cursor.execute("DELETE FROM session_attribute where sid=%s", (user,)) + # Delete session + cursor.execute("DELETE FROM session where sid=%s", (user,)) + # Delete any custom permissions set for the user + cursor.execute("DELETE FROM permission where username=%s", (user,)) db.commit() db.close() - # Delete from password store + # Delete from password store self.log.debug('deleted user') if self.password_store.delete_user(user): self._notify('deleted', user) diff -r 4323ec7584c8 acct_mgr/templates/admin_accountsconfig.html --- a/acct_mgr/templates/admin_accountsconfig.html Tue Jun 17 19:21:53 2008 +0000 +++ b/acct_mgr/templates/admin_accountsconfig.html Thu Aug 07 03:29:25 2008 +0700 @@ -39,6 +39,22 @@ No + +
+ diff -r 4323ec7584c8 acct_mgr/templates/login.html --- a/acct_mgr/templates/login.html Tue Jun 17 19:21:53 2008 +0000 +++ b/acct_mgr/templates/login.html Thu Aug 07 03:29:25 2008 +0700 @@ -34,6 +34,10 @@ +diff -r 4323ec7584c8 acct_mgr/web_ui.py --- a/acct_mgr/web_ui.py Tue Jun 17 19:21:53 2008 +0000 +++ b/acct_mgr/web_ui.py Thu Aug 07 03:29:25 2008 +0700 @@ -13,6 +13,7 @@ import os import random import string +import time from trac import perm, util from trac.core import * @@ -411,7 +412,7 @@ class LoginModule(auth.LoginModule): - implements(ITemplateProvider) + implements(ITemplateProvider, IRequestFilter) def authenticate(self, req): if req.method == 'POST' and req.path_info.startswith('/login'): @@ -425,17 +426,110 @@ if req.path_info.startswith('/login') and req.authname == 'anonymous': data = { 'referer': self._referer(req), - 'reset_password_enabled': AccountModule(self.env).reset_password_enabled + 'reset_password_enabled': \ + AccountModule(self.env).reset_password_enabled, + 'persistent_sessions': \ + AccountManager(self.env).persistent_sessions } if req.method == 'POST': data['login_error'] = 'Invalid username or password' return 'login.html', data, None return auth.LoginModule.process_request(self, req) + # IRequestFilter methods + def pre_process_request(self, req, handler): + if 'trac_auth_session' in req.incookie: + # Let's check for a matching IP, proxy'ed or not + remote_addr = req.get_header("X-Forwarded-For") or req.remote_addr + if not req.incookie['trac_auth_session'].value == remote_addr: + self.log.debug("Cookie IP does not match Remote address IP: " + "'%s'!='%s'; Killing Session", + req.incookie['trac_auth_session'].value, + remote_addr) + self._do_logout(req) + # Repeat request after logout in order for the user not to + # think he's logged in + req.redirect(req.path_info) + + + # Let's now check with the one we have on auth_cookie db table + db = self.env.get_db_cnx() + cursor = db.cursor() + cursor.execute("SELECT ipnr from auth_cookie WHERE cookie=%s", + (req.incookie['trac_auth'].value,)) + row = cursor.fetchone() + if not req.incookie['trac_auth_session'].value == row[0]: + self.log.debug("Cookie IP does not match DB IP: '%s'!='%s'; " + "Killing Session", + req.incookie['trac_auth_session'].value, + row[0]) + self._do_logout(req) + # Repeat request after logout in order for the user not to + # think he's logged in + req.redirect(req.path_info) + # XXX: Would there be a way to know if a user authenticated using a + # cookie and not the login form, and, if he requested /prefs force him + # to re-authenticate? + return handler + + def post_process_request(self, req, template, data, content_type): + return (template, data, content_type) + + def _get_name_for_cookie(self, req, cookie): + name = auth.LoginModule._get_name_for_cookie(self, req, cookie) + if not AccountManager(self.env).persistent_sessions: + # Persistent sessions not enabled + return name + + self.env.log.debug('Updating auth cookie %s for user %s' % + (cookie.value, name)) + db = self.env.get_db_cnx() + cursor = db.cursor() + cursor.execute('UPDATE auth_cookie SET time=%s WHERE cookie=%s', + (int(time.time()), cookie.value)) + db.commit() + req.outcookie['trac_auth'] = cookie.value + req.outcookie['trac_auth']['path'] = self.env.href() + + if 'trac_auth_session' in req.incookie: + # Let's check for a matching IP, proxy'ed or not + remote_addr = req.get_header("X-Forwarded-For") or req.remote_addr + if req.incookie['trac_auth_session'].value == remote_addr: + req.outcookie['trac_auth']['expires'] = 86400 * 30 + return name + def _do_login(self, req): if not req.remote_user: req.redirect(self.env.abs_href()) - return auth.LoginModule._do_login(self, req) + res = auth.LoginModule._do_login(self, req) + if req.args.get('rememberme', '0') == '1': + req.outcookie['trac_auth']['expires'] = 86400 * 30 + req.outcookie['trac_auth_session'] = req.remote_addr + req.outcookie['trac_auth_session']['expires'] = 86400 * 30 + return res + + def _do_logout(self, req): + """Log the user out. + + Simply deletes the corresponding record from the auth_cookie table. + """ + if req.authname == 'anonymous': + # Not logged in + return + + # While deleting this cookie we also take the opportunity to delete + # cookies older than 30 days + db = self.env.get_db_cnx() + cursor = db.cursor() + cursor.execute("DELETE FROM auth_cookie WHERE name=%s OR time < %s", + (req.authname, int(time.time()) - 86400 * 30)) + db.commit() + self._expire_cookie(req) + + # Expire the persistent session cookie + req.outcookie['trac_auth_session'] = '' + req.outcookie['trac_auth_session']['path'] = self.env.href() + req.outcookie['trac_auth_session']['expires'] = -10000 def _remote_user(self, req): user = req.args.get('user')