Author: jure
Date: Mon Mar 18 09:46:02 2013
New Revision: 1457686

URL: http://svn.apache.org/r1457686
Log:
#438, implement and enforce product permission policy, patch 
t438_r1456016_product_perms.diff applied (from Olemis)


Added:
    
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/multiproduct/perm.py
Modified:
    
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/multiproduct/api.py
    
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/multiproduct/config.py
    
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/multiproduct/env.py
    
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/setup.py
    
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/tests/perm.py

Modified: 
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/multiproduct/api.py
URL: 
http://svn.apache.org/viewvc/incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/multiproduct/api.py?rev=1457686&r1=1457685&r2=1457686&view=diff
==============================================================================
--- 
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/multiproduct/api.py
 (original)
+++ 
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/multiproduct/api.py
 Mon Mar 18 09:46:02 2013
@@ -353,7 +353,12 @@ class MultiProductSystem(Component):
     def get_permission_actions(self):
         acts = ['PRODUCT_CREATE', 'PRODUCT_DELETE', 'PRODUCT_MODIFY',
                 'PRODUCT_VIEW']
-        return acts + [('PRODUCT_ADMIN', acts)] + [('ROADMAP_ADMIN', acts)]
+        if not isinstance(self.env, ProductEnvironment):
+            return acts + [('PRODUCT_ADMIN', acts)] + [('ROADMAP_ADMIN', acts)]
+        else:
+            # In product context PRODUCT_ADMIN will be provided by product env
+            # to ensure it will always be handy
+            return acts
 
     # ITicketFieldProvider methods
     def get_select_fields(self):

Modified: 
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/multiproduct/config.py
URL: 
http://svn.apache.org/viewvc/incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/multiproduct/config.py?rev=1457686&r1=1457685&r2=1457686&view=diff
==============================================================================
--- 
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/multiproduct/config.py
 (original)
+++ 
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/multiproduct/config.py
 Mon Mar 18 09:46:02 2013
@@ -22,12 +22,13 @@ __all__ = 'Configuration', 'Section'
 
 import os.path
 
-from trac.config import Configuration, ConfigurationError, Option, Section, \
-        _use_default
+from trac.config import Configuration, ConfigurationError, Option, \
+        OrderedExtensionsOption, Section, _use_default
 from trac.resource import ResourceNotFound
 from trac.util.text import to_unicode
 
 from multiproduct.model import ProductSetting
