Ticket #131: remember_me.3.patch

File remember_me.3.patch, 10.5 kB (added by dottedmag, 5 months ago)

Updated patch against r3857

  • a/acct_mgr/admin.py

    old new  
    5959                    self.config.save() 
    6060            self.config.set('account-manager', 'force_passwd_change', 
    6161                            req.args.get('force_passwd_change')) 
     62            self.config.set('account-manager', 'persistent_sessions', 
     63                            req.args.get('persistent_sessions')) 
    6264            self.config.save() 
    6365 
    6466 
     
    8183        ] 
    8284        sections = sorted(sections, key=lambda i: i['name']) 
    8385        data = {'sections': sections, 
    84                 'force_passwd_change': self.account_manager.force_passwd_change} 
     86                'force_passwd_change': self.account_manager.force_passwd_change, 
     87                'persistent_sessions': self.account_manager.persistent_sessions} 
    8588        return 'admin_accountsconfig.html', data 
    8689 
    8790    def _do_users(self, req): 
  • a/acct_mgr/api.py

    old new  
    8888    force_passwd_change = BoolOption('account-manager', 'force_passwd_change', 
    8989                                     True, doc="Forge the user to change " 
    9090                                     "password when it's reset.") 
     91    persistent_sessions = BoolOption('account-manager', 'persistent_sessions', 
     92                                     False, doc="Allow the user to be " 
     93                                     "remembered across sessions without " 
     94                                     "needing to re-authenticate. This is, " 
     95                                     "user checks a \"Remember Me\" checkbox " 
     96                                     "and, next time he visits the site, he'll " 
     97                                     "be remembered") 
    9198 
    9299    # Public API 
    93100 
     
    107114        return self.password_store.check_password(user, password) 
    108115 
    109116    def delete_user(self, user): 
    110         db = self.env.get_db_cnx()  
    111         cursor = db.cursor()  
    112         # Delete session attributes  
    113         cursor.execute("DELETE FROM session_attribute where sid=%s", (user,))  
    114         # Delete session  
    115         cursor.execute("DELETE FROM session where sid=%s", (user,))  
    116         # Delete any custom permissions set for the user  
    117         cursor.execute("DELETE FROM permission where username=%s", (user,))  
     117        db = self.env.get_db_cnx() 
     118        cursor = db.cursor() 
     119        # Delete session attributes 
     120        cursor.execute("DELETE FROM session_attribute where sid=%s", (user,)) 
     121        # Delete session 
     122        cursor.execute("DELETE FROM session where sid=%s", (user,)) 
     123        # Delete any custom permissions set for the user 
     124        cursor.execute("DELETE FROM permission where username=%s", (user,)) 
    118125        db.commit() 
    119126        db.close() 
    120         # Delete from password store  
     127        # Delete from password store 
    121128        self.log.debug('deleted user') 
    122129        if self.password_store.delete_user(user): 
    123130            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  
    1313import os 
    1414import random 
    1515import string 
     16import time 
    1617 
    1718from trac import perm, util 
    1819from trac.core import * 
     
    411412 
    412413class LoginModule(auth.LoginModule): 
    413414 
    414     implements(ITemplateProvider
     415    implements(ITemplateProvider, IRequestFilter
    415416 
    416417    def authenticate(self, req): 
    417418        if req.method == 'POST' and req.path_info.startswith('/login'): 
     
    425426        if req.path_info.startswith('/login') and req.authname == 'anonymous': 
    426427            data = { 
    427428                'referer': self._referer(req), 
    428                 'reset_password_enabled': AccountModule(self.env).reset_password_enabled 
     429                'reset_password_enabled': \ 
     430                                AccountModule(self.env).reset_password_enabled, 
     431                'persistent_sessions': \ 
     432                                    AccountManager(self.env).persistent_sessions 
    429433            } 
    430434            if req.method == 'POST': 
    431435                data['login_error'] = 'Invalid username or password' 
    432436            return 'login.html', data, None 
    433437        return auth.LoginModule.process_request(self, req) 
    434438 
     439    # IRequestFilter methods 
     440    def pre_process_request(self, req, handler): 
     441        if 'trac_auth_session' in req.incookie: 
     442            # Let's check for a matching IP, proxy'ed or not 
     443            remote_addr = req.get_header("X-Forwarded-For") or req.remote_addr 
     444            if not req.incookie['trac_auth_session'].value == remote_addr: 
     445                self.log.debug("Cookie IP does not match Remote address IP: " 
     446                               "'%s'!='%s'; Killing Session", 
     447                               req.incookie['trac_auth_session'].value, 
     448                               remote_addr) 
     449                self._do_logout(req) 
     450                # Repeat request after logout in order for the user not to 
     451                # think he's logged in 
     452                req.redirect(req.path_info) 
     453 
     454 
     455            # Let's now check with the one we have on auth_cookie db table 
     456            db = self.env.get_db_cnx() 
     457            cursor = db.cursor() 
     458            cursor.execute("SELECT ipnr from auth_cookie WHERE cookie=%s", 
     459                           (req.incookie['trac_auth'].value,)) 
     460            row = cursor.fetchone() 
     461            if not req.incookie['trac_auth_session'].value == row[0]: 
     462                self.log.debug("Cookie IP does not match DB IP: '%s'!='%s'; " 
     463                               "Killing Session", 
     464                               req.incookie['trac_auth_session'].value, 
     465                               row[0]) 
     466                self._do_logout(req) 
     467                # Repeat request after logout in order for the user not to 
     468                # think he's logged in 
     469                req.redirect(req.path_info) 
     470        # XXX: Would there be a way to know if a user authenticated using a 
     471        # cookie and not the login form, and, if he requested /prefs force him 
     472        # to re-authenticate? 
     473        return handler 
     474 
     475    def post_process_request(self, req, template, data, content_type): 
     476        return (template, data, content_type) 
     477 
     478    def _get_name_for_cookie(self, req, cookie): 
     479        name = auth.LoginModule._get_name_for_cookie(self, req, cookie) 
     480        if not AccountManager(self.env).persistent_sessions: 
     481            # Persistent sessions not enabled 
     482            return name 
     483 
     484        self.env.log.debug('Updating auth cookie %s for user %s' % 
     485                            (cookie.value, name)) 
     486        db = self.env.get_db_cnx() 
     487        cursor = db.cursor() 
     488        cursor.execute('UPDATE auth_cookie SET time=%s WHERE cookie=%s', 
     489                        (int(time.time()), cookie.value)) 
     490        db.commit() 
     491        req.outcookie['trac_auth'] = cookie.value 
     492        req.outcookie['trac_auth']['path'] = self.env.href() 
     493 
     494        if 'trac_auth_session' in req.incookie: 
     495            # Let's check for a matching IP, proxy'ed or not 
     496            remote_addr = req.get_header("X-Forwarded-For") or req.remote_addr 
     497            if req.incookie['trac_auth_session'].value == remote_addr: 
     498                req.outcookie['trac_auth']['expires'] = 86400 * 30 
     499        return name 
     500 
    435501    def _do_login(self, req): 
    436502        if not req.remote_user: 
    437503            req.redirect(self.env.abs_href()) 
    438         return auth.LoginModule._do_login(self, req) 
     504        res = auth.LoginModule._do_login(self, req) 
     505        if req.args.get('rememberme', '0') == '1': 
     506            req.outcookie['trac_auth']['expires'] = 86400 * 30 
     507            req.outcookie['trac_auth_session'] = req.remote_addr 
     508            req.outcookie['trac_auth_session']['expires'] = 86400 * 30 
     509        return res 
     510 
     511    def _do_logout(self, req): 
     512        """Log the user out. 
     513 
     514        Simply deletes the corresponding record from the auth_cookie table. 
     515        """ 
     516        if req.authname == 'anonymous': 
     517            # Not logged in 
     518            return 
     519 
     520        # While deleting this cookie we also take the opportunity to delete 
     521        # cookies older than 30 days 
     522        db = self.env.get_db_cnx() 
     523        cursor = db.cursor() 
     524        cursor.execute("DELETE FROM auth_cookie WHERE name=%s OR time < %s", 
     525                        (req.authname, int(time.time()) - 86400 * 30)) 
     526        db.commit() 
     527        self._expire_cookie(req) 
     528 
     529        # Expire the persistent session cookie 
     530        req.outcookie['trac_auth_session'] = '' 
     531        req.outcookie['trac_auth_session']['path'] = self.env.href() 
     532        req.outcookie['trac_auth_session']['expires'] = -10000 
    439533 
    440534    def _remote_user(self, req): 
    441535        user = req.args.get('user')