[Checkins] SVN: PluggableAuthService/branches/tseaver-pluggable_allowed/ First pass at making PropertiedUser.allowed pluggable (no local roles plugin yet).

Tres Seaver tseaver at palladion.com
Thu Aug 3 17:27:52 EDT 2006


Log message for revision 69344:
  First pass at making PropertiedUser.allowed pluggable (no local roles plugin yet).

Changed:
  U   PluggableAuthService/branches/tseaver-pluggable_allowed/PluggableAuthService.py
  U   PluggableAuthService/branches/tseaver-pluggable_allowed/PropertiedUser.py
  U   PluggableAuthService/branches/tseaver-pluggable_allowed/interfaces/plugins.py
  A   PluggableAuthService/branches/tseaver-pluggable_allowed/plugins/UserAllowedPolicies.py
  A   PluggableAuthService/branches/tseaver-pluggable_allowed/plugins/tests/test_UserAllowedPolicies.py
  U   PluggableAuthService/branches/tseaver-pluggable_allowed/tests/conformance.py
  U   PluggableAuthService/branches/tseaver-pluggable_allowed/tests/test_PropertiedUser.py

-=-
Modified: PluggableAuthService/branches/tseaver-pluggable_allowed/PluggableAuthService.py
===================================================================
--- PluggableAuthService/branches/tseaver-pluggable_allowed/PluggableAuthService.py	2006-08-03 18:35:05 UTC (rev 69343)
+++ PluggableAuthService/branches/tseaver-pluggable_allowed/PluggableAuthService.py	2006-08-03 21:27:52 UTC (rev 69344)
@@ -79,6 +79,7 @@
 from interfaces.plugins import IRoleAssignerPlugin
 from interfaces.plugins import IChallengeProtocolChooser
 from interfaces.plugins import IRequestTypeSniffer
+from interfaces.plugins import IUserAllowedPolicyPlugin
 
 from permissions import SearchPrincipals
 
@@ -1243,6 +1244,11 @@
     , 'request_type_sniffer'
     , "Request Type Sniffer plugins detect the type of an incoming request."
     )
