Ticket #9566: 9566-scoped-repos-r10006.patch
| File 9566-scoped-repos-r10006.patch, 13.8 KB (added by rblank, 22 months ago) |
|---|
-
trac/versioncontrol/svn_authz.py
diff --git a/trac/versioncontrol/svn_authz.py b/trac/versioncontrol/svn_authz.py
a b 40 40 path = path[:idx + 1] 41 41 42 42 43 def join(*args): 44 args = (arg.strip('/') for arg in args) 45 return '/'.join(arg for arg in args if arg) 46 47 43 48 class ParseError(Exception): 44 49 """Exception thrown for parse errors in authz files""" 45 50 … … 125 130 126 131 authz_module_name = Option('trac', 'authz_module_name', '', 127 132 """The module prefix used in the `authz_file` for the default 128 repository. If left empty, the global section s will beused.133 repository. If left empty, the global section is used. 129 134 """) 130 135 131 136 _mtime = 0 132 137 _authz = {} 133 138 _users = set() 134 139 140 _handled_perms = frozenset([('source', 'BROWSER_VIEW'), 141 ('source', 'FILE_VIEW'), 142 ('source', 'LOG_VIEW'), 143 ('changeset', 'CHANGESET_VIEW')]) 144 135 145 # IPermissionPolicy methods 136 146 137 147 def check_permission(self, action, username, resource, perm): 138 if username == 'anonymous': 139 usernames = ('$anonymous', '*') 140 else: 141 usernames = (username, '$authenticated', '*') 142 if action in ('BROWSER_VIEW', 'FILE_VIEW', 'LOG_VIEW'): 148 if (resource.realm, action) in self._handled_perms: 143 149 authz, users = self._get_authz_info() 144 150 if authz is None: 145 151 return False 152 153 if username == 'anonymous': 154 usernames = ('$anonymous', '*') 155 else: 156 usernames = (username, '$authenticated', '*') 146 157 if resource is None: 147 158 return bool(users & set(usernames)) 148 if resource.realm == 'source': 149 modules = [resource.parent.id or self.authz_module_name] 150 if modules[0]: 151 modules.append('') 152 path = '/' + resource.id.strip('/') 159 160 rm = RepositoryManager(self.env) 161 repos = rm.get_repository(resource.parent.id) 162 scope = getattr(repos, 'scope', '') 163 modules = [resource.parent.id or self.authz_module_name] 164 if modules[0]: 165 modules.append('') 166 167 def check_path(path): 168 path = '/' + join(scope, path) 153 169 if path != '/': 154 170 path += '/' 155 171 … … 170 186 if spath.startswith(path) 171 187 for user in usernames): 172 188 return True 189 190 if resource.realm == 'source': 191 return check_path(resource.id) 173 192 174 elif action == 'CHANGESET_VIEW': 175 authz, users = self._get_authz_info() 176 if authz is None: 177 return False 178 if resource is None: 179 return bool(users & set(usernames)) 180 if resource.realm == 'changeset': 181 rm = RepositoryManager(self.env) 182 repos = rm.get_repository(resource.parent.id) 193 elif resource.realm == 'changeset': 183 194 changes = list(repos.get_changeset(resource.id).get_changes()) 184 if not changes: 195 if not changes or any(check_path(change[0]) 196 for change in changes): 185 197 return True 186 source = Resource('source', version=resource.id,187 parent=resource.parent)188 return any('FILE_VIEW' in perm(source(id=change[0]))189 for change in changes)190 198 191 199 def _get_authz_info(self): 192 200 try: -
trac/versioncontrol/tests/svn_authz.py
diff --git a/trac/versioncontrol/tests/svn_authz.py b/trac/versioncontrol/tests/svn_authz.py
a b 16 16 import unittest 17 17 18 18 from trac.resource import Resource 19 from trac.test import EnvironmentStub 19 from trac.test import EnvironmentStub, Mock 20 20 from trac.util import create_file 21 from trac.versioncontrol.api import RepositoryManager 21 22 from trac.versioncontrol.svn_authz import AuthzSourcePolicy, ParseError, \ 22 23 parse 23 24 … … 185 186 &jekyll = r 186 187 [/aliases_b] 187 188 @alias2 = r 189 190 # Scoped repository 191 [scoped:/scope/dir1] 192 joe = r 193 [scoped:/scope/dir2] 194 jane = r 188 195 """) 189 196 self.env = EnvironmentStub(enable=[AuthzSourcePolicy]) 190 197 self.env.config.set('trac', 'authz_file', self.authz) 191 198 self.policy = AuthzSourcePolicy(self.env) 199 200 # Monkey-subclass RepositoryManager to serve mock repositories 201 rm = RepositoryManager(self.env) 202 203 class TestRepositoryManager(rm.__class__): 204 def get_repository(self, reponame): 205 if reponame == 'scoped': 206 def get_changeset(rev): 207 if rev == 123: 208 def get_changes(): 209 yield ('/dir1/file',) 210 elif rev == 456: 211 def get_changes(): 212 yield ('/dir2/file',) 213 else: 214 def get_changes(): 215 return iter([]) 216 return Mock(get_changes=get_changes) 217 return Mock(scope='/scope', 218 get_changeset=get_changeset) 219 return Mock() 220 221 rm.__class__ = TestRepositoryManager 192 222 193 223 def tearDown(self): 194 224 self.env.reset_db() 195 225 os.remove(self.authz) 196 226 197 def assertP ermission(self, result, user, reponame, path):227 def assertPathPerm(self, result, user, reponame, path): 198 228 """Assert that `user` is granted access `result` to `path` within 199 229 the repository `reponame`. 200 230 """ … … 204 234 check = self.policy.check_permission(perm, user, resource, None) 205 235 self.assertEqual(result, check) 206 236 237 def assertRevPerm(self, result, user, reponame, rev): 238 """Assert that `user` is granted access `result` to `rev` within 239 the repository `reponame`. 240 """ 241 resource = Resource('changeset', rev, 242 parent=Resource('repository', reponame)) 243 check = self.policy.check_permission('CHANGESET_VIEW', user, resource, 244 None) 245 self.assertEqual(result, check) 246 207 247 def test_default_permission(self): 208 248 # By default, permissions are undecided 209 self.assertP ermission(None, 'joe', '', '/not_defined')210 self.assertP ermission(None, 'jane', 'repo', '/not/defined/either')249 self.assertPathPerm(None, 'joe', '', '/not_defined') 250 self.assertPathPerm(None, 'jane', 'repo', '/not/defined/either') 211 251 212 252 def test_read_write(self): 213 253 # Allow 'r' and 'rw' entries, deny 'w' and empty entries 214 self.assertP ermission(True, 'user', '', '/readonly')215 self.assertP ermission(True, 'user', '', '/readwrite')216 self.assertP ermission(False, 'user', '', '/writeonly')217 self.assertP ermission(False, 'user', '', '/empty')254 self.assertPathPerm(True, 'user', '', '/readonly') 255 self.assertPathPerm(True, 'user', '', '/readwrite') 256 self.assertPathPerm(False, 'user', '', '/writeonly') 257 self.assertPathPerm(False, 'user', '', '/empty') 218 258 219 259 def test_trailing_slashes(self): 220 260 # Combinations of trailing slashes in the file and in the path 221 self.assertP ermission(True, 'user', '', '/trailing_a')222 self.assertP ermission(True, 'user', '', '/trailing_a/')223 self.assertP ermission(True, 'user', '', '/trailing_b')224 self.assertP ermission(True, 'user', '', '/trailing_b/')261 self.assertPathPerm(True, 'user', '', '/trailing_a') 262 self.assertPathPerm(True, 'user', '', '/trailing_a/') 263 self.assertPathPerm(True, 'user', '', '/trailing_b') 264 self.assertPathPerm(True, 'user', '', '/trailing_b/') 225 265 226 266 def test_sub_path(self): 227 267 # Permissions are inherited from containing directories 228 self.assertP ermission(True, 'user', '', '/sub/path')229 self.assertP ermission(True, 'user', '', '/sub/path/test')230 self.assertP ermission(True, 'user', '', '/sub/path/other/sub')268 self.assertPathPerm(True, 'user', '', '/sub/path') 269 self.assertPathPerm(True, 'user', '', '/sub/path/test') 270 self.assertPathPerm(True, 'user', '', '/sub/path/other/sub') 231 271 232 272 def test_module_usage(self): 233 273 # If a module name is specified, the rules are specific to the module 234 self.assertP ermission(True, 'user', 'module', '/module_a')235 self.assertP ermission(None, 'user', 'module', '/module_b')274 self.assertPathPerm(True, 'user', 'module', '/module_a') 275 self.assertPathPerm(None, 'user', 'module', '/module_b') 236 276 # If a module is specified, but the configuration contains a non-module 237 277 # path, the non-module path can still apply 238 self.assertP ermission(True, 'user', 'module', '/module_c')278 self.assertPathPerm(True, 'user', 'module', '/module_c') 239 279 # The module-specific rule takes precedence 240 self.assertP ermission(False, 'user', 'module', '/module_d')280 self.assertPathPerm(False, 'user', 'module', '/module_d') 241 281 242 282 def test_wildcard(self): 243 283 # The * wildcard matches all users, including anonymous 244 self.assertP ermission(True, 'anonymous', '', '/wildcard')245 self.assertP ermission(True, 'joe', '', '/wildcard')246 self.assertP ermission(True, 'jane', '', '/wildcard')284 self.assertPathPerm(True, 'anonymous', '', '/wildcard') 285 self.assertPathPerm(True, 'joe', '', '/wildcard') 286 self.assertPathPerm(True, 'jane', '', '/wildcard') 247 287 248 288 def test_special_tokens(self): 249 289 # The $anonymous token matches only anonymous users 250 self.assertP ermission(True, 'anonymous', '', '/special/anonymous')251 self.assertP ermission(None, 'user', '', '/special/anonymous')290 self.assertPathPerm(True, 'anonymous', '', '/special/anonymous') 291 self.assertPathPerm(None, 'user', '', '/special/anonymous') 252 292 # The $authenticated token matches all authenticated users 253 self.assertP ermission(None, 'anonymous', '', '/special/authenticated')254 self.assertP ermission(True, 'joe', '', '/special/authenticated')255 self.assertP ermission(True, 'jane', '', '/special/authenticated')293 self.assertPathPerm(None, 'anonymous', '', '/special/authenticated') 294 self.assertPathPerm(True, 'joe', '', '/special/authenticated') 295 self.assertPathPerm(True, 'jane', '', '/special/authenticated') 256 296 257 297 def test_groups(self): 258 298 # Groups are specified in a separate section and used with an @ prefix 259 self.assertP ermission(True, 'user', '', '/groups_a')299 self.assertPathPerm(True, 'user', '', '/groups_a') 260 300 # Groups can also be members of other groups 261 self.assertP ermission(True, 'user', '', '/groups_b')301 self.assertPathPerm(True, 'user', '', '/groups_b') 262 302 # Groups should not be defined cyclically, but they are still handled 263 303 # correctly to avoid infinite loops 264 self.assertP ermission(True, 'user', '', '/cyclic')304 self.assertPathPerm(True, 'user', '', '/cyclic') 265 305 266 306 def test_precedence(self): 267 307 # Module-specific sections take precedence over non-module sections 268 self.assertP ermission(False, 'user', 'module', '/precedence_a')308 self.assertPathPerm(False, 'user', 'module', '/precedence_a') 269 309 # The most specific section applies 270 self.assertP ermission(True, 'user', '', '/precedence_b/sub/test')271 self.assertP ermission(False, 'user', '', '/precedence_b/sub')272 self.assertP ermission(True, 'user', '', '/precedence_b')310 self.assertPathPerm(True, 'user', '', '/precedence_b/sub/test') 311 self.assertPathPerm(False, 'user', '', '/precedence_b/sub') 312 self.assertPathPerm(True, 'user', '', '/precedence_b') 273 313 # Within a section, the first matching rule applies 274 self.assertP ermission(False, 'user', '', '/precedence_c')275 self.assertP ermission(True, 'user', '', '/precedence_d')314 self.assertPathPerm(False, 'user', '', '/precedence_c') 315 self.assertPathPerm(True, 'user', '', '/precedence_d') 276 316 277 317 def test_aliases(self): 278 318 # Aliases are specified in a separate section and used with an & prefix 279 self.assertP ermission(True, 'Mr Hyde', '', '/aliases_a')319 self.assertPathPerm(True, 'Mr Hyde', '', '/aliases_a') 280 320 # Aliases can also be used in groups 281 self.assertPermission(True, 'Mr Hyde', '', '/aliases_b') 321 self.assertPathPerm(True, 'Mr Hyde', '', '/aliases_b') 322 323 def test_scoped_repository(self): 324 # Take repository scope into account 325 self.assertPathPerm(True, 'joe', 'scoped', '/dir1') 326 self.assertPathPerm(None, 'joe', 'scoped', '/dir2') 327 self.assertPathPerm(True, 'joe', 'scoped', '/') 328 self.assertPathPerm(None, 'jane', 'scoped', '/dir1') 329 self.assertPathPerm(True, 'jane', 'scoped', '/dir2') 330 self.assertPathPerm(True, 'jane', 'scoped', '/') 331 332 def test_changesets(self): 333 # Changesets are allowed if at least one changed path is allowed, or 334 # if the changeset is empty 335 self.assertRevPerm(True, 'joe', 'scoped', 123) 336 self.assertRevPerm(None, 'joe', 'scoped', 456) 337 self.assertRevPerm(True, 'joe', 'scoped', 789) 338 self.assertRevPerm(None, 'jane', 'scoped', 123) 339 self.assertRevPerm(True, 'jane', 'scoped', 456) 340 self.assertRevPerm(True, 'jane', 'scoped', 789) 341 self.assertRevPerm(None, 'user', 'scoped', 123) 342 self.assertRevPerm(None, 'user', 'scoped', 456) 343 self.assertRevPerm(True, 'user', 'scoped', 789) 282 344 283 345 284 346 def suite():
