Edgewall Software

Ticket #1366: authz_groups.diff

File authz_groups.diff, 12.9 KB (added by Matthew Good <trac matt-good net>, 7 years ago)

Authz groups support

  • trac/util.py

    === trac/util.py
    ==================================================================
     
    9595            break 
    9696    return text 
    9797 
     98def strip(text, skip): 
     99    """Python2.1 doesn't support custom skip characters""" 
     100    return lstrip(rstrip(text, skip), skip) 
     101 
    98102def to_utf8(text, charset='iso-8859-15'): 
    99103    """Convert a string to utf-8, assume the encoding is either utf-8 or latin1""" 
    100104    try: 
  • trac/versioncontrol/cache.py

    === trac/versioncontrol/cache.py
    ==================================================================
     
    2222from __future__ import generators 
    2323 
    2424from trac.util import TracError 
    25 from trac.versioncontrol import Changeset, Node, Repository 
     25from trac.versioncontrol import Changeset, Node, Repository, Authorizer 
    2626 
    2727 
    2828_kindmap = {'D': Node.DIRECTORY, 'F': Node.FILE} 
     
    5252        self.log.debug("Checking whether sync with repository is needed") 
    5353        youngest_stored = self.repos.get_youngest_rev_in_cache(self.db) 
    5454        if youngest_stored != str(self.repos.youngest_rev): 
     55            authz = self.repos.authz 
     56            self.repos.authz = Authorizer() # remove permission checking 
     57 
    5558            kindmap = dict(zip(_kindmap.values(), _kindmap.keys())) 
    5659            actionmap = dict(zip(_actionmap.values(), _actionmap.keys())) 
    5760            self.log.info("Syncing with repository (%s to %s)" 
     
    8083                                   base_path, base_rev)) 
    8184                current_rev = self.repos.next_rev(current_rev) 
    8285            self.db.commit() 
     86            self.repos.authz = authz # restore permission checking 
    8387 
    8488    def get_node(self, path, rev=None): 
    8589        return self.repos.get_node(path, rev) 
  • trac/versioncontrol/svn_authz.py

    === trac/versioncontrol/svn_authz.py
    ==================================================================
     
    1919# 
    2020# Author: Francois Harvey <fharvey@securiweb.net> 
    2121 
     22from __future__ import generators 
    2223from trac.versioncontrol import Authorizer 
    2324 
     25def SubversionAuthorizer(env, authname): 
     26    authz_file = env.get_config('trac','authz_file')     
     27    if not authz_file: 
     28        return Authorizer() 
    2429 
    25 class SubversionAuthorizer(Authorizer): 
     30    module_name = env.get_config('trac','authz_module_name','') 
     31    db = env.get_db_cnx() 
     32    return RealSubversionAuthorizer(db, authname, module_name, authz_file) 
    2633 
     34def parent_iter(path): 
     35    from trac.util import strip 
     36    path = strip(path, '/') 
     37    if path: 
     38        path = '/' + path + '/' 
     39    else: 
     40        path = '/' 
     41 
     42    while 1: 
     43        yield path 
     44        if path == '/': 
     45            raise StopIteration() 
     46        path = path[:-1] 
     47        yield path 
     48        idx = path.rfind('/') 
     49        path = path[:idx + 1] 
     50 
     51class RealSubversionAuthorizer(Authorizer): 
     52 
    2753    auth_name = '' 
    2854    module_name = '' 
    2955    conf_authz = None 
    30     authz_file = '' 
    3156 
    32     def __init__(self, env, authname): 
    33         self.auth_name = authname 
    34          
    35         if env.get_config('trac','authz_module_name','') == '': 
    36             self.module_name = '' 
     57    def __init__(self, db, auth_name, module_name, cfg_file, cfg_fp=None): 
     58        self.db = db 
     59        self.auth_name = auth_name 
     60        self.module_name = module_name 
     61                                 
     62        from ConfigParser import ConfigParser 
     63        self.conf_authz = ConfigParser() 
     64        if cfg_fp: 
     65            self.conf_authz.readfp(cfg_fp, cfg_file) 
     66        elif cfg_file: 
     67            self.conf_authz.read(cfg_file) 
     68 
     69        self.groups = self.get_groups() 
     70 
     71    def get_groups(self): 
     72        if not self.conf_authz.has_section('groups'): 
     73            return [] 
    3774        else: 
    38             self.module_name = env.get_config('trac','authz_module_name') + ':' 
    39                                   
    40         self.autz_file = env.get_config('trac','authz_file')     
    41         if env.get_config('trac','authz_file'): 
    42             from ConfigParser import ConfigParser 
    43             self.conf_authz = ConfigParser() 
    44             self.conf_authz.read(self.autz_file) 
     75            return [group for group in self.conf_authz.options('groups') \ 
     76                          if self.in_group(group)] 
    4577 
    46         self.db = env.get_db_cnx() 
    47  
    48     def group_contains_user(self, group_name, user_name): 
    49         if self.conf_authz.has_section('groups'): 
    50             if self.conf_authz.has_option('groups', group_name): 
    51                 users_list = self.conf_authz.get('groups', group_name).split(',') 
    52                 return users_list.has_key(user_name) 
     78    def in_group(self, group): 
     79        for user in self.conf_authz.get('groups', group).split(','): 
     80            if self.auth_name == user.strip(): 
     81                return 1 
    5382        return 0 
    5483 
    5584    def has_permission(self, path): 
    56         acc = '' 
     85        if path is None: 
     86            return 1 
    5787 
    58         if path != None and self.conf_authz != None: 
    59             if self.conf_authz.has_section(self.module_name + '/') and \ 
    60                    self.conf_authz.has_option(self.module_name  + '/', 
    61                                               self.auth_name): 
    62                 acc = self.conf_authz.get(self.module_name + '/',self.auth_name) 
    63             elif self.conf_authz.has_section(self.module_name + '/') and \ 
    64                  self.conf_authz.has_option(self.module_name  + '/', '*'): 
    65                      acc = self.conf_authz.get(self.module_name + '/','*') 
    66             path_comb = '' 
    67             for path_elem in path.split('/'): 
    68                 if path_elem != '': 
    69                     path_comb = self.module_name + path_comb + '/' + path_elem 
    70                     if self.conf_authz.has_section(path_comb) and \ 
    71                            self.conf_authz.has_option(path_comb, self.auth_name): 
    72                         acc =  self.conf_authz.get(path_comb, self.auth_name) 
    73                     elif self.conf_authz.has_section(path_comb) and \ 
    74                             self.conf_authz.has_option(path_comb, '*'): 
    75                         acc =  self.conf_authz.get(path_comb, '*') 
    76         else: 
    77             acc = 'r' 
    78         return acc 
     88        for p in parent_iter(path): 
     89            if self.module_name: 
     90                for perm in self.get_section(self.module_name + ':' + p): 
     91                    if perm is not None: 
     92                        return perm 
     93            for perm in self.get_section(p): 
     94                if perm is not None: 
     95                    return perm 
    7996 
     97        return 0 
     98 
     99    def get_section(self, section): 
     100        if not self.conf_authz.has_section(section): 
     101            return 
     102 
     103        yield self.get_permission(section, self.auth_name) 
     104 
     105        group_perm = None 
     106        for g in self.groups: 
     107            p = self.get_permission(section, '@' + g) 
     108            if p is not None: 
     109                group_perm = p 
     110 
     111            if group_perm: 
     112                yield 1 
     113 
     114        yield group_perm 
     115 
     116        yield self.get_permission(section, '*') 
     117 
     118    def get_permission(self, section, subject): 
     119        if self.conf_authz.has_option(section, subject): 
     120            return 'r' in self.conf_authz.get(section, subject) 
     121        return None 
     122 
    80123    def has_permission_for_changeset(self, rev): 
    81124        cursor = self.db.cursor() 
    82125        cursor.execute("SELECT path FROM node_change WHERE rev=%s", (rev,)) 
  • trac/versioncontrol/tests/__init__.py

    === trac/versioncontrol/tests/__init__.py
    ==================================================================
     
    11import unittest 
    22 
    3 from trac.versioncontrol.tests import cache, diff 
     3from trac.versioncontrol.tests import cache, diff, svn_authz 
    44 
    55def suite(): 
    66 
    77    suite = unittest.TestSuite() 
    88    suite.addTest(cache.suite()) 
    99    suite.addTest(diff.suite()) 
     10    suite.addTest(svn_authz.suite()) 
    1011    return suite 
    1112 
    1213if __name__ == '__main__': 
  • trac/versioncontrol/tests/svn_authz.py

    === trac/versioncontrol/tests/svn_authz.py
    ==================================================================
     
     1from trac.versioncontrol import svn_authz 
     2 
     3import unittest 
     4 
     5def suite(): 
     6    try: 
     7        from doctest import DocFileSuite 
     8        return DocFileSuite('svn_authz.txt') 
     9    except ImportError: 
     10        import sys 
     11        print>>sys.stderr, "WARNING: DocTestSuite required to run these tests" 
     12    return unittest.TestSuite() 
     13 
     14if __name__ == '__main__': 
     15    runner = unittest.TextTestRunner() 
     16    runner.run(suite()) 
     17 
  • trac/versioncontrol/tests/svn_authz.txt

    === trac/versioncontrol/tests/svn_authz.txt
    ==================================================================
     
     1Subversion Authz File Permissions 
     2================================= 
     3 
     4Setup code 
     5---------- 
     6We'll use the ``make_auth`` method to create Authorizer objects 
     7for testing the use of authz files.  ``make_auth`` takes a module name 
     8and a string for the authz configuration contents. 
     9 
     10>>> from trac.versioncontrol.svn_authz import RealSubversionAuthorizer 
     11>>> from StringIO import StringIO 
     12>>> make_auth = lambda mod, cfg: RealSubversionAuthorizer(None, 
     13...                   'user', mod, None, StringIO(cfg)) 
     14 
     15 
     16Simple operation 
     17---------------- 
     18Returns 1 if no path is given: 
     19    >>> int(make_auth('', '').has_permission(None)) 
     20    1 
     21 
     22By default read permission is not enabled: 
     23    >>> int(make_auth('', '').has_permission('/')) 
     24    0 
     25 
     26Read and Write Permissions 
     27---------------------- 
     28Trac is only concerned about read permissions. 
     29    >>> a = make_auth('', ''' 
     30    ... [/readonly] 
     31    ... user = r 
     32    ... [/writeonly] 
     33    ... user = w 
     34    ... [/readwrite] 
     35    ... user = rw 
     36    ... [/empty] 
     37    ... user =  
     38    ... ''') 
     39 
     40Permissions of 'r' or 'rw' will allow access: 
     41    >>> int(a.has_permission('/readonly')) 
     42    1 
     43    >>> int(a.has_permission('/readwrite')) 
     44    1 
     45 
     46If only 'w' permission is given, Trac does not allow access: 
     47    >>> int(a.has_permission('/writeonly')) 
     48    0 
     49 
     50And an empty permission does not give access: 
     51    >>> int(a.has_permission('/empty')) 
     52    0 
     53 
     54Trailing Slashes 
     55---------------- 
     56Checks all combinations of trailing slashes in the configuration 
     57or in the path parameter: 
     58    >>> a = make_auth('', ''' 
     59    ... [/a] 
     60    ... user = r 
     61    ... [/b/] 
     62    ... user = r 
     63    ... ''') 
     64    >>> int(a.has_permission('/a')) 
     65    1 
     66    >>> int(a.has_permission('/a/')) 
     67    1 
     68    >>> int(a.has_permission('/b')) 
     69    1 
     70    >>> int(a.has_permission('/b/')) 
     71    1 
     72 
     73 
     74Module Usage 
     75------------ 
     76If a module name is specified, the rules used are specific to the module. 
     77    >>> a = make_auth('module', ''' 
     78    ... [module:/a] 
     79    ... user = r 
     80    ... [other:/b] 
     81    ... user = r 
     82    ... ''') 
     83    >>> int(a.has_permission('/a')) 
     84    1 
     85    >>> int(a.has_permission('/b')) 
     86    0 
     87 
     88If a module is specified, but the configuration contains a non-module 
     89path, the non-module path can still apply: 
     90    >>> int(make_auth('module', ''' 
     91    ... [/a] 
     92    ... user = r 
     93    ... ''').has_permission('/a')) 
     94    1 
     95 
     96However, the module-specific rule will take precedence if both exist: 
     97    >>> int(make_auth('module', ''' 
     98    ... [module:/a] 
     99    ... user =  
     100    ... [/a] 
     101    ... user = r 
     102    ... ''').has_permission('/a')) 
     103    0 
     104 
     105 
     106Groups and Wildcards 
     107-------------------- 
     108Authz provides a * wildcard for matching any user: 
     109    >>> int(make_auth('', ''' 
     110    ... [/a] 
     111    ... * = r 
     112    ... ''').has_permission('/a')) 
     113    1 
     114 
     115Groups are specified in a separate section and used with an @ prefix: 
     116    >>> int(make_auth('', ''' 
     117    ... [groups] 
     118    ... grp = user 
     119    ... [/a] 
     120    ... @grp = r 
     121    ... ''').has_permission('/a')) 
     122    1 
     123 
     124If more than one group matches at the specific path, access is granted 
     125if any of the group rules allow access. 
     126    >>> a = make_auth('', ''' 
     127    ... [groups] 
     128    ... grp1 = user 
     129    ... grp2 = user 
     130    ... [/a] 
     131    ... @grp1 = r 
     132    ... @grp2 =  
     133    ... [/b] 
     134    ... @grp1 =  
     135    ... @grp2 = r 
     136    ... ''') 
     137    >>> int(a.has_permission('/a')) 
     138    1 
     139    >>> int(a.has_permission('/b')) 
     140    1 
     141 
     142 
     143Precedence 
     144---------- 
     145Precedence is user, group, then *: 
     146    >>> a = make_auth('', ''' 
     147    ... [groups] 
     148    ... grp = user 
     149    ... [/a] 
     150    ... @grp = r 
     151    ... user =  
     152    ... [/b] 
     153    ... * = r 
     154    ... @grp =  
     155    ... ''') 
     156 
     157User specific permission overrides the group permission: 
     158    >>> int(a.has_permission('/a')) 
     159    0 
     160 
     161And group permission overrides the * permission: 
     162    >>> int(a.has_permission('/b')) 
     163    0 
     164 
     165The most specific matching path takes precedence: 
     166    >>> a = make_auth('', ''' 
     167    ... [/] 
     168    ... * = r 
     169    ... [/b] 
     170    ... user =  
     171    ... ''') 
     172    >>> int(a.has_permission('/')) 
     173    1 
     174    >>> int(a.has_permission('/a')) 
     175    1 
     176    >>> int(a.has_permission('/b')) 
     177    0 
     178 
     179Changeset Permissions 
     180--------------------- 
     181A test should go here for the changeset permissions.