Package trac :: Package versioncontrol :: Package tests :: Module svn_authz

Source Code for Module trac.versioncontrol.tests.svn_authz

  1  # -*- coding: utf-8 -*- 
  2  # 
  3  # Copyright (C) 2005-2020 Edgewall Software 
  4  # All rights reserved. 
  5  # 
  6  # This software is licensed as described in the file COPYING, which 
  7  # you should have received as part of this distribution. The terms 
  8  # are also available at https://trac.edgewall.org/wiki/TracLicense. 
  9  # 
 10  # This software consists of voluntary contributions made by many 
 11  # individuals. For the exact contribution history, see the revision 
 12  # history and logs, available at https://trac.edgewall.org/log/. 
 13   
 14  import os.path 
 15  import textwrap 
 16  import unittest 
 17   
 18  from trac.config import ConfigurationError 
 19  from trac.resource import Resource 
 20  from trac.test import EnvironmentStub, Mock, mkdtemp, rmtree 
 21  from trac.util import create_file 
 22  from trac.versioncontrol.api import RepositoryManager 
 23  from trac.versioncontrol.svn_authz import AuthzSourcePolicy, parse 
 24   
 25   
26 -class AuthzParserTestCase(unittest.TestCase):
27
28 - def setUp(self):
29 self.tmpdir = mkdtemp() 30 self.authz_file = os.path.join(self.tmpdir, 'trac-authz')
31
32 - def tearDown(self):
33 rmtree(self.tmpdir)
34
35 - def test_parse_file(self):
36 create_file(self.authz_file, textwrap.dedent("""\ 37 [groups] 38 developers = foo, bar 39 users = @developers, &baz 40 41 [aliases] 42 baz = CN=Hàröld Hacker,OU=Enginéers,DC=red-bean,DC=com 43 44 # Applies to all repositories 45 [/] 46 * = r 47 48 [/trunk] 49 @developers = rw 50 &baz = 51 @users = r 52 53 [/branches] 54 bar = rw 55 56 ; Applies only to module 57 [module:/trunk] 58 foo = rw 59 &baz = r 60 61 ; Unicode module names 62 [module:/c/résumé] 63 bar = rw 64 Foo = rw 65 BAZ = r 66 67 ; Unicode module names 68 [module:/c/résumé] 69 bar = rw 70 Foo = rw 71 BAZ = r 72 73 ; Unused module, not parsed 74 [unused:/some/path] 75 foo = r 76 """)) 77 authz = parse(self.authz_file, {'', 'module'}) 78 self.assertEqual({ 79 '': { 80 u'/': { 81 u'*': True, 82 }, 83 u'/trunk': { 84 u'foo': True, 85 u'bar': True, 86 u'CN=Hàröld Hacker,OU=Enginéers,DC=red-bean,DC=com': True, 87 }, 88 u'/branches': { 89 u'bar': True, 90 }, 91 }, 92 u'module': { 93 u'/trunk': { 94 u'foo': True, 95 u'CN=Hàröld Hacker,OU=Enginéers,DC=red-bean,DC=com': True, 96 }, 97 u'/c/résumé': { 98 u'bar': True, 99 u'Foo': True, 100 u'BAZ': True, 101 }, 102 }, 103 }, authz)
104 105
106 -class AuthzSourcePolicyTestCase(unittest.TestCase):
107
108 - def setUp(self):
109 tmpdir = mkdtemp() 110 self.authz_file = os.path.join(tmpdir, 'trac-authz') 111 create_file(self.authz_file, textwrap.dedent("""\ 112 [groups] 113 group1 = user 114 group2 = @group1 115 116 cycle1 = @cycle2 117 cycle2 = @cycle3 118 cycle3 = @cycle1, user 119 120 alias1 = &jekyll 121 alias2 = @alias1 122 123 [aliases] 124 jekyll = Mr Hyde 125 126 # Read / write permissions 127 [/readonly] 128 user = r 129 [/writeonly] 130 user = w 131 [/readwrite] 132 user = rw 133 [/empty] 134 user = 135 136 # Trailing slashes 137 [/trailing_a] 138 user = r 139 [/trailing_b/] 140 user = r 141 142 # Sub-paths 143 [/sub/path] 144 user = r 145 146 # Module usage 147 [module:/module_a] 148 user = r 149 [other:/module_b] 150 user = r 151 [/module_c] 152 user = r 153 [module:/module_d] 154 user = 155 [/module_d] 156 user = r 157 158 # Wildcards 159 [/wildcard] 160 * = r 161 162 # Special tokens 163 [/special/anonymous] 164 $anonymous = r 165 [/special/authenticated] 166 $authenticated = r 167 168 # Groups 169 [/groups_a] 170 @group1 = r 171 [/groups_b] 172 @group2 = r 173 [/cyclic] 174 @cycle1 = r 175 176 # Precedence 177 [module:/precedence_a] 178 user = 179 [/precedence_a] 180 user = r 181 [/precedence_b] 182 user = r 183 [/precedence_b/sub] 184 user = 185 [/precedence_b/sub/test] 186 user = r 187 [/precedence_c] 188 user = 189 @group1 = r 190 [/precedence_d] 191 @group1 = r 192 user = 193 194 # Aliases 195 [/aliases_a] 196 &jekyll = r 197 [/aliases_b] 198 @alias2 = r 199 200 # Scoped repository 201 [scoped:/scope/dir1] 202 joe = r 203 [scoped:/scope/dir2] 204 Jane = r 205 206 # multiple entries 207 [/multiple] 208 $authenticated = r 209 [/multiple/foo] 210 joe = 211 $authenticated = 212 * = r 213 [/multiple/bar] 214 * = 215 john = r 216 Jane = r 217 $anonymous = r 218 [/multiple/baz] 219 $anonymous = r 220 * = 221 Jane = r 222 [module:/multiple/bar] 223 joe = r 224 john = 225 226 # multiple entries with module and parent directory 227 [/multiple/1] 228 user = r 229 @group1 = r 230 $authenticated = r 231 * = r 232 [module:/multiple/1/user] 233 user = 234 [module:/multiple/1/group] 235 @group1 = 236 [module:/multiple/1/auth] 237 $authenticated = 238 [module:/multiple/1/star] 239 * = 240 [/multiple/2] 241 user = 242 @group1 = 243 $authenticated = 244 * = 245 [module:/multiple/2/user] 246 user = r 247 [module:/multiple/2/group] 248 @group1 = r 249 [module:/multiple/2/auth] 250 $authenticated = r 251 [module:/multiple/2/star] 252 * = r 253 """)) 254 self.env = EnvironmentStub(enable=[AuthzSourcePolicy], path=tmpdir) 255 self.env.config.set('trac', 'permission_policies', 256 'AuthzSourcePolicy, DefaultPermissionPolicy') 257 self.env.config.set('svn', 'authz_file', self.authz_file) 258 259 # Monkey-subclass RepositoryManager to serve mock repositories 260 rm = RepositoryManager(self.env) 261 262 class TestRepositoryManager(rm.__class__): 263 def get_real_repositories(self): 264 return {Mock(reponame='module'), Mock(reponame='other'), 265 Mock(reponame='scoped')}
266 267 def get_repository(self, reponame): 268 if reponame == 'scoped': 269 def get_changeset(rev): 270 if rev == 123: 271 def get_changes(): 272 yield ('/dir1/file',)
273 elif rev == 456: 274 def get_changes(): 275 yield ('/dir2/file',) 276 else: 277 def get_changes(): 278 return iter([]) 279 return Mock(get_changes=get_changes) 280 return Mock(scope='/scope', 281 get_changeset=get_changeset) 282 return Mock(scope='/') 283 284 rm.__class__ = TestRepositoryManager 285
286 - def tearDown(self):
287 self.env.reset_db_and_disk()
288
289 - def test_get_authz_file_notfound_raises(self):
290 """ConfigurationError exception is raised if file not found.""" 291 authz_file = os.path.join(self.env.path, 'some-nonexistent-file') 292 self.env.config.set('svn', 'authz_file', authz_file) 293 policy = AuthzSourcePolicy(self.env) 294 self.assertRaises(ConfigurationError, policy.check_permission, 295 'BROWSER_VIEW', 'user', None, None)
296
297 - def test_get_authz_file_notdefined_raises(self):
298 """ConfigurationError exception is raised if the option 299 `[svn] authz_file` is not specified in trac.ini.""" 300 self.env.config.remove('svn', 'authz_file') 301 policy = AuthzSourcePolicy(self.env) 302 self.assertRaises(ConfigurationError, policy.check_permission, 303 'BROWSER_VIEW', 'user', None, None)
304
305 - def test_get_authz_file_empty_raises(self):
306 """ConfigurationError exception is raised if the option 307 `[svn] authz_file` is empty.""" 308 self.env.config.set('svn', 'authz_file', '') 309 policy = AuthzSourcePolicy(self.env) 310 self.assertRaises(ConfigurationError, policy.check_permission, 311 'BROWSER_VIEW', 'user', None, None)
312
313 - def test_get_authz_file_removed_raises(self):
314 """ConfigurationError exception is raised if file is removed.""" 315 policy = AuthzSourcePolicy(self.env) 316 os.remove(self.authz_file) 317 self.assertRaises(ConfigurationError, policy.check_permission, 318 'BROWSER_VIEW', 'user', None, None)
319
320 - def test_parse_error_raises(self):
321 """ConfigurationError exception is raised when exception occurs 322 parsing the `[svn authz_file`.""" 323 create_file(self.authz_file, textwrap.dedent("""\ 324 [/somepath 325 joe = r 326 """)) 327 policy = AuthzSourcePolicy(self.env) 328 self.assertRaises(ConfigurationError, policy.check_permission, 329 'BROWSER_VIEW', 'user', None, None)
330
331 - def assertPathPerm(self, result, user, reponame=None, path=None):
332 """Assert that `user` is granted access `result` to `path` within 333 the repository `reponame`. 334 """ 335 policy = AuthzSourcePolicy(self.env) 336 resource = None 337 if reponame is not None: 338 resource = Resource('source', path, 339 parent=Resource('repository', reponame)) 340 for perm in ('BROWSER_VIEW', 'FILE_VIEW', 'LOG_VIEW'): 341 check = policy.check_permission(perm, user, resource, None) 342 self.assertEqual(result, check)
343
344 - def assertRevPerm(self, result, user, reponame=None, rev=None):
345 """Assert that `user` is granted access `result` to `rev` within 346 the repository `reponame`. 347 """ 348 policy = AuthzSourcePolicy(self.env) 349 resource = None 350 if reponame is not None: 351 resource = Resource('changeset', rev, 352 parent=Resource('repository', reponame)) 353 check = policy.check_permission('CHANGESET_VIEW', user, resource, 354 None) 355 self.assertEqual(result, check)
356
357 - def test_coarse_permissions(self):
358 policy = AuthzSourcePolicy(self.env) 359 # Granted to all due to wildcard 360 self.assertPathPerm(True, 'unknown') 361 self.assertPathPerm(True, 'joe') 362 self.assertRevPerm(True, 'unknown') 363 self.assertRevPerm(True, 'joe') 364 # Granted if at least one fine permission is granted 365 policy._mtime = 0 366 create_file(self.authz_file, textwrap.dedent("""\ 367 [/somepath] 368 joe = r 369 denied = 370 [module:/otherpath] 371 Jane = r 372 $anonymous = r 373 [inactive:/not-in-this-instance] 374 unknown = r 375 """)) 376 self.assertPathPerm(None, 'unknown') 377 self.assertRevPerm(None, 'unknown') 378 self.assertPathPerm(None, 'denied') 379 self.assertRevPerm(None, 'denied') 380 self.assertPathPerm(True, 'joe') 381 self.assertRevPerm(True, 'joe') 382 self.assertPathPerm(True, 'Jane') 383 self.assertRevPerm(True, 'Jane') 384 self.assertPathPerm(True, 'anonymous') 385 self.assertRevPerm(True, 'anonymous')
386
387 - def test_default_permission(self):
388 # By default, permissions are undecided 389 self.assertPathPerm(None, 'joe', '', '/not_defined') 390 self.assertPathPerm(None, 'Jane', 'repo', '/not/defined/either')
391
392 - def test_read_write(self):
393 # Allow 'r' and 'rw' entries, deny 'w' and empty entries 394 self.assertPathPerm(True, 'user', '', '/readonly') 395 self.assertPathPerm(True, 'user', '', '/readwrite') 396 self.assertPathPerm(False, 'user', '', '/writeonly') 397 self.assertPathPerm(False, 'user', '', '/empty')
398
399 - def test_trailing_slashes(self):
400 # Combinations of trailing slashes in the file and in the path 401 self.assertPathPerm(True, 'user', '', '/trailing_a') 402 self.assertPathPerm(True, 'user', '', '/trailing_a/') 403 self.assertPathPerm(True, 'user', '', '/trailing_b') 404 self.assertPathPerm(True, 'user', '', '/trailing_b/')
405
406 - def test_sub_path(self):
407 # Permissions are inherited from containing directories 408 self.assertPathPerm(True, 'user', '', '/sub/path') 409 self.assertPathPerm(True, 'user', '', '/sub/path/test') 410 self.assertPathPerm(True, 'user', '', '/sub/path/other/sub')
411
412 - def test_module_usage(self):
413 # If a module name is specified, the rules are specific to the module 414 self.assertPathPerm(True, 'user', 'module', '/module_a') 415 self.assertPathPerm(None, 'user', 'module', '/module_b') 416 # If a module is specified, but the configuration contains a non-module 417 # path, the non-module path can still apply 418 self.assertPathPerm(True, 'user', 'module', '/module_c') 419 # The module-specific rule takes precedence 420 self.assertPathPerm(True, 'user', '', '/module_d') 421 self.assertPathPerm(False, 'user', 'module', '/module_d')
422
423 - def test_wildcard(self):
424 # The * wildcard matches all users, including anonymous 425 self.assertPathPerm(True, 'anonymous', '', '/wildcard') 426 self.assertPathPerm(True, 'joe', '', '/wildcard') 427 self.assertPathPerm(True, 'Jane', '', '/wildcard')
428
429 - def test_special_tokens(self):
430 # The $anonymous token matches only anonymous users 431 self.assertPathPerm(True, 'anonymous', '', '/special/anonymous') 432 self.assertPathPerm(None, 'user', '', '/special/anonymous') 433 # The $authenticated token matches all authenticated users 434 self.assertPathPerm(None, 'anonymous', '', '/special/authenticated') 435 self.assertPathPerm(True, 'joe', '', '/special/authenticated') 436 self.assertPathPerm(True, 'Jane', '', '/special/authenticated')
437
438 - def test_groups(self):
439 # Groups are specified in a separate section and used with an @ prefix 440 self.assertPathPerm(True, 'user', '', '/groups_a') 441 # Groups can also be members of other groups 442 self.assertPathPerm(True, 'user', '', '/groups_b') 443 # Groups should not be defined cyclically, but they are still handled 444 # correctly to avoid infinite loops 445 self.assertPathPerm(True, 'user', '', '/cyclic')
446
447 - def test_precedence(self):
448 # Module-specific sections take precedence over non-module sections 449 self.assertPathPerm(False, 'user', 'module', '/precedence_a') 450 # The most specific section applies 451 self.assertPathPerm(True, 'user', '', '/precedence_b/sub/test') 452 # ... intentional deviation from SVN's rules as we need to 453 # make '/precedence_b/sub' browseable so that the user can see 454 # '/precedence_b/sub/test': 455 self.assertPathPerm(True, 'user', '', '/precedence_b/sub') 456 self.assertPathPerm(True, 'user', '', '/precedence_b') 457 # Ordering isn't significant; any entry could grant permission 458 self.assertPathPerm(True, 'user', '', '/precedence_c') 459 self.assertPathPerm(True, 'user', '', '/precedence_d')
460
461 - def test_aliases(self):
462 # Aliases are specified in a separate section and used with an & prefix 463 self.assertPathPerm(True, 'Mr Hyde', '', '/aliases_a') 464 # Aliases can also be used in groups 465 self.assertPathPerm(True, 'Mr Hyde', '', '/aliases_b')
466
467 - def test_scoped_repository(self):
468 # Take repository scope into account 469 self.assertPathPerm(True, 'joe', 'scoped', '/dir1') 470 self.assertPathPerm(None, 'joe', 'scoped', '/dir2') 471 self.assertPathPerm(True, 'joe', 'scoped', '/') 472 self.assertPathPerm(None, 'Jane', 'scoped', '/dir1') 473 self.assertPathPerm(True, 'Jane', 'scoped', '/dir2') 474 self.assertPathPerm(True, 'Jane', 'scoped', '/')
475
476 - def test_multiple_entries(self):
477 self.assertPathPerm(True, 'anonymous', '', '/multiple/foo') 478 self.assertPathPerm(True, 'joe', '', '/multiple/foo') 479 self.assertPathPerm(True, 'anonymous', '', '/multiple/bar') 480 self.assertPathPerm(False, 'joe', '', '/multiple/bar') 481 self.assertPathPerm(True, 'john', '', '/multiple/bar') 482 self.assertPathPerm(True, 'anonymous', '', '/multiple/baz') 483 self.assertPathPerm(True, 'Jane', '', '/multiple/baz') 484 self.assertPathPerm(False, 'joe', '', '/multiple/baz') 485 self.assertPathPerm(True, 'anonymous', 'module', '/multiple/foo') 486 self.assertPathPerm(True, 'joe', 'module', '/multiple/foo') 487 self.assertPathPerm(True, 'anonymous', 'module', '/multiple/bar') 488 self.assertPathPerm(True, 'joe', 'module', '/multiple/bar') 489 self.assertPathPerm(False, 'john', 'module', '/multiple/bar') 490 self.assertPathPerm(True, 'anonymous', 'module', '/multiple/baz') 491 self.assertPathPerm(True, 'Jane', 'module', '/multiple/baz') 492 self.assertPathPerm(False, 'joe', 'module', '/multiple/baz')
493
494 - def test_multiple_entries_with_module_and_parent_directory(self):
495 self.assertPathPerm(True, 'anonymous', '', '/multiple/1') 496 self.assertPathPerm(True, 'user', '', '/multiple/1') 497 self.assertPathPerm(True, 'someone', '', '/multiple/1') 498 self.assertPathPerm(True, 'anonymous', 'module', '/multiple/1') 499 self.assertPathPerm(True, 'user', 'module', '/multiple/1') 500 self.assertPathPerm(True, 'someone', 'module', '/multiple/1') 501 self.assertPathPerm(True, 'anonymous', 'module', '/multiple/1/user') 502 self.assertPathPerm(False, 'user', 'module', '/multiple/1/user') 503 self.assertPathPerm(True, 'someone', 'module', '/multiple/1/user') 504 self.assertPathPerm(True, 'anonymous', 'module', '/multiple/1/group') 505 self.assertPathPerm(False, 'user', 'module', '/multiple/1/group') 506 self.assertPathPerm(True, 'someone', 'module', '/multiple/1/group') 507 self.assertPathPerm(True, 'anonymous', 'module', '/multiple/1/auth') 508 self.assertPathPerm(False, 'user', 'module', '/multiple/1/auth') 509 self.assertPathPerm(False, 'someone', 'module', '/multiple/1/auth') 510 self.assertPathPerm(False, 'anonymous', 'module', '/multiple/1/star') 511 self.assertPathPerm(False, 'user', 'module', '/multiple/1/star') 512 self.assertPathPerm(False, 'someone', 'module', '/multiple/1/star') 513 514 self.assertPathPerm(False, 'anonymous', '', '/multiple/2') 515 self.assertPathPerm(False, 'user', '', '/multiple/2') 516 self.assertPathPerm(False, 'someone', '', '/multiple/2') 517 self.assertPathPerm(True, 'anonymous', 'module', '/multiple/2') 518 self.assertPathPerm(True, 'user', 'module', '/multiple/2') 519 self.assertPathPerm(True, 'someone', 'module', '/multiple/2') 520 self.assertPathPerm(False, 'anonymous', 'module', '/multiple/2/user') 521 self.assertPathPerm(True, 'user', 'module', '/multiple/2/user') 522 self.assertPathPerm(False, 'someone', 'module', '/multiple/2/user') 523 self.assertPathPerm(False, 'anonymous', 'module', '/multiple/2/group') 524 self.assertPathPerm(True, 'user', 'module', '/multiple/2/group') 525 self.assertPathPerm(False, 'someone', 'module', '/multiple/2/group') 526 self.assertPathPerm(False, 'anonymous', 'module', '/multiple/2/auth') 527 self.assertPathPerm(True, 'user', 'module', '/multiple/2/auth') 528 self.assertPathPerm(True, 'someone', 'module', '/multiple/2/auth') 529 self.assertPathPerm(True, 'anonymous', 'module', '/multiple/2/star') 530 self.assertPathPerm(True, 'user', 'module', '/multiple/2/star') 531 self.assertPathPerm(True, 'someone', 'module', '/multiple/2/star')
532
533 - def test_changesets(self):
534 # Changesets are allowed if at least one changed path is allowed, or 535 # if the changeset is empty 536 self.assertRevPerm(True, 'joe', 'scoped', 123) 537 self.assertRevPerm(None, 'joe', 'scoped', 456) 538 self.assertRevPerm(True, 'joe', 'scoped', 789) 539 self.assertRevPerm(None, 'Jane', 'scoped', 123) 540 self.assertRevPerm(True, 'Jane', 'scoped', 456) 541 self.assertRevPerm(True, 'Jane', 'scoped', 789) 542 self.assertRevPerm(None, 'user', 'scoped', 123) 543 self.assertRevPerm(None, 'user', 'scoped', 456) 544 self.assertRevPerm(True, 'user', 'scoped', 789)
545 546
547 -def test_suite():
548 suite = unittest.TestSuite() 549 suite.addTest(unittest.makeSuite(AuthzParserTestCase)) 550 suite.addTest(unittest.makeSuite(AuthzSourcePolicyTestCase)) 551 return suite
552 553 554 if __name__ == '__main__': 555 unittest.main(defaultTest='test_suite') 556