Ticket #1366: authz_groups.diff
| File authz_groups.diff, 12.9 KB (added by Matthew Good <trac matt-good net>, 7 years ago) |
|---|
-
trac/util.py
=== trac/util.py ==================================================================
95 95 break 96 96 return text 97 97 98 def strip(text, skip): 99 """Python2.1 doesn't support custom skip characters""" 100 return lstrip(rstrip(text, skip), skip) 101 98 102 def to_utf8(text, charset='iso-8859-15'): 99 103 """Convert a string to utf-8, assume the encoding is either utf-8 or latin1""" 100 104 try: -
trac/versioncontrol/cache.py
=== trac/versioncontrol/cache.py ==================================================================
22 22 from __future__ import generators 23 23 24 24 from trac.util import TracError 25 from trac.versioncontrol import Changeset, Node, Repository 25 from trac.versioncontrol import Changeset, Node, Repository, Authorizer 26 26 27 27 28 28 _kindmap = {'D': Node.DIRECTORY, 'F': Node.FILE} … … 52 52 self.log.debug("Checking whether sync with repository is needed") 53 53 youngest_stored = self.repos.get_youngest_rev_in_cache(self.db) 54 54 if youngest_stored != str(self.repos.youngest_rev): 55 authz = self.repos.authz 56 self.repos.authz = Authorizer() # remove permission checking 57 55 58 kindmap = dict(zip(_kindmap.values(), _kindmap.keys())) 56 59 actionmap = dict(zip(_actionmap.values(), _actionmap.keys())) 57 60 self.log.info("Syncing with repository (%s to %s)" … … 80 83 base_path, base_rev)) 81 84 current_rev = self.repos.next_rev(current_rev) 82 85 self.db.commit() 86 self.repos.authz = authz # restore permission checking 83 87 84 88 def get_node(self, path, rev=None): 85 89 return self.repos.get_node(path, rev) -
trac/versioncontrol/svn_authz.py
=== trac/versioncontrol/svn_authz.py ==================================================================
19 19 # 20 20 # Author: Francois Harvey <fharvey@securiweb.net> 21 21 22 from __future__ import generators 22 23 from trac.versioncontrol import Authorizer 23 24 25 def SubversionAuthorizer(env, authname): 26 authz_file = env.get_config('trac','authz_file') 27 if not authz_file: 28 return Authorizer() 24 29 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) 26 33 34 def 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 51 class RealSubversionAuthorizer(Authorizer): 52 27 53 auth_name = '' 28 54 module_name = '' 29 55 conf_authz = None 30 authz_file = ''31 56 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 [] 37 74 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)] 45 77 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 53 82 return 0 54 83 55 84 def has_permission(self, path): 56 acc = '' 85 if path is None: 86 return 1 57 87 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 79 96 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 80 123 def has_permission_for_changeset(self, rev): 81 124 cursor = self.db.cursor() 82 125 cursor.execute("SELECT path FROM node_change WHERE rev=%s", (rev,)) -
trac/versioncontrol/tests/__init__.py
=== trac/versioncontrol/tests/__init__.py ==================================================================
1 1 import unittest 2 2 3 from trac.versioncontrol.tests import cache, diff 3 from trac.versioncontrol.tests import cache, diff, svn_authz 4 4 5 5 def suite(): 6 6 7 7 suite = unittest.TestSuite() 8 8 suite.addTest(cache.suite()) 9 9 suite.addTest(diff.suite()) 10 suite.addTest(svn_authz.suite()) 10 11 return suite 11 12 12 13 if __name__ == '__main__': -
trac/versioncontrol/tests/svn_authz.py
=== trac/versioncontrol/tests/svn_authz.py ==================================================================
1 from trac.versioncontrol import svn_authz 2 3 import unittest 4 5 def 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 14 if __name__ == '__main__': 15 runner = unittest.TextTestRunner() 16 runner.run(suite()) 17 -
trac/versioncontrol/tests/svn_authz.txt
=== trac/versioncontrol/tests/svn_authz.txt ==================================================================
1 Subversion Authz File Permissions 2 ================================= 3 4 Setup code 5 ---------- 6 We'll use the ``make_auth`` method to create Authorizer objects 7 for testing the use of authz files. ``make_auth`` takes a module name 8 and 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 16 Simple operation 17 ---------------- 18 Returns 1 if no path is given: 19 >>> int(make_auth('', '').has_permission(None)) 20 1 21 22 By default read permission is not enabled: 23 >>> int(make_auth('', '').has_permission('/')) 24 0 25 26 Read and Write Permissions 27 ---------------------- 28 Trac 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 40 Permissions of 'r' or 'rw' will allow access: 41 >>> int(a.has_permission('/readonly')) 42 1 43 >>> int(a.has_permission('/readwrite')) 44 1 45 46 If only 'w' permission is given, Trac does not allow access: 47 >>> int(a.has_permission('/writeonly')) 48 0 49 50 And an empty permission does not give access: 51 >>> int(a.has_permission('/empty')) 52 0 53 54 Trailing Slashes 55 ---------------- 56 Checks all combinations of trailing slashes in the configuration 57 or 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 74 Module Usage 75 ------------ 76 If 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 88 If a module is specified, but the configuration contains a non-module 89 path, the non-module path can still apply: 90 >>> int(make_auth('module', ''' 91 ... [/a] 92 ... user = r 93 ... ''').has_permission('/a')) 94 1 95 96 However, 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 106 Groups and Wildcards 107 -------------------- 108 Authz provides a * wildcard for matching any user: 109 >>> int(make_auth('', ''' 110 ... [/a] 111 ... * = r 112 ... ''').has_permission('/a')) 113 1 114 115 Groups 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 124 If more than one group matches at the specific path, access is granted 125 if 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 143 Precedence 144 ---------- 145 Precedence 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 157 User specific permission overrides the group permission: 158 >>> int(a.has_permission('/a')) 159 0 160 161 And group permission overrides the * permission: 162 >>> int(a.has_permission('/b')) 163 0 164 165 The 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 179 Changeset Permissions 180 --------------------- 181 A test should go here for the changeset permissions.