+from multiproduct.perm import MultiproductPermissionPolicy
 
 class Configuration(Configuration):
     """Product-aware settings repository equivalent to instances of
@@ -315,3 +316,21 @@ class Section(Section):
             path = os.path.join(env.path, 'conf', path)
         return os.path.normcase(os.path.realpath(path))
 
+#--------------------
+# Option override classes
+#--------------------
+
+class ProductPermissionPolicyOption(OrderedExtensionsOption):
+    """Prepend an instance of `multiproduct.perm.MultiproductPermissionPolicy`
+    """
+    def __get__(self, instance, owner):
+        # FIXME: Better handling of recursive imports
+        from multiproduct.env import ProductEnvironment
+
+        if instance is None:
+            return self
+        components = OrderedExtensionsOption.__get__(self, instance, owner)
+        env = getattr(instance, 'env', None)
+        return [MultiproductPermissionPolicy(env)] + components \
+               if isinstance(env, ProductEnvironment) \
+               else components

Modified: 
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/multiproduct/env.py
URL: 
http://svn.apache.org/viewvc/incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/multiproduct/env.py?rev=1457686&r1=1457685&r2=1457686&view=diff
==============================================================================
--- 
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/multiproduct/env.py
 (original)
+++ 
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/multiproduct/env.py
 Mon Mar 18 09:46:02 2013
@@ -16,16 +16,17 @@
 #  under the License.
 
 """Bloodhound product environment and related APIs"""
-from multiproduct.hooks import MultiProductEnvironmentFactory
 
 import os.path
 from urlparse import urlsplit
 from sqlite3 import OperationalError
 
 from trac.config import BoolOption, ConfigSection, Option
-from trac.core import Component, ComponentManager, ComponentMeta, \
-        ExtensionPoint, implements, Interface
-from trac.db.api import TransactionContextManager, QueryContextManager, 
DatabaseManager
+from trac.core import Component, ComponentManager, ExtensionPoint, implements, 
\
+                      ComponentMeta
+from trac.db.api import TransactionContextManager, QueryContextManager, \
+                        DatabaseManager
+from trac.perm import IPermissionRequestor, PermissionSystem
 from trac.util import get_pkginfo, lazy
 from trac.util.compat import sha1
 from trac.util.text import to_unicode, unicode_quote
@@ -35,7 +36,8 @@ from trac.web.href import Href
 from multiproduct.api import MultiProductSystem, 
ISupportMultiProductEnvironment
 from multiproduct.cache import lru_cache, default_keymap
 from multiproduct.config import Configuration
-from multiproduct.dbcursor import ProductEnvContextManager, 
BloodhoundConnectionWrapper, BloodhoundIterableCursor
+from multiproduct.dbcursor import BloodhoundConnectionWrapper, 
BloodhoundIterableCursor, \
+                                  ProductEnvContextManager
 from multiproduct.model import Product
 
 import trac.env
@@ -359,7 +361,7 @@ class ProductEnvironment(Component, Comp
 
         del product_env_keymap
 
-    implements(trac.env.ISystemInfoProvider)
+    implements(trac.env.ISystemInfoProvider, IPermissionRequestor)
 
     setup_participants = ExtensionPoint(trac.env.IEnvironmentSetupParticipant)
     multi_product_support_components = 
ExtensionPoint(ISupportMultiProductEnvironment)
@@ -559,6 +561,25 @@ class ProductEnvironment(Component, Comp
             os.makedirs(folder)
         return folder
 
+    # IPermissionRequestor methods
+    def get_permission_actions(self):
+        """Implement the product-specific `PRODUCT_ADMIN` meta permission.
+        """
+        actions = set()
+        permsys = PermissionSystem(self)
+        for requestor in permsys.requestors:
+            if requestor is not self and requestor is not permsys:
+                for action in requestor.get_permission_actions() or []:
+                    if isinstance(action, tuple):
+                        actions.add(action[0])
+                    else:
+                        actions.add(action)
+        # PermissionSystem's method was not invoked
+        actions.add('EMAIL_VIEW')
+        # FIXME: should not be needed, JIC better double check
+        actions.discard('TRAC_ADMIN')
+        return [('PRODUCT_ADMIN', list(actions))]
+
     # ISystemInfoProvider methods
 
     # Same as parent environment's . Avoid duplicated code
@@ -884,3 +905,8 @@ class ProductEnvironment(Component, Comp
 
 lookup_product_env = ProductEnvironment.lookup_env
 resolve_product_href = ProductEnvironment.resolve_href
+
+# Override product-specific options
+from multiproduct.config import ProductPermissionPolicyOption
+PermissionSystem.policies.__class__ = ProductPermissionPolicyOption
+

Added: 
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/multiproduct/perm.py
URL: 
http://svn.apache.org/viewvc/incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/multiproduct/perm.py?rev=1457686&view=auto
==============================================================================
--- 
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/multiproduct/perm.py
 (added)
+++ 
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/multiproduct/perm.py
 Mon Mar 18 09:46:02 2013
@@ -0,0 +1,49 @@
+
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing,
+#  software distributed under the License is distributed on an
+#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#  KIND, either express or implied.  See the License for the
+#  specific language governing permissions and limitations
+#  under the License.
+
+"""Permission components for Bloodhound product environments"""
+
+__all__ = 'ProductPermissionPolicy',
+
+from trac.core import Component, implements
+from trac.perm import IPermissionPolicy, PermissionSystem
+
+class MultiproductPermissionPolicy(Component):
+    """Apply product policy in product environments to deal with TRAC_ADMIN,
+    PRODUCT_ADMIN and alike.
+    """
+    implements(IPermissionPolicy)
+
+    # IPermissionPolicy methods
+    def check_permission(self, action, username, resource, perm):
+        # FIXME: Better handling of recursive imports
+        from multiproduct.env import ProductEnvironment
+
+        if isinstance(self.env, ProductEnvironment):
+            if action == 'TRAC_ADMIN':
+                # Always lookup TRAC_ADMIN permission in global scope
+                permsys = PermissionSystem(self.env.parent)
+                return bool(permsys.check_permission(action, username, 
+                                                resource, perm))
+            elif username == self.env.product.owner:
+                # Product owner granted with PRODUCT_ADMIN permission ootb
+                permsys = PermissionSystem(self.env)
+                # FIXME: would `action != 'TRAC_ADMIN'` be enough ?
+                return True if action in permsys.get_actions() and \
+                                action != 'TRAC_ADMIN' \
+                            else None

Modified: 
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/setup.py
URL: 
http://svn.apache.org/viewvc/incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/setup.py?rev=1457686&r1=1457685&r2=1457686&view=diff
==============================================================================
--- 
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/setup.py
 (original)
+++ 
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/setup.py
 Mon Mar 18 09:46:02 2013
@@ -30,6 +30,7 @@ setup(
     package_data = {'multiproduct' : ['templates/*.html',]},
     entry_points = {'trac.plugins': [
             'multiproduct.model = multiproduct.model',
+            'multiproduct.perm = multiproduct.perm',
             'multiproduct.product_admin = multiproduct.product_admin',
             'multiproduct.ticket.query = multiproduct.ticket.query',
             'multiproduct.ticket.web_ui = multiproduct.ticket.web_ui',

Modified: 
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/tests/perm.py
URL: 
http://svn.apache.org/viewvc/incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/tests/perm.py?rev=1457686&r1=1457685&r2=1457686&view=diff
==============================================================================
--- 
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/tests/perm.py
 (original)
+++ 
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/tests/perm.py
 Mon Mar 18 09:46:02 2013
@@ -21,10 +21,14 @@
 
 import unittest
 
+from trac.admin.api import AdminCommandError
 from trac import perm
-from trac.tests.perm import DefaultPermissionStoreTestCase
+from trac.tests.perm import DefaultPermissionStoreTestCase,\
+        PermissionSystemTestCase, PermissionCacheTestCase,\
+        PermissionPolicyTestCase, TestPermissionPolicy, TestPermissionRequestor
 
 from multiproduct.env import ProductEnvironment
+from multiproduct.perm import MultiproductPermissionPolicy
 from tests.env import MultiproductTestCase
 
 
@@ -74,9 +78,178 @@ class ProductDefaultPermissionStoreTestC
         self.assertEquals(['MILESTONE_VIEW', 'TICKET_CREATE'],
                           sorted(store1.get_user_permissions('john')))
 
+class ProductPermissionSystemTestCase(PermissionSystemTestCase,
+                                      MultiproductTestCase):
+    @property
+    def env(self):
+        env = getattr(self, '_env', None)
+        if env is None:
+            self.global_env = self._setup_test_env(enable=[
+                    perm.PermissionSystem,
+                    perm.DefaultPermissionStore,
+                    TestPermissionRequestor])
+            self._upgrade_mp(self.global_env)
+            self._setup_test_log(self.global_env)
+            self._load_product_from_data(self.global_env, self.default_product)
+            self._env = env = ProductEnvironment(
+                    self.global_env, self.default_product)
+        return env
+
+    @env.setter
+    def env(self, value):
+        pass
+
+    def test_all_permissions(self):
+        # PRODUCT_ADMIN meta-permission in product context
+        self.assertEqual({'EMAIL_VIEW': True, 'TRAC_ADMIN': True,
+                          'TEST_CREATE': True, 'TEST_DELETE': True,
+                          'TEST_MODIFY': True,  'TEST_ADMIN': True,
+                          'PRODUCT_ADMIN' : True},
+                         self.perm.get_user_permissions())
+
+    def test_expand_actions_iter_7467(self):
+        # Check that expand_actions works with iterators (#7467)
+        # PRODUCT_ADMIN meta-permission in product context
+        perms = set(['EMAIL_VIEW', 'TRAC_ADMIN', 'TEST_DELETE', 'TEST_MODIFY',
+                     'TEST_CREATE', 'TEST_ADMIN', 'PRODUCT_ADMIN'])
+        self.assertEqual(perms, self.perm.expand_actions(['TRAC_ADMIN']))
+        self.assertEqual(perms, self.perm.expand_actions(iter(['TRAC_ADMIN'])))
+
+
+class ProductPermissionCacheTestCase(PermissionCacheTestCase,
+                                      MultiproductTestCase):
+    @property
+    def env(self):
+        env = getattr(self, '_env', None)
+        if env is None:
+            self.global_env = self._setup_test_env(enable=[
+                    perm.DefaultPermissionStore,
+                    perm.DefaultPermissionPolicy,
+                    TestPermissionRequestor])
+            self._upgrade_mp(self.global_env)
+            self._setup_test_log(self.global_env)
+            self._load_product_from_data(self.global_env, self.default_product)
+            self._env = env = ProductEnvironment(
+                    self.global_env, self.default_product)
+        return env
+
+    @env.setter
+    def env(self, value):
+        pass
+
+
+class ProductPermissionPolicyTestCase(PermissionPolicyTestCase, 
+                                           MultiproductTestCase):
+    @property
+    def env(self):
+        env = getattr(self, '_env', None)
+        if env is None:
+            self.global_env = self._setup_test_env(enable=[
+                    perm.DefaultPermissionStore,
+                    perm.DefaultPermissionPolicy,
+                    perm.PermissionSystem,
+                    TestPermissionPolicy,
+                    TestPermissionRequestor,
+                    MultiproductPermissionPolicy])
+            self._upgrade_mp(self.global_env)
+            self._setup_test_log(self.global_env)
+            self._load_product_from_data(self.global_env, self.default_product)
+            self._env = env = ProductEnvironment(
+                    self.global_env, self.default_product)
+        return env
+
+    @env.setter
+    def env(self, value):
+        pass
+
+    def setUp(self):
+        super(ProductPermissionPolicyTestCase, self).setUp()
+
+        self.global_env.config.set('trac', 'permission_policies', 
+                                   'DefaultPermissionPolicy')
+        self.permsys = perm.PermissionSystem(self.env)
+        self.global_perm_admin = perm.PermissionAdmin(self.global_env)
+        self.product_perm_admin = perm.PermissionAdmin(self.env)
+
+    def tearDown(self):
+        self.global_env.reset_db()
+        self.global_env = self.env = None
+
+    def test_prepend_mp_policy(self):
+        self.assertEqual([MultiproductPermissionPolicy(self.env), self.policy],
+                         self.permsys.policies)
+
+    def test_policy_chaining(self):
+        self.env.config.set('trac', 'permission_policies', 
+                            'TestPermissionPolicy,DefaultPermissionPolicy')
+        self.policy.grant('testuser', ['TEST_MODIFY'])
+        system = perm.PermissionSystem(self.env)
+        system.grant_permission('testuser', 'TEST_ADMIN')
+
+        self.assertEqual(list(system.policies),
+                         [MultiproductPermissionPolicy(self.env),
+                          self.policy,
+                          perm.DefaultPermissionPolicy(self.env)])
+        self.assertEqual('TEST_MODIFY' in self.perm, True)
+        self.assertEqual('TEST_ADMIN' in self.perm, True)
+        self.assertEqual(self.policy.results,
+                         {('testuser', 'TEST_MODIFY'): True,
+                          ('testuser', 'TEST_ADMIN'): None})
+
+    def test_product_trac_admin_success(self):
+        """TRAC_ADMIN in global env also valid in product env
+        """
+        self.global_perm_admin._do_add('testuser', 'TRAC_ADMIN')
+        self.assertTrue(self.perm.has_permission('TRAC_ADMIN'))
+
+    def test_product_trac_admin_fail_local(self):
+        """TRAC_ADMIN granted in product env will be ignored
+        """
+        try:
+            # Not needed but added just in case , also for readability
+            self.global_perm_admin._do_remove('testuser', 'TRAC_ADMIN')
+        except AdminCommandError:
+            pass
+
+        # Setting TRAC_ADMIN permission in product scope is in vain
+        # since it controls access to critical actions affecting the whole site
+        # This will protect the system against malicious actors
+        # and / or failures leading to the addition of TRAC_ADMIN permission 
+        # in product perm store in spite of obtaining unrighteous super powers.
+        # On the other hand this also means that PRODUCT_ADMIN(s) are 
+        # able to set user permissions at will without jeopardizing system
+        # integrity and stability.
+        self.product_perm_admin._do_add('testuser', 'TRAC_ADMIN')
+        self.assertFalse(self.perm.has_permission('TRAC_ADMIN'))
+
+    def test_product_owner_perm(self):
+        """Product owner automatically granted with PRODUCT_ADMIN
+        """
+        self.assertIs(self.env.product.owner, None)
+        self.assertFalse(self.perm.has_permission('PRODUCT_ADMIN'))
+
+        self.env.product.owner = 'testuser'
+        # FIXME: update really needed ?
+        self.env.product.update()
+        try:
+            # Not needed but added just in case , also for readability
+            self.global_perm_admin._do_remove('testuser', 'TRAC_ADMIN')
+        except AdminCommandError:
+            pass
+        self.perm._cache.clear()
+
+        self.assertTrue(self.perm.has_permission('PRODUCT_ADMIN'))
+        self.assertFalse(self.perm.has_permission('TRAC_ADMIN'))
+
 
 def test_suite():
-    return unittest.makeSuite(ProductDefaultPermissionStoreTestCase,'test')
+    suite = unittest.TestSuite()
+    suite.addTest(unittest.makeSuite(ProductDefaultPermissionStoreTestCase, 
+                                     'test'))
+    suite.addTest(unittest.makeSuite(ProductPermissionSystemTestCase, 'test'))
+    suite.addTest(unittest.makeSuite(ProductPermissionCacheTestCase, 'test'))
+    suite.addTest(unittest.makeSuite(ProductPermissionPolicyTestCase, 'test'))
+    return suite
 
 if __name__ == '__main__':
     unittest.main(defaultTest='test_suite')


Reply via email to