Edgewall Software

Ticket #1855: AuthProvider_refactor_r2036.patch

File AuthProvider_refactor_r2036.patch, 11.5 KB (added by Rede <redetin@…>, 7 years ago)

Patch for authentication system refactoring

  • trac/web/auth.py

     
    2222from __future__ import generators 
    2323 
    2424import time 
     25import re 
    2526 
    2627from trac import util 
    2728from trac.core import * 
    2829from trac.web.chrome import INavigationContributor 
     30from trac.web.main import IRequestHandler 
    2931 
     32class IAuthenticationProvider(Interface): 
     33    """ 
     34    Extension point interface for adding authentication provider to the 
     35    Trac. 
     36    """ 
     37     
     38    def authenticate(self, req): 
     39        """ 
     40        Authenticate user. If user is authenticated, this method must return 
     41        true value. 
     42        """ 
    3043 
    31 class Authenticator: 
     44class Authenticator(Component): 
     45 
     46    auth_providers = ExtensionPoint(IAuthenticationProvider) 
     47     
     48    def authorize(self, req): 
     49        for authenticator in self.auth_providers: 
     50            if authenticator.authenticate(req): 
     51                break 
     52     
     53class LoginModule(Component): 
    3254    """Implements user authentication based on HTTP authentication provided by 
    3355    the web-server, combined with cookies for communicating the login 
    3456    information across the whole site. 
     
    4062    to identify the user in subsequent requests to non-protected resources. 
    4163    """ 
    4264 
    43     def __init__(self, db, req, check_ip=True, ignore_case=False): 
    44         self.db = db 
    45         self.authname = 'anonymous' 
    46         self.ignore_case = ignore_case 
     65    implements(INavigationContributor, IAuthenticationProvider, IRequestHandler) 
    4766 
    48         if req.incookie.has_key('trac_auth'): 
    49             cookie = req.incookie['trac_auth'].value 
    50             cursor = db.cursor() 
    51             if check_ip: 
    52                 cursor.execute("SELECT name FROM auth_cookie " 
    53                                "WHERE cookie=%s AND ipnr=%s", 
    54                                (cookie, req.remote_addr)) 
    55             else: 
    56                 cursor.execute("SELECT name FROM auth_cookie WHERE cookie=%s", 
    57                                (cookie,)) 
    58             row = cursor.fetchone() 
    59             if row: 
    60                 self.authname = row[0] 
    61             else: 
    62                 # Tell the user to drop any auth_cookie for which no 
    63                 # corresponding entry in our cookie table exists. 
    64                 self.expire_auth_cookie(req) 
     67    # INavigationContributor methods 
     68    def get_active_navigation_item(self, req): 
     69        return 'login' 
     70         
     71    def get_navigation_items(self, req): 
     72        if req.authname and req.authname != 'anonymous': 
     73            yield 'metanav', 'login', 'logged in as %s' \ 
     74                  % util.escape(req.authname) 
     75            yield 'metanav', 'logout', '<a href="%s">Logout</a>' \ 
     76                  % util.escape(self.env.href.logout()) 
     77        else: 
     78            yield 'metanav', 'login', '<a href="%s">Login</a>' \ 
     79                  % util.escape(self.env.href.login()) 
     80     
     81    # IRequestProvider methods 
     82    def match_request(self, req): 
     83        match = re.match(r'^/(login|logout)', req.path_info) 
     84        if match: 
     85            if match.group(1): 
     86                req.args['action'] = match.group(1) 
     87            return 1 
    6588 
    66     def login(self, req): 
     89    def process_request(self, req):  
     90        db = self.env.get_db_cnx() 
     91         
     92        action = req.args.get('action') or '' 
     93        if action == 'login': 
     94            self._login(req, db) 
     95        else: 
     96            self._logout(req, db) 
     97 
     98    # IAuthenticationProvider methods 
     99    def authenticate(self, req): 
     100        return standard_authenticator(self.env, req) 
     101 
     102    # Internal methods 
     103    def _login(self, req, db): 
    67104        """Log the remote user in. 
    68105         
    69106        This function expects to be called when the remote user name is 
     
    80117        assert req.remote_user, 'Authentication information not available.' 
    81118         
    82119        remote_user = req.remote_user 
    83         if self.ignore_case: 
     120        ignore_case = self.env.config.get('trac', 'ignore_auth_case') 
     121        ignore_case = ignore_case.strip().lower() in util.TRUE 
     122        if ignore_case: 
    84123            remote_user = remote_user.lower() 
    85124 
    86         if self.authname == remote_user: 
    87             # Already logged in with the same user name 
    88             return 
    89         assert self.authname == 'anonymous', \ 
    90                'Already logged in as %s.' % self.authname 
     125        if req.authname != remote_user: 
     126            assert req.authname == 'anonymous', \ 
     127               'Already logged in as %s.' % req.authname 
    91128 
    92         cookie = util.hex_entropy() 
    93         cursor = self.db.cursor() 
    94         cursor.execute("INSERT INTO auth_cookie (cookie,name,ipnr,time) " 
    95                        "VALUES (%s, %s, %s, %s)", 
    96                        (cookie, remote_user, req.remote_addr, int(time.time()))) 
    97         self.db.commit() 
    98         self.authname = remote_user 
    99         req.outcookie['trac_auth'] = cookie 
    100         req.outcookie['trac_auth']['path'] = util.quote_cookie_value(req.cgi_location) 
     129            req.authname = remote_user 
     130         
     131            standard_login(req, db) 
    101132 
    102     def logout(self, req): 
     133        referer = req.get_header('Referer') 
     134        if referer and not referer.startswith(req.base_url): 
     135            # only redirect to referer if the latter is from the 
     136            # same instance 
     137            referer = None 
     138        req.redirect(referer or self.env.href.wiki()) 
     139 
     140    def _logout(self, req, db): 
    103141        """Log the user out. 
    104142         
    105143        Simply deletes the corresponding record from the auth_cookie table. 
    106144        """ 
    107         if self.authname == 'anonymous': 
    108             # Not logged in 
    109             return 
     145        if req.authname and req.authname != 'anonymous': 
     146            standard_logoff(req, db)     
     147            req.authname = 'anonymous' 
     148             
     149        referer = req.get_header('Referer') 
     150        if referer and not referer.startswith(req.base_url): 
     151            # only redirect to referer if the latter is from the same 
     152            # instance 
     153            referer = None 
     154        req.redirect(referer or self.env.href.wiki()) 
    110155 
    111         cursor = self.db.cursor() 
    112         # While deleting this cookie we also take the opportunity to delete 
    113         # cookies older than 10 days 
    114         cursor.execute("DELETE FROM auth_cookie WHERE name=%s OR time < %s", 
    115                        (self.authname, int(time.time()) - 86400 * 10)) 
    116         self.db.commit() 
    117         self.expire_auth_cookie(req) 
     156def standard_authenticator(env, req): 
     157    """ 
     158    Does standard authentication check from cookie. 
     159     
     160    Optionally checks that IP of requester matches one in db. 
     161    """ 
     162     
     163    db = env.get_db_cnx() 
     164    authname = 'anonymous' 
     165     
     166    check_ip = env.config.get('trac', 'check_auth_ip') 
     167    check_ip = check_ip.strip().lower() in util.TRUE 
     168     
     169    if req.incookie.has_key('trac_auth'): 
     170        cookie = req.incookie['trac_auth'].value 
     171        cursor = db.cursor() 
     172        if check_ip: 
     173            cursor.execute("SELECT name FROM auth_cookie " 
     174                           "WHERE cookie=%s AND ipnr=%s", 
     175                           (cookie, req.remote_addr)) 
     176        else: 
     177            cursor.execute("SELECT name FROM auth_cookie WHERE cookie=%s", 
     178                           (cookie,)) 
     179        row = cursor.fetchone() 
     180        if row: 
     181            authname = row[0] 
     182        else: 
     183            # Tell the user to drop any auth_cookie for which no 
     184            # corresponding entry in our cookie table exists. 
     185             
     186            expire_auth_cookie(req) 
    118187 
    119     def expire_auth_cookie(self, req): 
    120         """Instruct the user agent to drop the auth cookie by setting the 
    121         "expires" property to a date in the past. 
    122         """ 
    123         req.outcookie['trac_auth'] = '' 
    124         req.outcookie['trac_auth']['path'] = util.quote_cookie_value(req.cgi_location) 
    125         req.outcookie['trac_auth']['expires'] = -10000 
     188    req.authname = authname 
     189     
     190    return req.authname != 'anonymous' 
    126191 
     192def standard_login(req, db): 
     193    """ 
     194    Does standard login by saving authname, sessionid and time to session table.     
     195    """ 
    127196 
    128 class LoginModule(Component): 
     197    cookie = util.hex_entropy() 
     198    cursor = db.cursor() 
     199    cursor.execute("INSERT INTO auth_cookie (cookie,name,ipnr,time) " 
     200                   "VALUES (%s, %s, %s, %s)", 
     201                   (cookie, req.authname, req.remote_addr, int(time.time()))) 
     202    db.commit() 
     203     
     204    req.outcookie['trac_auth'] = cookie 
     205    req.outcookie['trac_auth']['path'] = util.quote_cookie_value(req.cgi_location) 
    129206 
    130     implements(INavigationContributor) 
     207def standard_logoff(req, db): 
     208    """ 
     209    Does standard logoff by deleting cookie data from session table. 
     210     
     211    Also deletes cookies older than 10 days. 
     212    """ 
     213     
     214    cursor = db.cursor() 
     215    # While deleting this cookie we also take the opportunity to delete 
     216    # cookies older than 10 days 
     217    cursor.execute("DELETE FROM auth_cookie WHERE name=%s OR time < %s", 
     218                   (req.authname, int(time.time()) - 86400 * 10)) 
     219    db.commit() 
     220    expire_auth_cookie(req) 
     221         
     222def expire_auth_cookie(req): 
     223    """Instruct the user agent to drop the auth cookie by setting the 
     224    "expires" property to a date in the past. 
     225    """ 
     226    req.outcookie['trac_auth'] = '' 
     227    req.outcookie['trac_auth']['path'] = util.quote_cookie_value(req.cgi_location) 
     228    req.outcookie['trac_auth']['expires'] = -10000 
    131229 
    132     # INavigationContributor methods 
    133  
    134     def get_navigation_items(self, req): 
    135         if req.authname and req.authname != 'anonymous': 
    136             yield 'metanav', 'login', 'logged in as %s' \ 
    137                   % util.escape(req.authname) 
    138             yield 'metanav', 'logout', '<a href="%s">Logout</a>' \ 
    139                   % util.escape(self.env.href.logout()) 
    140         else: 
    141             yield 'metanav', 'login', '<a href="%s">Login</a>' \ 
    142                   % util.escape(self.env.href.login()) 
  • trac/web/main.py

     
    419419    try: 
    420420        try: 
    421421            from trac.web.auth import Authenticator 
    422             check_ip = env.config.get('trac', 'check_auth_ip') 
    423             check_ip = check_ip.strip().lower() in TRUE 
    424             ignore_case = env.config.get('trac', 'ignore_auth_case') 
    425             ignore_case = ignore_case.strip().lower() in TRUE 
    426             authenticator = Authenticator(db, req, check_ip, ignore_case) 
    427             if path_info == '/logout': 
    428                 authenticator.logout(req) 
    429                 referer = req.get_header('Referer') 
    430                 if referer and not referer.startswith(req.base_url): 
    431                     # only redirect to referer if the latter is from the same 
    432                     # instance 
    433                     referer = None 
    434                 req.redirect(referer or env.href.wiki()) 
    435             elif req.remote_user: 
    436                 authenticator.login(req) 
    437                 if path_info == '/login': 
    438                     referer = req.get_header('Referer') 
    439                     if referer and not referer.startswith(req.base_url): 
    440                         # only redirect to referer if the latter is from the 
    441                         # same instance 
    442                         referer = None 
    443                     req.redirect(referer or env.href.wiki()) 
    444             req.authname = authenticator.authname 
     422             
     423            authenticator = Authenticator(env) 
     424            authenticator.authorize(req) 
     425                 
    445426            req.perm = PermissionCache(env, req.authname) 
    446427 
    447428            newsession = req.args.has_key('newsession')