+  , ( IUserAllowedPolicyPlugin
+    , 'IUserAllowedPolicyPlugin'
+    , 'user_allowed_policy'
+    , "Apply a policy for granting access to a user given the required roles."
+    )
   )
 
 def addPluggableAuthService( dispatcher

Modified: PluggableAuthService/branches/tseaver-pluggable_allowed/PropertiedUser.py
===================================================================
--- PluggableAuthService/branches/tseaver-pluggable_allowed/PropertiedUser.py	2006-08-03 18:35:05 UTC (rev 69343)
+++ PluggableAuthService/branches/tseaver-pluggable_allowed/PropertiedUser.py	2006-08-03 21:27:52 UTC (rev 69344)
@@ -22,6 +22,7 @@
 from AccessControl.PermissionRole import _what_not_even_god_should_do
 
 from interfaces.authservice import IPropertiedUser
+from interfaces.plugins import IUserAllowedPolicyPlugin
 from UserPropertySheet import UserPropertySheet
 from utils import classImplements
 
@@ -149,9 +150,29 @@
         o Ripped off from AccessControl.User.BasicUser, which provides
           no other extension mechanism. :(
         """
+        plugins = aq_parent( self )._getOb( 'plugins' )
+
+        # This isn't really a policy -- everything in Zope expects that
+        # 'roles = ()' means "VERBOTEN".
         if object_roles is _what_not_even_god_should_do:
             return 0
 
+        policies = plugins.listPlugins( IUserAllowedPolicyPlugin )
+
+        if len(policies) == 0:
+            return self._default_allow_policies( object, object_roles )
+
+        for policy_id, policy in policies:
+            result = policy.isUserAllowed( self, object, object_roles )
+            if result is None:
+                continue
+            return result
+
+        return None
+
+    def _default_allow_policies( self, object, object_roles ):
+        """ These policies reflect those from the traditional Zope acl_users.
+        """
         # Short-circuit the common case of anonymous access.
         if object_roles is None or 'Anonymous' in object_roles:
             return 1

Modified: PluggableAuthService/branches/tseaver-pluggable_allowed/interfaces/plugins.py
===================================================================
--- PluggableAuthService/branches/tseaver-pluggable_allowed/interfaces/plugins.py	2006-08-03 18:35:05 UTC (rev 69343)
+++ PluggableAuthService/branches/tseaver-pluggable_allowed/interfaces/plugins.py	2006-08-03 21:27:52 UTC (rev 69344)
@@ -432,7 +432,8 @@
 
 class IRequestTypeSniffer( Interface ):
 
-    """ Given a request, detects the request type for later use by other plugins.
+    """ Given a request, detects the request type for later use by other
+        plugins.
     """
     def sniffRequestType( request ):
         """ Return a interface identifying what kind the request is.
@@ -455,8 +456,15 @@
         o Once the protocol is decided, all challenge plugins for that
             protocol will be executed.
         """
-#
-#   XXX:  Do we need a LocalRoleAlgorithm plugin type?  E.g., base_cms
-#         has two different algorithms, based on whether or not the
-#         context object implements IPlacelessSecurity.
-#
+
+class IUserAllowedPolicyPlugin( Interface ):
+
+    """ Pluggable policy for testing user access given required roles.
+    """
+    def isUserAllowed( user, object, object_roles ):
+
+        """ Return a boolean whether the user is allowed to access the object.
+
+        o Return None to indicate that this plugin cannot determine the
+          answer either way.
+        """

Added: PluggableAuthService/branches/tseaver-pluggable_allowed/plugins/UserAllowedPolicies.py
===================================================================
--- PluggableAuthService/branches/tseaver-pluggable_allowed/plugins/UserAllowedPolicies.py	2006-08-03 18:35:05 UTC (rev 69343)
+++ PluggableAuthService/branches/tseaver-pluggable_allowed/plugins/UserAllowedPolicies.py	2006-08-03 21:27:52 UTC (rev 69344)
@@ -0,0 +1,81 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation and Contributors. All Rights
+# Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this
+# distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+""" Policies for plugging into 'PropertiedUser.allowed()'
+
+$Id$
+"""
+from AccessControl.SecurityInfo import ClassSecurityInfo
+from App.class_init import default__class_init__ as InitializeClass
+
+from Products.PluggableAuthService.interfaces.plugins \
+    import IUserAllowedPolicyPlugin
+from Products.PluggableAuthService.plugins.BasePlugin import BasePlugin
+from Products.PluggableAuthService.utils import classImplements
+
+class ShortcutAllowPolicy( BasePlugin ):
+
+    """ Plugin for traditional "shortcut" allow policy.
+    """
+    meta_type = 'Shortcut Allow Policy'
+
+    security = ClassSecurityInfo()
+
+    security.declarePrivate( 'isUserAllowed' )
+    def isUserAllowed( self, user, object, object_roles ):
+        """ See IUserAllowedPolicyPlugin.
+        """
+        # Short-circuit the common case of anonymous access.
+        if object_roles is None or 'Anonymous' in object_roles:
+            return True
+
+        # Provide short-cut access if object is protected by 'Authenticated'
+        # role and user is not nobody
+        if 'Authenticated' in object_roles and (
+            user.getUserName() != 'Anonymous User'):
+            return True
+
+        return None # pass
+
+classImplements( ShortcutAllowPolicy
+               , IUserAllowedPolicyPlugin
+               )
+
+InitializeClass( ShortcutAllowPolicy )
+
+class GlobalRolesAllowPolicy( BasePlugin ):
+
+    """ Plugin for traditional allow policy using global roles.
+    """
+    meta_type = 'Global Roles Allow Policy'
+
+    security = ClassSecurityInfo()
+
+    security.declarePrivate( 'isUserAllowed' )
+    def isUserAllowed( self, user, object, object_roles ):
+        """ See IUserAllowedPolicyPlugin.
+        """
+        user_roles = user.getRoles()
+
+        for role in object_roles:
+            if role in user_roles:
+                return user._check_context(object)
+
+        return None # pass
+
+classImplements( GlobalRolesAllowPolicy
+               , IUserAllowedPolicyPlugin
+               )
+
+InitializeClass( GlobalRolesAllowPolicy )


Property changes on: PluggableAuthService/branches/tseaver-pluggable_allowed/plugins/UserAllowedPolicies.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: PluggableAuthService/branches/tseaver-pluggable_allowed/plugins/tests/test_UserAllowedPolicies.py
===================================================================
--- PluggableAuthService/branches/tseaver-pluggable_allowed/plugins/tests/test_UserAllowedPolicies.py	2006-08-03 18:35:05 UTC (rev 69343)
+++ PluggableAuthService/branches/tseaver-pluggable_allowed/plugins/tests/test_UserAllowedPolicies.py	2006-08-03 21:27:52 UTC (rev 69344)
@@ -0,0 +1,138 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation and Contributors. All Rights
+# Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this
+# distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+""" Unit tests for UserAllowedPolicies
+
+$Id$
+"""
+import unittest
+
+from Products.PluggableAuthService.tests.conformance \
+    import IUserAllowedPolicyPlugin_conformance
+
+class PolicyTestBase( unittest.TestCase
+                    , IUserAllowedPolicyPlugin_conformance
+                    ):
+
+    # must supply '_getTargetClass'
+
+    def _makeOne( self, *args, **kw ):
+        return self._getTargetClass()( *args, **kw )
+
+class ShortcutAllowPolicyTests( PolicyTestBase ):
+
+    def _getTargetClass( self ):
+        from Products.PluggableAuthService.plugins.UserAllowedPolicies \
+            import ShortcutAllowPolicy
+        return ShortcutAllowPolicy
+    
+    def test_pass_wo_anonymous_or_authenticated_in_roles( self ):
+        plugin = self._makeOne()
+        user = DummyUser()
+        obj = object()
+        obj_roles = ( 'Role A', )
+        allowed = plugin.isUserAllowed( user, obj, obj_roles )
+        self.assertEqual(allowed, None)
+    
+    def test_allows_for_public( self ):
+        plugin = self._makeOne()
+        user = DummyUser()
+        obj = object()
+        obj_roles = None # classic Zope "public"
+        allowed = plugin.isUserAllowed( user, obj, obj_roles )
+        self.failUnless(allowed)
+    
+    def test_allows_for_Anonymous_in_roles( self ):
+        plugin = self._makeOne()
+        user = DummyUser()
+        obj = object()
+        obj_roles = ( 'Anonymous', )
+        allowed = plugin.isUserAllowed( user, obj, obj_roles )
+        self.failUnless(allowed)
+    
+    def test_allows_for_Authenticated_in_roles_not_anonymous( self ):
+        plugin = self._makeOne()
+        user = DummyUser()
+        obj = object()
+        obj_roles = ( 'Authenticated', )
+        allowed = plugin.isUserAllowed( user, obj, obj_roles )
+        self.failUnless(allowed)
+    
+    def test_pass_for_Authenticated_in_roles_and_anonymous( self ):
+        plugin = self._makeOne()
+        user = DummyUser( 'Anonymous User' )
+        obj = object()
+        obj_roles = ( 'Authenticated', )
+        allowed = plugin.isUserAllowed( user, obj, obj_roles )
+        self.assertEqual(allowed, None)
+
+class GlobalRolesAllowPolicyTests( PolicyTestBase ):
+
+    def _getTargetClass( self ):
+        from Products.PluggableAuthService.plugins.UserAllowedPolicies \
+            import GlobalRolesAllowPolicy
+        return GlobalRolesAllowPolicy
+    
+    def test_pass_for_no_match_in_roles( self ):
+        plugin = self._makeOne()
+        user = DummyUser()
+        obj = object()
+        obj_roles = ( 'Role A', )
+        allowed = plugin.isUserAllowed( user, obj, obj_roles )
+        self.assertEqual(allowed, None)
+    
+    def test_allow_for_match_in_roles_and_in_context( self ):
+        plugin = self._makeOne()
+        user = DummyUser( roles=( 'Role B', ), in_context=True )
+        obj = object()
+        obj_roles = ( 'Role A', 'Role B', 'Role C' )
+        allowed = plugin.isUserAllowed( user, obj, obj_roles )
+        self.failUnless(allowed)
+    
+    def test_disallow_for_match_in_roles( self ):
+        plugin = self._makeOne()
+        user = DummyUser( roles=( 'Role B', ), in_context=False )
+        obj = object()
+        obj_roles = ( 'Role A', 'Role B', 'Role C' )
+        allowed = plugin.isUserAllowed( user, obj, obj_roles )
+        self.failIf(allowed)
+
+class DummyUser:
+    def __init__( self, name='Dummy User', roles=(), in_context=True
+                , shared_roles=None ):
+        self.name = name
+        self.roles = roles
+        self.in_context = in_context
+        self.shared_roles = shared_roles
+
+    def getUserName( self ):
+        return self.name
+
+    def getRoles( self ):
+        return self.roles
+
+    def _check_context( self, object ):
+        return self.in_context
+
+    def _shared_roles( self, object ):
+        return self.shared_roles
+
+def test_suite():
+    return unittest.TestSuite((
+        unittest.makeSuite( ShortcutAllowPolicyTests ),
+        unittest.makeSuite( GlobalRolesAllowPolicyTests ),
+        ))
+
+if __name__ == "__main__":
+    unittest.main(defaultTest='test_suite')


Property changes on: PluggableAuthService/branches/tseaver-pluggable_allowed/plugins/tests/test_UserAllowedPolicies.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Modified: PluggableAuthService/branches/tseaver-pluggable_allowed/tests/conformance.py
===================================================================
--- PluggableAuthService/branches/tseaver-pluggable_allowed/tests/conformance.py	2006-08-03 18:35:05 UTC (rev 69343)
+++ PluggableAuthService/branches/tseaver-pluggable_allowed/tests/conformance.py	2006-08-03 21:27:52 UTC (rev 69344)
@@ -174,6 +174,15 @@
 
         verifyClass( IRequestTypeSniffer, self._getTargetClass() )
 
+class IUserAllowedPolicyPlugin_conformance:
+
+    def test_UserAllowedPolicyPlugin_conformance( self ):
+
+        from Products.PluggableAuthService.interfaces.plugins \
+            import IUserAllowedPolicyPlugin
+
+        verifyClass( IUserAllowedPolicyPlugin, self._getTargetClass() )
+
 class IUserFolder_conformance:
 
     def test_conformance_IUserFolder( self ):

Modified: PluggableAuthService/branches/tseaver-pluggable_allowed/tests/test_PropertiedUser.py
===================================================================
--- PluggableAuthService/branches/tseaver-pluggable_allowed/tests/test_PropertiedUser.py	2006-08-03 18:35:05 UTC (rev 69343)
+++ PluggableAuthService/branches/tseaver-pluggable_allowed/tests/test_PropertiedUser.py	2006-08-03 21:27:52 UTC (rev 69344)
@@ -31,6 +31,25 @@
 
         self.__ac_local_roles__ = local_roles
 
+class FauxPluginRegistry( Implicit ):
+
+    def __init__( self, plugins=() ):
+        self.plugins = plugins
+
+    def listPlugins( self, iface ):
+        return [ ( 'plugin_%02d' % i, self.plugins[ i ] )
+                  for i in range( len( self.plugins ) ) ]
+
+class FauxPAS( Implicit ):
+
+    def __init__( self, plugins ):
+
+        self.plugins = FauxPluginRegistry( plugins )
+
+    def _getOb( self, id ):
+
+        return getattr( self, id )
+
 class PropertiedUserTests( unittest.TestCase
                            , IBasicUser_conformance
                            , IPropertiedUser_conformance
@@ -47,6 +66,9 @@
 
         return self._getTargetClass()( id, login, *args, **kw )
 
+    def _makePAS( self, *plugins ):
+        return FauxPAS( plugins )
+
     def test_empty( self ):
 
         user = self._makeOne( 'empty' )
@@ -191,71 +213,81 @@
         self.assertEqual( len( local_roles ), 1 )
         self.failUnless( 'Manager' in local_roles )
 
-    def test_allowed_not_even_god_should( self ):
+    def test_allowed_not_even_god_should_do_no_plugins( self ):
 
         from AccessControl.PermissionRole import _what_not_even_god_should_do
-        user = self._makeOne()
+        pas = self._makePAS()
+        user = self._makeOne().__of__(pas)
 
         self.failIf( user.allowed( None, _what_not_even_god_should_do ) )
 
-    def test_allowed_anonymous( self ):
+    def test_allowed_anonymous_no_plugins( self ):
 
-        user = self._makeOne()
+        pas = self._makePAS()
+        user = self._makeOne().__of__(pas)
 
         self.failUnless( user.allowed( None, ('Anonymous',) ) )
 
-    def test_allowed_authenticated( self ):
+    def test_allowed_authenticated_no_plugins( self ):
 
-        user = self._makeOne()
+        pas = self._makePAS()
+        user = self._makeOne().__of__(pas)
 
         self.failUnless( user.allowed( None, ('Authenticated',) ) )
 
-    def test_allowed_authenticated_required_but_anonymous( self ):
+    def test_allowed_authenticated_required_but_anonymous_no_plugins( self ):
 
-        user = self._makeOne('Anonymous User')
+        pas = self._makePAS()
+        user = self._makeOne('Anonymous User').__of__(pas)
 
         self.failIf( user.allowed( None, ('Authenticated',) ) )
 
-    def test_allowed_global_roles_ok( self ):
+    def test_allowed_global_roles_ok_no_plugins( self ):
 
-        user = self._makeOne()
+        pas = self._makePAS()
+        user = self._makeOne().__of__(pas)
         user._addRoles( ( 'Role 1', 'Role 2' ) )
 
         self.failUnless( user.allowed( None, ( 'Role 1', ) ) )
 
-    def test_allowed_global_roles_not_ok( self ):
+    def test_allowed_global_roles_not_ok_no_plugins( self ):
 
-        user = self._makeOne()
+        pas = self._makePAS()
+        user = self._makeOne().__of__(pas)
         user._addRoles( ( 'Role 1', 'Role 2' ) )
 
         self.failIf( user.allowed( None, ( 'Role 3', ) ) )
 
-    def test_allowed_local_roles_on_user_ok( self ):
+    def test_allowed_local_roles_on_user_ok_no_plugins( self ):
 
-        user = self._makeOne( 'user' )
+        pas = self._makePAS()
+        user = self._makeOne( 'user' ).__of__(pas)
         object = FauxProtected( { 'user' : ( 'Role 1', ) } )
 
         self.failUnless( user.allowed( object, ( 'Role 1', ) ) )
 
-    def test_allowed_local_roles_on_user_not_ok( self ):
+    def test_allowed_local_roles_on_user_not_ok_no_plugins( self ):
 
-        user = self._makeOne( 'user' )
+        pas = self._makePAS()
+        user = self._makeOne( 'user' ).__of__(pas)
         object = FauxProtected( { 'user' : ( 'Role 1', ) } )
 
         self.failIf( user.allowed( object, ( 'Role 2', ) ) )
 
-    def test_allowed_local_roles_on_group_ok( self ):
+    def test_allowed_local_roles_on_group_ok_no_plugins( self ):
 
-        user = self._makeOne( 'user' )
+        pas = self._makePAS()
+        user = self._makeOne( 'user' ).__of__(pas)
         user._addGroups( ( 'Group 1', 'Group 2' ) )
         object = FauxProtected( { 'Group 1' : ( 'Role 1', ) } )
 
         self.failUnless( user.allowed( object, ( 'Role 1', ) ) )
 
-    def test_allowed_acquisition( self ):
+    def test_allowed_acquisition_no_plugins( self ):
 
         groups = ( 'Group A', 'Group B' )
-        user = self._makeOne()
+        pas = self._makePAS()
+        user = self._makeOne().__of__(pas)
         user._addGroups( groups )
 
         faux_container = FauxProtected( { 'Group A' : ( 'Manager', ) } )
@@ -264,12 +296,13 @@
 
         self.failUnless( user.allowed( faux_contained, ( 'Manager', ) ) )
 
-    def test_allowed_weslayan( self ):
+    def test_allowed_weslayan_no_plugins( self ):
 
         # Test "methodish" checks.
 
         groups = ( 'Group A', 'Group B' )
-        user = self._makeOne()
+        pas = self._makePAS()
+        user = self._makeOne().__of__(pas)
         user._addGroups( groups )
 
         faux_self = FauxProtected( { 'Group A' : ( 'Manager', ) } )
@@ -278,7 +311,28 @@
 
         self.failUnless( user.allowed( faux_method, ( 'Manager', ) ) )
 
+    def test_allowed_not_even_god_should_do_even_with_plugins( self ):
 
+        from AccessControl.PermissionRole import _what_not_even_god_should_do
+        pas = self._makePAS( allowAnything )
+        user = self._makeOne().__of__(pas)
+
+        self.failIf( user.allowed( None, _what_not_even_god_should_do ) )
+
+    def test_allowed_global_roles_not_ok_with_allowAnything_plugin( self ):
+
+        pas = self._makePAS( allowAnything )
+        user = self._makeOne().__of__(pas)
+        user._addRoles( ( 'Role 1', 'Role 2' ) )
+
+        self.failUnless( user.allowed( None, ( 'Role 3', ) ) )
+
+class AllowAnything:
+    def isUserAllowed( self, user, object, object_rules ):
+        return True
+
+allowAnything = AllowAnything()
+
 if __name__ == "__main__":
     unittest.main()
 



More information about the Checkins mailing list