1# -*- coding: utf-8 -*-
2#
3# Copyright (C) 2004-2021 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
14import unittest
15
16from trac import perm
17from trac.admin.console import TracAdmin
18from trac.admin.test import TracAdminTestCaseBase
19from trac.core import Component, ComponentMeta, TracError, implements
20from trac.resource import Resource
21from trac.test import EnvironmentStub
22
23# IPermissionRequestor implementations
24import trac.about
25import trac.admin.web_ui
26import trac.perm
27import trac.search.web_ui
28import trac.ticket.api
29import trac.ticket.batch
30import trac.ticket.report
31import trac.ticket.roadmap
32import trac.timeline.web_ui
33import trac.versioncontrol.admin
34import trac.versioncontrol.web_ui.browser
35import trac.versioncontrol.web_ui.changeset
36import trac.versioncontrol.web_ui.log
37import trac.web.chrome
38import trac.wiki.web_ui
39
40
41class DefaultPermissionStoreTestCase(unittest.TestCase):
42
43    def setUp(self):
44        self.env = \
45            EnvironmentStub(enable=[perm.DefaultPermissionStore,
46                                    perm.DefaultPermissionGroupProvider])
47        self.store = perm.DefaultPermissionStore(self.env)
48
49    def tearDown(self):
50        self.env.reset_db()
51
52    def test_simple_actions(self):
53        self.env.db_transaction.executemany(
54            "INSERT INTO permission VALUES (%s,%s)",
55            [('john', 'WIKI_MODIFY'),
56             ('john', 'REPORT_ADMIN'),
57             ('kate', 'TICKET_CREATE')])
58        self.assertEqual(['REPORT_ADMIN', 'WIKI_MODIFY'],
59                         self.store.get_user_permissions('john'))
60        self.assertEqual(['TICKET_CREATE'],
61                         self.store.get_user_permissions('kate'))
62
63    def test_simple_group(self):
64        self.env.db_transaction.executemany(
65            "INSERT INTO permission VALUES (%s,%s)",
66            [('dev', 'WIKI_MODIFY'),
67             ('dev', 'REPORT_ADMIN'),
68             ('john', 'dev')])
69        self.assertEqual(['REPORT_ADMIN', 'WIKI_MODIFY'],
70                         self.store.get_user_permissions('john'))
71
72    def test_nested_groups(self):
73        self.env.db_transaction.executemany(
74            "INSERT INTO permission VALUES (%s,%s)",
75            [('dev', 'WIKI_MODIFY'),
76             ('dev', 'REPORT_ADMIN'),
77             ('admin', 'dev'),
78             ('john', 'admin')])
79        self.assertEqual(['REPORT_ADMIN', 'WIKI_MODIFY'],
80                         self.store.get_user_permissions('john'))
81
82    def test_mixed_case_group(self):
83        self.env.db_transaction.executemany(
84            "INSERT INTO permission VALUES (%s,%s)",
85            [('Dev', 'WIKI_MODIFY'),
86             ('Dev', 'REPORT_ADMIN'),
87             ('Admin', 'Dev'),
88             ('john', 'Admin')])
89        self.assertEqual(['REPORT_ADMIN', 'WIKI_MODIFY'],
90                         self.store.get_user_permissions('john'))
91
92    def test_builtin_groups(self):
93        self.env.db_transaction.executemany(
94            "INSERT INTO permission VALUES (%s,%s)",
95            [('authenticated', 'WIKI_MODIFY'),
96             ('authenticated', 'REPORT_ADMIN'),
97             ('anonymous', 'TICKET_CREATE')])
98        self.assertEqual(['REPORT_ADMIN', 'TICKET_CREATE', 'WIKI_MODIFY'],
99                         self.store.get_user_permissions('john'))
100        self.assertEqual(['TICKET_CREATE'],
101                         self.store.get_user_permissions('anonymous'))
102
103    def test_get_all_permissions(self):
104        self.env.db_transaction.executemany(
105            "INSERT INTO permission VALUES (%s,%s)",
106            [('dev', 'WIKI_MODIFY'),
107             ('dev', 'REPORT_ADMIN'),
108             ('john', 'dev')])
109        expected = [('dev', 'WIKI_MODIFY'),
110                    ('dev', 'REPORT_ADMIN'),
111                    ('john', 'dev')]
112        for res in self.store.get_all_permissions():
113            self.assertIn(res, expected)
114
115    def test_get_permission_groups(self):
116        self.env.db_transaction.executemany(
117            "INSERT INTO permission VALUES (%s,%s)",
118            [('user1', 'group1'),
119             ('group1', 'group2'),
120             ('group2', 'group3'),
121             ('user2', 'group4'),
122             ('user1', 'group5'),
123             ('group6', 'group7'),
124             ('user3', 'group8'),  # test recursion
125             ('group8', 'group9'),
126             ('group9', 'group8'),
127             ('user3', 'group11'),
128             ('group11', 'group10'),  # test recursion
129             ('group10', 'group11'),
130             ('group10', 'group10')])
131        self.assertEqual(['group1', 'group2', 'group3', 'group5'],
132                         self.store.get_permission_groups('user1'))
133        self.assertEqual(['group4'],
134                         self.store.get_permission_groups('user2'))
135        self.assertEqual(['group10', 'group11', 'group8', 'group9'],
136                         self.store.get_permission_groups('user3'))
137
138
139class BaseTestCase(unittest.TestCase):
140
141    permission_requestors = []
142
143    @classmethod
144    def setUpClass(cls):
145        class TestPermissionRequestor(Component):
146            implements(perm.IPermissionRequestor)
147
148            def get_permission_actions(self):
149                return ['TEST_CREATE', 'TEST_DELETE', 'TEST_MODIFY',
150                        ('TEST_CREATE', []),
151                        ('TEST_ADMIN', ['TEST_CREATE', 'TEST_DELETE']),
152                        ('TEST_ADMIN', ['TEST_MODIFY'])]
153
154        cls.permission_requestors = [TestPermissionRequestor]
155
156    @classmethod
157    def tearDownClass(cls):
158        for component in cls.permission_requestors:
159            ComponentMeta.deregister(component)
160
161
162class PermissionErrorTestCase(unittest.TestCase):
163
164    def setUp(self):
165        self.env = EnvironmentStub()
166
167    def test_default_message(self):
168        permission_error = perm.PermissionError()
169        self.assertIsNone(permission_error.action)
170        self.assertIsNone(permission_error.resource)
171        self.assertIsNone(permission_error.env)
172        self.assertEqual("Insufficient privileges to perform this operation.",
173                         str(permission_error))
174        self.assertEqual("Forbidden", permission_error.title)
175        self.assertEqual(str(permission_error), permission_error.message)
176
177    def test_message_specified(self):
178        message = "The message."
179        permission_error = perm.PermissionError(msg=message)
180        self.assertEqual(message, str(permission_error))
181
182    def test_message_from_action(self):
183        action = 'WIKI_VIEW'
184        permission_error = perm.PermissionError(action)
185        self.assertEqual(action, permission_error.action)
186        self.assertIsNone(permission_error.resource)
187        self.assertIsNone(permission_error.env)
188        self.assertEqual("WIKI_VIEW privileges are required to perform this "
189                         "operation. You don't have the required "
190                         "permissions.", str(permission_error))
191
192    def test_message_from_action_and_resource(self):
193        action = 'WIKI_VIEW'
194        resource = Resource('wiki', 'WikiStart')
195        permission_error = perm.PermissionError(action, resource, self.env)
196        self.assertEqual(action, permission_error.action)
197        self.assertEqual(resource, permission_error.resource)
198        self.assertEqual(self.env, permission_error.env)
199        self.assertEqual("WIKI_VIEW privileges are required to perform this "
200                         "operation on WikiStart. You don't have the "
201                         "required permissions.", str(permission_error))
202
203    def test_message_from_action_and_resource_without_id(self):
204        action = 'TIMELINE_VIEW'
205        resource = Resource('timeline')
206        permission_error = perm.PermissionError(action, resource, self.env)
207        self.assertEqual(action, permission_error.action)
208        self.assertEqual(resource, permission_error.resource)
209        self.assertEqual(self.env, permission_error.env)
210        self.assertEqual("TIMELINE_VIEW privileges are required to perform "
211                         "this operation. You don't have the required "
212                         "permissions.", str(permission_error))
213
214
215class PermissionSystemTestCase(BaseTestCase):
216
217    def setUp(self):
218        self.env = EnvironmentStub(enable=[perm.PermissionSystem,
219                                           perm.DefaultPermissionGroupProvider,
220                                           perm.DefaultPermissionStore] +
221                                          self.permission_requestors)
222        self.perm = perm.PermissionSystem(self.env)
223
224    def tearDown(self):
225        self.env.reset_db()
226
227    def test_get_actions(self):
228        tpr_perms = ['TEST_ADMIN', 'TEST_CREATE', 'TEST_DELETE', 'TEST_MODIFY']
229        all_perms = tpr_perms + ['TRAC_ADMIN']
230        self.assertEqual(all_perms, self.perm.get_actions())
231        self.assertEqual(tpr_perms,
232                         self.perm.get_actions(skip=self.perm))
233
234    def test_actions(self):
235        self.assertEqual(self.perm.get_actions(), self.perm.actions)
236
237    def test_actions_is_lazy(self):
238        actions = self.perm.actions
239        self.assertEqual(id(actions), id(self.perm.actions))
240
241    def test_get_actions_dict(self):
242        self.assertEqual({
243            'TEST_ADMIN': ['TEST_CREATE', 'TEST_DELETE', 'TEST_MODIFY'],
244            'TEST_CREATE': [],
245            'TEST_DELETE': [],
246            'TEST_MODIFY': [],
247            'TRAC_ADMIN': ['TEST_ADMIN', 'TEST_CREATE', 'TEST_DELETE',
248                           'TEST_MODIFY'],
249        }, self.perm.get_actions_dict())
250        self.assertEqual({
251            'TEST_ADMIN': ['TEST_CREATE', 'TEST_DELETE', 'TEST_MODIFY'],
252            'TEST_CREATE': [],
253            'TEST_DELETE': [],
254            'TEST_MODIFY': [],
255        }, self.perm.get_actions_dict(skip=self.perm))
256
257    def test_all_permissions(self):
258        self.assertEqual({'TRAC_ADMIN': True, 'TEST_CREATE': True,
259                          'TEST_DELETE': True, 'TEST_MODIFY': True,
260                          'TEST_ADMIN': True},
261                         self.perm.get_user_permissions())
262
263    def test_simple_permissions(self):
264        self.perm.grant_permission('bob', 'TEST_CREATE')
265        self.perm.grant_permission('jane', 'TEST_DELETE')
266        self.perm.grant_permission('jane', 'TEST_MODIFY')
267        self.assertEqual({'TEST_CREATE': True},
268                         self.perm.get_user_permissions('bob'))
269        self.assertEqual({'TEST_DELETE': True, 'TEST_MODIFY': True},
270                         self.perm.get_user_permissions('jane'))
271
272    def test_meta_permissions(self):
273        self.perm.grant_permission('bob', 'TEST_CREATE')
274        self.perm.grant_permission('jane', 'TEST_ADMIN')
275        self.assertEqual({'TEST_CREATE': True},
276                         self.perm.get_user_permissions('bob'))
277        self.assertEqual({'TEST_CREATE': True, 'TEST_DELETE': True,
278                          'TEST_MODIFY': True,  'TEST_ADMIN': True},
279                         self.perm.get_user_permissions('jane'))
280
281    def test_undefined_permissions(self):
282        """Only defined actions are returned in the dictionary."""
283        self.perm.grant_permission('bob', 'TEST_CREATE')
284        self.perm.grant_permission('jane', 'TEST_DELETE')
285        self.perm.grant_permission('jane', 'TEST_MODIFY')
286
287        self.env.disable_component(self.permission_requestors[0])
288
289        self.assertEqual({}, self.perm.get_user_permissions('bob'))
290        self.assertEqual({}, self.perm.get_user_permissions('jane'))
291
292    def test_grant_permission_differs_from_action_by_casing(self):
293        """`TracError` is raised when granting a permission that differs
294        from an action by casing.
295        """
296        self.assertRaises(TracError, self.perm.grant_permission, 'user1',
297                          'Test_Create')
298
299    def test_grant_permission_already_granted(self):
300        """`PermissionExistsError` is raised when granting a permission
301        that has already been granted.
302        """
303        self.perm.grant_permission('user1', 'TEST_CREATE')
304        self.assertRaises(perm.PermissionExistsError,
305                          self.perm.grant_permission, 'user1', 'TEST_CREATE')
306
307    def test_grant_permission_already_in_group(self):
308        """`PermissionExistsError` is raised when adding a user to
309        a group of which they are already a member.
310        """
311        self.perm.grant_permission('user1', 'group1')
312        self.assertRaises(perm.PermissionExistsError,
313                          self.perm.grant_permission, 'user1', 'group1')
314
315    def test_get_all_permissions(self):
316        self.perm.grant_permission('bob', 'TEST_CREATE')
317        self.perm.grant_permission('jane', 'TEST_ADMIN')
318        expected = [('bob', 'TEST_CREATE'),
319                    ('jane', 'TEST_ADMIN')]
320        for res in self.perm.get_all_permissions():
321            self.assertIn(res, expected)
322
323    def test_get_groups_dict(self):
324        permissions = [
325            ('user2', 'group1'),
326            ('user1', 'group1'),
327            ('user3', 'group1'),
328            ('user3', 'group2')
329        ]
330        for perm_ in permissions:
331            self.perm.grant_permission(*perm_)
332
333        groups = self.perm.get_groups_dict()
334        self.assertEqual(2, len(groups))
335        self.assertEqual(['user1', 'user2', 'user3'], groups['group1'])
336        self.assertEqual(['user3'], groups['group2'])
337
338    def test_get_users_dict(self):
339        permissions = [
340            ('user2', 'TEST_CREATE'),
341            ('user1', 'TEST_DELETE'),
342            ('user1', 'TEST_ADMIN'),
343            ('user1', 'TEST_CREATE')
344        ]
345        for perm_ in permissions:
346            self.perm.grant_permission(*perm_)
347
348        users = self.perm.get_users_dict()
349        self.assertEqual(2, len(users))
350        self.assertEqual(['TEST_ADMIN', 'TEST_CREATE', 'TEST_DELETE'],
351                         users['user1'])
352        self.assertEqual(['TEST_CREATE'], users['user2'])
353
354    def test_get_permission_groups(self):
355        permissions = [
356            ('user1', 'group1'),
357            ('group1', 'group2'),
358            ('group2', 'group3'),
359            ('user2', 'group4'),
360            ('user1', 'group5'),
361            ('group6', 'group7'),
362            ('user3', 'group8'), # test recursion
363            ('group8', 'group9'),
364            ('group9', 'group8'),
365            ('user3', 'group11'),
366            ('group11', 'group10'),  # test recursion
367            ('group10', 'group11'),
368            ('group10', 'group10'),
369        ]
370        for perm_ in permissions:
371            self.perm.grant_permission(*perm_)
372
373        self.assertEqual(['anonymous', 'authenticated', 'group1', 'group2',
374                          'group3', 'group5'],
375                         self.perm.get_permission_groups('user1'))
376        self.assertEqual(['anonymous', 'authenticated', 'group4'],
377                         self.perm.get_permission_groups('user2'))
378        self.assertEqual(['anonymous', 'authenticated', 'group10', 'group11',
379                          'group8', 'group9'],
380                         self.perm.get_permission_groups('user3'))
381
382    def test_expand_actions_iter_7467(self):
383        # Check that expand_actions works with iterators (#7467)
384        perms = ['TEST_ADMIN', 'TEST_CREATE', 'TEST_DELETE', 'TEST_MODIFY',
385                 'TRAC_ADMIN']
386        self.assertEqual(perms, self.perm.expand_actions(['TRAC_ADMIN']))
387        self.assertEqual(perms, self.perm.expand_actions(iter(['TRAC_ADMIN'])))
388
389
390class PermissionCacheTestCase(BaseTestCase):
391
392    def setUp(self):
393        self.env = EnvironmentStub(enable=[perm.DefaultPermissionStore,
394                                           perm.DefaultPermissionPolicy] +
395                                          self.permission_requestors)
396        self.env.config.set('trac', 'permission_policies',
397                            'DefaultPermissionPolicy')
398        self.perm_system = perm.PermissionSystem(self.env)
399        # by-pass DefaultPermissionPolicy cache:
400        perm.DefaultPermissionPolicy.CACHE_EXPIRY = -1
401        self.perm_system.grant_permission('testuser', 'TEST_MODIFY')
402        self.perm_system.grant_permission('testuser', 'TEST_ADMIN')
403        self.perm = perm.PermissionCache(self.env, 'testuser')
404
405    def tearDown(self):
406        self.env.reset_db()
407
408    def test_contains(self):
409        self.assertIn('TEST_MODIFY', self.perm)
410        self.assertIn('TEST_ADMIN', self.perm)
411        self.assertNotIn('TRAC_ADMIN', self.perm)
412
413    def test_has_permission(self):
414        self.assertTrue(self.perm.has_permission('TEST_MODIFY'))
415        self.assertTrue(self.perm.has_permission('TEST_ADMIN'))
416        self.assertFalse(self.perm.has_permission('TRAC_ADMIN'))
417
418    def test_require(self):
419        self.perm.require('TEST_MODIFY')
420        self.perm.require('TEST_ADMIN')
421        with self.assertRaises(perm.PermissionError):
422            self.perm.require('TRAC_ADMIN')
423
424    def test_assert_permission(self):
425        self.perm.assert_permission('TEST_MODIFY')
426        self.perm.assert_permission('TEST_ADMIN')
427        with self.assertRaises(perm.PermissionError):
428            self.perm.assert_permission('TRAC_ADMIN')
429
430    def test_cache(self):
431        self.perm.require('TEST_MODIFY')
432        self.perm.require('TEST_ADMIN')
433        self.perm_system.revoke_permission('testuser', 'TEST_ADMIN')
434        # Using cached GRANT here
435        self.perm.require('TEST_ADMIN')
436
437    def test_cache_shared(self):
438        # we need to start with an empty cache here (#7201)
439        perm1 = perm.PermissionCache(self.env, 'testcache')
440        perm1 = perm1('ticket', 1)
441        perm2 = perm1('ticket', 1) # share internal cache
442        self.perm_system.grant_permission('testcache', 'TEST_ADMIN')
443        perm1.require('TEST_ADMIN')
444        self.perm_system.revoke_permission('testcache', 'TEST_ADMIN')
445        # Using cached GRANT here (from shared cache)
446        perm2.require('TEST_ADMIN')
447
448    def test_has_permission_on_resource_none(self):
449        """'PERM' in perm(None) should cache the same value as
450        'PERM' in perm(None) (#12597).
451        """
452        'TEST_ADMIN' in self.perm
453        self.assertEqual(1, len(self.perm._cache))
454        'TEST_ADMIN' in self.perm(None)
455        self.assertEqual(1, len(self.perm._cache))
456
457
458class TestPermissionPolicy(Component):
459    implements(perm.IPermissionPolicy)
460
461    def __init__(self):
462        self.allowed = {}
463        self.results = {}
464
465    def grant(self, username, permissions):
466        self.allowed.setdefault(username, set()).update(permissions)
467
468    def revoke(self, username, permissions):
469        self.allowed.setdefault(username, set()).difference_update(permissions)
470
471    def check_permission(self, action, username, resource, perm):
472        result = action in self.allowed.get(username, set()) or None
473        self.results[(username, action)] = result
474        return result
475
476
477class PermissionPolicyTestCase(BaseTestCase):
478
479    def setUp(self):
480        self.env = EnvironmentStub(enable=[perm.DefaultPermissionStore,
481                                           perm.DefaultPermissionPolicy,
482                                           TestPermissionPolicy] +
483                                          self.permission_requestors)
484        self.env.config.set('trac', 'permission_policies',
485                            'TestPermissionPolicy')
486        self.policy = TestPermissionPolicy(self.env)
487        self.perm = perm.PermissionCache(self.env, 'testuser')
488
489    def tearDown(self):
490        self.env.reset_db()
491
492    def test_no_permissions(self):
493        self.assertRaises(perm.PermissionError,
494                          self.perm.require, 'TEST_MODIFY')
495        self.assertRaises(perm.PermissionError,
496                          self.perm.require, 'TEST_ADMIN')
497        self.assertEqual(self.policy.results,
498                         {('testuser', 'TEST_MODIFY'): None,
499                          ('testuser', 'TEST_ADMIN'): None})
500
501    def test_grant_revoke_permissions(self):
502        self.policy.grant('testuser', ['TEST_MODIFY', 'TEST_ADMIN'])
503        self.assertIn('TEST_MODIFY', self.perm)
504        self.assertIn('TEST_ADMIN', self.perm)
505        self.assertEqual(self.policy.results,
506                         {('testuser', 'TEST_MODIFY'): True,
507                          ('testuser', 'TEST_ADMIN'): True})
508
509    def test_policy_chaining(self):
510        self.env.config.set('trac', 'permission_policies',
511                            'TestPermissionPolicy,DefaultPermissionPolicy')
512        self.policy.grant('testuser', ['TEST_MODIFY'])
513        system = perm.PermissionSystem(self.env)
514        system.grant_permission('testuser', 'TEST_ADMIN')
515
516        self.assertEqual(list(system.policies),
517                         [self.policy,
518                          perm.DefaultPermissionPolicy(self.env)])
519        self.assertIn('TEST_MODIFY', self.perm)
520        self.assertIn('TEST_ADMIN', self.perm)
521        self.assertEqual(self.policy.results,
522                         {('testuser', 'TEST_MODIFY'): True,
523                          ('testuser', 'TEST_ADMIN'): None})
524
525
526class RecursivePolicyTestCase(unittest.TestCase):
527    """Test case for policies that perform recursive permission checks."""
528
529    permission_policies = []
530    decisions = []
531
532    @classmethod
533    def setUpClass(cls):
534
535        class PermissionPolicy1(Component):
536
537            implements(perm.IPermissionPolicy)
538
539            def __init__(self):
540                self.call_count = 0
541                self.decisions = cls.decisions
542
543            def check_permission(self, action, username, resource, perm):
544                self.call_count += 1
545                decision = None
546                if 'ACTION_2' in perm(resource):
547                    decision = None
548                elif action == 'ACTION_1':
549                    decision = username == 'user1'
550                self.decisions.append(('policy1', action, decision))
551                return decision
552
553        class PermissionPolicy2(Component):
554
555            implements(perm.IPermissionPolicy)
556
557            def __init__(self):
558                self.call_count = 0
559                self.decisions = cls.decisions
560
561            def check_permission(self, action, username, resource, perm):
562                self.call_count += 1
563                decision = None
564                if action == 'ACTION_2':
565                    decision = username == 'user2'
566                self.decisions.append(('policy2', action, decision))
567                return decision
568
569        cls.permission_policies = [PermissionPolicy1, PermissionPolicy2]
570
571    @classmethod
572    def tearDownClass(cls):
573        from trac.core import ComponentMeta
574        for component in cls.permission_policies:
575            ComponentMeta.deregister(component)
576
577    def setUp(self):
578        self.__class__.decisions = []
579        self.env = EnvironmentStub(enable=self.permission_policies)
580        self.env.config.set('trac', 'permission_policies',
581                            'PermissionPolicy1, PermissionPolicy2')
582        self.ps = perm.PermissionSystem(self.env)
583
584    def tearDown(self):
585        self.env.reset_db()
586
587    def test_user1_allowed_by_policy1(self):
588        """policy1 consulted for ACTION_1. policy1 and policy2 consulted
589        for ACTION_2.
590        """
591        perm_cache = perm.PermissionCache(self.env, 'user1')
592        self.assertIn('ACTION_1', perm_cache)
593        self.assertEqual(2, self.ps.policies[0].call_count)
594        self.assertEqual(1, self.ps.policies[1].call_count)
595        self.assertEqual([
596            ('policy1', 'ACTION_2', None),
597            ('policy2', 'ACTION_2', False),
598            ('policy1', 'ACTION_1', True),
599        ], self.decisions)
600
601    def test_user2_denied_by_no_decision(self):
602        """policy1 and policy2 consulted for ACTION_1. policy1 and
603        policy2 consulted for ACTION_2.
604        """
605        perm_cache = perm.PermissionCache(self.env, 'user2')
606        self.assertNotIn('ACTION_1', perm_cache)
607        self.assertEqual(2, self.ps.policies[0].call_count)
608        self.assertEqual(2, self.ps.policies[1].call_count)
609        self.assertEqual([
610            ('policy1', 'ACTION_2', None),
611            ('policy2', 'ACTION_2', True),
612            ('policy1', 'ACTION_1', None),
613            ('policy2', 'ACTION_1', None),
614        ], self.decisions)
615
616    def test_user1_denied_by_policy2(self):
617        """policy1 consulted for ACTION_2. policy2 consulted for ACTION_2.
618        """
619        perm_cache = perm.PermissionCache(self.env, 'user1')
620        self.assertNotIn('ACTION_2', perm_cache)
621        self.assertEqual(1, self.ps.policies[0].call_count)
622        self.assertEqual(1, self.ps.policies[1].call_count)
623        self.assertEqual([
624            ('policy1', 'ACTION_2', None),
625            ('policy2', 'ACTION_2', False),
626        ], self.decisions)
627
628    def test_user1_allowed_by_policy2(self):
629        """policy1 consulted for ACTION_2. policy2 consulted for ACTION_2.
630        """
631        perm_cache = perm.PermissionCache(self.env, 'user2')
632        self.assertIn('ACTION_2', perm_cache)
633        self.assertEqual(1, self.ps.policies[0].call_count)
634        self.assertEqual(1, self.ps.policies[1].call_count)
635        self.assertEqual([
636            ('policy1', 'ACTION_2', None),
637            ('policy2', 'ACTION_2', True),
638        ], self.decisions)
639
640
641class TracAdminTestCase(TracAdminTestCaseBase):
642
643    def setUp(self):
644        self.env = EnvironmentStub(default_data=True)
645        self.admin = TracAdmin()
646        self.admin.env_set('', self.env)
647
648    def tearDown(self):
649        self.env.reset_db()
650        self.env = None
651
652    def test_permission_list_ok(self):
653        """Tests the 'permission list' command in trac-admin."""
654        rv, output = self.execute('permission list')
655        self.assertEqual(0, rv, output)
656        self.assertExpectedResult(output)
657
658    def test_permission_list_includes_undefined_actions(self):
659        """Undefined actions are included in the User Action table,
660        but not in the Available Actions list.
661        """
662        self.env.disable_component(trac.search.web_ui.SearchModule)
663        rv, output = self.execute('permission list')
664        self.assertEqual(0, rv, output)
665        self.assertExpectedResult(output)
666
667    def test_permission_add_one_action_ok(self):
668        """
669        Tests the 'permission add' command in trac-admin.  This particular
670        test passes valid arguments to add one permission and checks for
671        success.
672        """
673        self.execute('permission add test_user WIKI_VIEW')
674        rv, output = self.execute('permission list')
675        self.assertEqual(0, rv, output)
676        self.assertExpectedResult(output)
677
678    def test_permission_add_multiple_actions_ok(self):
679        """
680        Tests the 'permission add' command in trac-admin.  This particular
681        test passes valid arguments to add multiple permissions and checks for
682        success.
683        """
684        self.execute('permission add test_user LOG_VIEW FILE_VIEW')
685        rv, output = self.execute('permission list')
686        self.assertEqual(0, rv, output)
687        self.assertExpectedResult(output)
688
689    def test_permission_add_already_exists(self):
690        """
691        Tests the 'permission add' command in trac-admin.  This particular
692        test passes a permission that already exists and checks for the
693        message. Other permissions passed are added.
694        """
695        rv, output = self.execute('permission add anonymous WIKI_CREATE '
696                                   'WIKI_VIEW WIKI_MODIFY')
697        self.assertEqual(0, rv, output)
698        rv, output2 = self.execute('permission list')
699        self.assertEqual(0, rv, output2)
700        self.assertExpectedResult(output + output2)
701
702    def test_permission_add_subject_already_in_group(self):
703        """
704        Tests the 'permission add' command in trac-admin.  This particular
705        test passes a group that the subject is already a member of and
706        checks for the message. Other permissions passed are added.
707        """
708        rv, output1 = self.execute('permission add user1 group2')
709        self.assertEqual(0, rv, output1)
710        rv, output2 = self.execute('permission add user1 group1 group2 '
711                                    'group3')
712        self.assertEqual(0, rv, output2)
713        rv, output3 = self.execute('permission list')
714        self.assertEqual(0, rv, output3)
715        self.assertExpectedResult(output2 + output3)
716
717    def test_permission_add_differs_from_action_by_casing(self):
718        """
719        Tests the 'permission add' command in trac-admin.  This particular
720        test passes a permission that differs from an action by casing and
721        checks for the message. None of the permissions in the list are
722        granted.
723        """
724        rv, output = self.execute('permission add joe WIKI_CREATE '
725                                   'Trac_Admin WIKI_MODIFY')
726        self.assertEqual(2, rv, output)
727        rv, output2 = self.execute('permission list')
728        self.assertEqual(0, rv, output2)
729        self.assertExpectedResult(output + output2)
730
731    def test_permission_add_unknown_action(self):
732        """
733        Tests the 'permission add' command in trac-admin.  This particular
734        test tries granting NOT_A_PERM to a user. NOT_A_PERM does not exist
735        in the system. None of the permissions in the list are granted.
736        """
737        rv, output = self.execute('permission add joe WIKI_CREATE '
738                                   'NOT_A_PERM WIKI_MODIFY')
739        self.assertEqual(2, rv, output)
740        rv, output2 = self.execute('permission list')
741        self.assertEqual(0, rv, output2)
742        self.assertExpectedResult(output + output2)
743
744    def test_permission_remove_one_action_ok(self):
745        """
746        Tests the 'permission remove' command in trac-admin.  This particular
747        test passes valid arguments to remove one permission and checks for
748        success.
749        """
750        self.execute('permission remove anonymous TICKET_MODIFY')
751        rv, output = self.execute('permission list')
752        self.assertEqual(0, rv, output)
753        self.assertExpectedResult(output)
754
755    def test_permission_remove_multiple_actions_ok(self):
756        """
757        Tests the 'permission remove' command in trac-admin.  This particular
758        test passes valid arguments to remove multiple permission and checks
759        for success.
760        """
761        self.execute('permission remove anonymous WIKI_CREATE WIKI_MODIFY')
762        rv, output = self.execute('permission list')
763        self.assertEqual(0, rv, output)
764        self.assertExpectedResult(output)
765
766    def test_permission_remove_all_actions_for_user(self):
767        """
768        Tests the 'permission remove' command in trac-admin.  This particular
769        test removes all permissions for anonymous.
770        """
771        self.execute('permission remove anonymous *')
772        rv, output = self.execute('permission list')
773        self.assertEqual(0, rv, output)
774        self.assertExpectedResult(output)
775
776    def test_permission_remove_action_for_all_users(self):
777        """
778        Tests the 'permission remove' command in trac-admin.  This particular
779        test removes the TICKET_CREATE permission from all users.
780        """
781        self.execute('permission add anonymous TICKET_CREATE')
782        self.execute('permission remove * TICKET_CREATE')
783        rv, output = self.execute('permission list')
784        self.assertEqual(0, rv, output)
785        self.assertExpectedResult(output)
786
787    def test_permission_remove_unknown_user(self):
788        """
789        Tests the 'permission remove' command in trac-admin.  This particular
790        test tries removing a permission from an unknown user.
791        """
792        rv, output = self.execute('permission remove joe TICKET_VIEW')
793        self.assertEqual(2, rv, output)
794        self.assertExpectedResult(output)
795
796    def test_permission_remove_action_not_granted(self):
797        """
798        Tests the 'permission remove' command in trac-admin.  This particular
799        test tries removing TICKET_CREATE from user anonymous, who doesn't
800        have that permission.
801        """
802        rv, output = self.execute('permission remove anonymous TICKET_CREATE')
803        self.assertEqual(2, rv, output)
804        self.assertExpectedResult(output)
805
806    def test_permission_remove_action_granted_through_meta_permission(self):
807        """
808        Tests the 'permission remove' command in trac-admin.  This particular
809        test tries removing WIKI_VIEW from a user. WIKI_VIEW has been granted
810        through user anonymous."""
811        self.execute('permission add joe TICKET_VIEW')
812        rv, output = self.execute('permission remove joe WIKI_VIEW')
813        self.assertEqual(2, rv, output)
814        self.assertExpectedResult(output)
815
816    def test_permission_remove_unknown_action(self):
817        """
818        Tests the 'permission remove' command in trac-admin.  This particular
819        test tries removing NOT_A_PERM from a user. NOT_A_PERM does not exist
820        in the system."""
821        rv, output = self.execute('permission remove joe NOT_A_PERM')
822        self.assertEqual(2, rv, output)
823        self.assertExpectedResult(output)
824
825    def test_permission_remove_unknown_action_granted(self):
826        """
827        Tests the 'permission remove' command in trac-admin.  This particular
828        test tries removing NOT_A_PERM from a user. NOT_A_PERM does not exist
829        in the system, but the user possesses the permission."""
830        self.env.db_transaction("""
831            INSERT INTO permission VALUES (%s, %s)
832        """, ('joe', 'NOT_A_PERM'))
833        rv, output = self.execute('permission remove joe NOT_A_PERM')
834        self.assertEqual(0, rv, output)
835        rv, output = self.execute('permission list')
836        self.assertEqual(0, rv, output)
837        self.assertExpectedResult(output)
838
839    def test_permission_export_ok(self):
840        """
841        Tests the 'permission export' command in trac-admin.  This particular
842        test exports the default permissions to stdout.
843        """
844        rv, output = self.execute('permission export')
845        self.assertEqual(0, rv, output)
846        self.assertExpectedResult(output)
847
848    def test_permission_import_ok(self):
849        """
850        Tests the 'permission import' command in trac-admin.  This particular
851        test exports additional permissions, removes them and imports them back.
852        """
853        user = 'test_user\u0250'
854        self.execute('permission add ' + user + ' WIKI_VIEW')
855        self.execute('permission add ' + user + ' TICKET_VIEW')
856        rv, output = self.execute('permission export')
857        self.execute('permission remove ' + user + ' *')
858        rv, output = self.execute('permission import', input=output)
859        self.assertEqual(0, rv, output)
860        self.assertEqual('', output)
861        rv, output = self.execute('permission list')
862        self.assertEqual(0, rv, output)
863        self.assertExpectedResult(output)
864
865
866def test_suite():
867    suite = unittest.TestSuite()
868    suite.addTest(unittest.makeSuite(DefaultPermissionStoreTestCase))
869    suite.addTest(unittest.makeSuite(PermissionErrorTestCase))
870    suite.addTest(unittest.makeSuite(PermissionSystemTestCase))
871    suite.addTest(unittest.makeSuite(PermissionCacheTestCase))
872    suite.addTest(unittest.makeSuite(PermissionPolicyTestCase))
873    suite.addTest(unittest.makeSuite(RecursivePolicyTestCase))
874    suite.addTest(unittest.makeSuite(TracAdminTestCase))
875    return suite
876
877
878if __name__ == '__main__':
879    unittest.main(defaultTest='test_suite')
880