Ticket #131: remember_me.2.patch

File remember_me.2.patch, 10.3 kB (added by s0undt3ch, 6 months ago)

This one also checks against the ip stored on auth_cookie

  • a/acct_mgr/admin.py

    old new  
    6060                    self.config.save() 
    6161            self.config.set('account-manager', 'force_passwd_change', 
    6262                            req.args.get('force_passwd_change')) 
     63            self.config.set('account-manager', 'persistent_sessions', 
     64                            req.args.get('persistent_sessions')) 
    6365            self.config.save() 
    6466 
    6567 
     
    8284        ] 
    8385        sections = sorted(sections, key=lambda i: i['name']) 
    8486        data = {'sections': sections, 
    85                 'force_passwd_change': self.account_manager.force_passwd_change} 
     87                'force_passwd_change': self.account_manager.force_passwd_change, 
     88                'persistent_sessions': self.account_manager.persistent_sessions} 
    8689        return 'admin_accountsconfig.html', data 
    8790 
    8891    def _do_users(self, req): 
  • a/acct_mgr/api.py

    old new  
    9090                                     "password when it's reset.") 
    9191    verify_accounts = BoolOption('account-manager', 'verify_accounts', True, 
    9292                                 "Force user to verify their email address") 
     93    persistent_sessions = BoolOption('account-manager', 'persistent_sessions', 
     94                                     False, doc="Allow the user to be " 
     95                                     "remembered across sessions without " 
     96                                     "needing to re-authenticate. This is, " 
     97                                     "user checks a \"Remember Me\" checkbox " 
     98                                     "and, next time he visits the site, he'll " 
     99                                     "be remembered") 
    93100 
    94101    # Public API 
    95102 
     
    109116        return self.password_store.check_password(user, password) 
    110117 
    111118    def delete_user(self, user): 
    112         db = self.env.get_db_cnx()  
    113         cursor = db.cursor()  
    114         # Delete session attributes  
    115         cursor.execute("DELETE FROM session_attribute where sid=%s", (user,))  
    116         # Delete session  
    117         cursor.execute("DELETE FROM session where sid=%s", (user,))  
    118         # Delete any custom permissions set for the user  
    119         cursor.execute("DELETE FROM permission where username=%s", (user,))  
     119        db = self.env.get_db_cnx() 
     120        cursor = db.cursor() 
     121        # Delete session attributes 
     122        cursor.execute("DELETE FROM session_attribute where sid=%s", (user,)) 
     123        # Delete session 
     124        cursor.execute("DELETE FROM session where sid=%s", (user,)) 
     125        # Delete any custom permissions set for the user 
     126        cursor.execute("DELETE FROM permission where username=%s", (user,)) 
    120127        db.commit() 
    121128        db.close() 
    122         # Delete from password store  
     129        # Delete from password store 
    123130        self.log.debug('deleted user') 
    124131        if self.password_store.delete_user(user): 
    125132            self._notify('deleted', user) 
  • a/acct_mgr/templates/admin_accountsconfig.html

    old new  
    3939        <input type="radio" name="force_passwd_change" value="false" 
    4040          checked="${not force_passwd_change and 'checked' or None}">No</input> 
    4141      </fieldset> 
     42 
     43      <fieldset> 
     44        <legend>Persistent Sessions</legend> 
     45        <label for="persistent_sessions"> 
     46          Allow the user to be remembered across sessions without needing to 
     47          re-authenticate?<br/> 
     48          This is, user checks a "Remember Me" <tt>checkbox</tt> and, next time 
     49          he visits the site,<br/> 
     50          he'll be remembered and automatically authenticated. 
     51        </label> 
     52        <input type="radio" name="persistent_sessions" value="true" 
     53          checked="${persistent_sessions and 'checked' or None}">Yes</input> 
     54        <input type="radio" name="persistent_sessions" value="false" 
     55          checked="${not persistent_sessions and 'checked' or None}">No</input> 
     56      </fieldset> 
     57 
    4258      <div class="buttons"> 
    4359        <input type="submit" name="save" value="Save" /> 
    4460      </div> 
  • a/acct_mgr/templates/login.html

    old new  
    3434          <label for="password">Password:</label> 
    3535          <input type="password" id="password" name="password" class="textwidget" size="20" /> 
    3636        </div> 
     37        <div py:if="persistent_sessions"> 
     38          <input type="checkbox" id="rememberme" name="rememberme" value="1" /> 
     39          <label for="rememberme">Remember me</label> 
     40        </div> 
    3741        <input type="submit" value="Login" /> 
    3842 
    3943        <p py:if="reset_password_enabled"> 
  • a/acct_mgr/web_ui.py

    old new  
    1111 
    1212import random 
    1313import string 
     14import time 
    1415 
    1516from trac import perm, util 
    1617from trac.core import * 
     
    416417 
    417418class LoginModule(auth.LoginModule): 
    418419 
    419     implements(ITemplateProvider
     420    implements(ITemplateProvider, IRequestFilter
    420421 
    421422    def authenticate(self, req): 
    422423        if req.method == 'POST' and req.path_info.startswith('/login'): 
     
    430431        if req.path_info.startswith('/login') and req.authname == 'anonymous': 
    431432            data = { 
    432433                'referer': self._referer(req), 
    433                 'reset_password_enabled': AccountModule(self.env).reset_password_enabled 
     434                'reset_password_enabled': \ 
     435                                AccountModule(self.env).reset_password_enabled, 
     436                'persistent_sessions': \ 
     437                                    AccountManager(self.env).persistent_sessions 
    434438            } 
    435439            if req.method == 'POST': 
    436440                data['login_error'] = 'Invalid username or password' 
    437441            return 'login.html', data, None 
    438442        return auth.LoginModule.process_request(self, req) 
    439443 
     444    # IRequestFilter methods 
     445    def pre_process_request(self, req, handler): 
     446        if 'trac_auth_session' in req.incookie: 
     447            # Let's check for a matching IP, proxy'ed or not 
     448            remote_addr = req.get_header("X-Forwarded-For") or req.remote_addr 
     449            if not req.incookie['trac_auth_session'].value == remote_addr: 
     450                self.log.debug("Cookie IP does not match Remote address IP: " 
     451                               "'%s'!='%s'; Killing Session", 
     452                               req.incookie['trac_auth_session'].value, 
     453                               remote_addr) 
     454                self._do_logout(req) 
     455                # Repeat request after logout in order for the user not to 
     456                # think he's logged in 
     457                req.redirect(req.path_info) 
     458 
     459 
     460            # Let's now check with the one we have on auth_cookie db table 
     461            db = self.env.get_db_cnx() 
     462            cursor = db.cursor() 
     463            cursor.execute("SELECT ipnr from auth_cookie WHERE cookie=%s", 
     464                           (req.incookie['trac_auth'].value,)) 
     465            row = cursor.fetchone() 
     466            if not req.incookie['trac_auth_session'].value == row[0]: 
     467                self.log.debug("Cookie IP does not match DB IP: '%s'!='%s'; " 
     468                               "Killing Session", 
     469                               req.incookie['trac_auth_session'].value, 
     470                               row[0]) 
     471                self._do_logout(req) 
     472                # Repeat request after logout in order for the user not to 
     473                # think he's logged in 
     474                req.redirect(req.path_info) 
     475        # XXX: Would there be a way to know if a user authenticated using a 
     476        # cookie and not the login form, and, if he requested /prefs force him 
     477        # to re-authenticate? 
     478        return handler 
     479 
     480    def post_process_request(self, req, template, data, content_type): 
     481        return (template, data, content_type) 
     482 
     483    def _get_name_for_cookie(self, req, cookie): 
     484        name = auth.LoginModule._get_name_for_cookie(self, req, cookie) 
     485        if not AccountManager(self.env).persistent_sessions: 
     486            # Persistent sessions not enabled 
     487            return name 
     488 
     489        self.env.log.debug('Updating auth cookie %s for user %s' % 
     490                            (cookie.value, name)) 
     491        db = self.env.get_db_cnx() 
     492        cursor = db.cursor() 
     493        cursor.execute('UPDATE auth_cookie SET time=%s WHERE cookie=%s', 
     494                        (int(time.time()), cookie.value)) 
     495        db.commit() 
     496        req.outcookie['trac_auth'] = cookie.value 
     497        req.outcookie['trac_auth']['path'] = self.env.href() 
     498 
     499        if 'trac_auth_session' in req.incookie: 
     500            # Let's check for a matching IP, proxy'ed or not 
     501            remote_addr = req.get_header("X-Forwarded-For") or req.remote_addr 
     502            if req.incookie['trac_auth_session'].value == remote_addr: 
     503                req.outcookie['trac_auth']['expires'] = 86400 * 30 
     504        return name 
     505 
    440506    def _do_login(self, req): 
    441507        if not req.remote_user: 
    442508            req.redirect(self.env.abs_href()) 
    443         return auth.LoginModule._do_login(self, req) 
     509        res = auth.LoginModule._do_login(self, req) 
     510        if req.args.get('rememberme', '0') == '1': 
     511            req.outcookie['trac_auth']['expires'] = 86400 * 30 
     512            req.outcookie['trac_auth_session'] = req.remote_addr 
     513            req.outcookie['trac_auth_session']['expires'] = 86400 * 30 
     514        return res 
     515 
     516    def _do_logout(self, req): 
     517        """Log the user out. 
     518 
     519        Simply deletes the corresponding record from the auth_cookie table. 
     520        """ 
     521        if req.authname == 'anonymous': 
     522            # Not logged in 
     523            return 
     524 
     525        # While deleting this cookie we also take the opportunity to delete 
     526        # cookies older than 30 days 
     527        db = self.env.get_db_cnx() 
     528        cursor = db.cursor() 
     529        cursor.execute("DELETE FROM auth_cookie WHERE name=%s OR time < %s", 
     530                        (req.authname, int(time.time()) - 86400 * 30)) 
     531        db.commit() 
     532        self._expire_cookie(req) 
     533 
     534        # Expire the persistent session cookie 
     535        req.outcookie['trac_auth_session'] = '' 
     536        req.outcookie['trac_auth_session']['path'] = self.env.href() 
     537        req.outcookie['trac_auth_session']['expires'] = -10000 
    444538 
    445539    def _remote_user(self, req): 
    446540        user = req.args.get('user')