[Zope-CVS] CVS: Products/PluggableAuthService/plugins/tests - ChallengeProtocolChooser.txt:1.4 test_ChallengeProtocolChooser.py:1.4 test_RequestTypeSniffer.py:1.4 test_doctests.py:1.4

Sidnei da Silva sidnei at enfoldsystems.com
Wed Aug 17 16:53:45 EDT 2005


Update of /cvs-repository/Products/PluggableAuthService/plugins/tests
In directory cvs.zope.org:/tmp/cvs-serv13697/plugins/tests

Added Files:
	ChallengeProtocolChooser.txt test_ChallengeProtocolChooser.py 
	test_RequestTypeSniffer.py test_doctests.py 
Log Message:

Merge changes from sidnei-challenge-protocol-chooser:

    - Added two new interfaces, IChallengeProtocolChooser and
      IRequestTypeSniffer. Those are used to select the 'authorization
      protocol' or 'challenger protocol' to be used for challenging
      according to the incoming request type.

    - Fixed a couple more places where Zope 2-style __implements__
      where being used to standardize on using classImplements.

    - Fixed fallback implementations of providedBy and
      implementedBy to always return a tuple.

    - Make sure challenge doesn't break if existing instances of the
      PluginRegistry don't yet have IChallengeProtocolChooser as a
      registered interface. (Would be nice to have some sort of
      migration for the PluginRegistry between PAS releases)

    - Don't assume that just because zope.interface can be imported
      that Five is present.


=== Products/PluggableAuthService/plugins/tests/ChallengeProtocolChooser.txt 1.3 => 1.4 ===
--- /dev/null	Wed Aug 17 16:53:44 2005
+++ Products/PluggableAuthService/plugins/tests/ChallengeProtocolChooser.txt	Wed Aug 17 16:53:14 2005
@@ -0,0 +1,268 @@
+Challenge Protocol Chooser
+--------------------------
+
+The Challenge Protocol Chooser is a plugin that decides what
+authentication protocol to use for a given request type.
+
+Let's start by setting up a PAS instance inside our existing test
+folder.
+
+  >>> folder = self.folder
+
+  >>> 'acl_users' in folder.objectIds()
+  True
+
+  >>> folder.manage_delObjects(ids=['acl_users'])
+
+  >>> 'acl_users' in folder.objectIds()
+  False
+
+  >>> dispatcher = folder.manage_addProduct['PluggableAuthService']
+  >>> dispatcher.addPluggableAuthService()
+
+  >>> 'acl_users' in folder.objectIds()
+  True
+
+  >>> folder.acl_users.meta_type
+  'Pluggable Auth Service'
+
+Now, we'll setup this PAS instance with what most people would get by
+default, users and roles stored in ZODB with HTTP Basic auth.
+
+  >>> pas = folder.acl_users
+
+  >>> dispatcher = pas.manage_addProduct['PluggableAuthService']
+
+  >>> dispatcher.addZODBUserManager('users')
+  >>> dispatcher.addZODBRoleManager('roles')
+  >>> dispatcher.addHTTPBasicAuthHelper('http_auth')
+
+  >>> plugins = pas.plugins
+
+  >>> from Products.PluggableAuthService.interfaces.plugins import \
+  ...   IAuthenticationPlugin, IUserEnumerationPlugin, IRolesPlugin, \
+  ...	IRoleEnumerationPlugin, IRoleAssignerPlugin, \
+  ...   IChallengePlugin, IExtractionPlugin, IUserAdderPlugin
+
+  >>> plugins.activatePlugin(IUserAdderPlugin, 'users')
+  >>> plugins.activatePlugin(IAuthenticationPlugin, 'users')
+  >>> plugins.activatePlugin(IUserEnumerationPlugin, 'users')
+  >>> plugins.activatePlugin(IRolesPlugin, 'roles')
+  >>> plugins.activatePlugin(IRoleEnumerationPlugin, 'roles')
+  >>> plugins.activatePlugin(IRoleAssignerPlugin, 'roles')
+  >>> plugins.activatePlugin(IExtractionPlugin, 'http_auth')
+  >>> plugins.activatePlugin(IChallengePlugin, 'http_auth')
+
+Create a user for testing:
+
+  >>> pas.getUserById('test_user_') is None
+  True
+
+  >>> username, password  = 'test_user_', 'test_user_pw'
+  >>> pas._doAddUser(username, password, ['Manager'], [])
+
+  >>> pas.getUserById('test_user_') is None
+  False
+
+We are now going to try some different kinds of requests and make sure
+all of them work. They all use HTTP Basic Auth, which is the default
+in this configuration we just set up. For the sake of testing, we are
+going to create a simple script that requires the 'Manager' role to be
+called.
+
+  >>> folder_name = folder.getId()
+  >>> dispatcher = folder.manage_addProduct['PythonScripts']
+  >>> _ = dispatcher.manage_addPythonScript('test_script')
+
+  >>> script = folder._getOb('test_script')
+  >>> script.write('return "Access Granted"')
+  >>> script.manage_permission(permission_to_manage='View',
+  ...                          roles=['Manager'], acquire=0)
+
+Access the script through a simple ``GET`` request, simulating browser
+access. Anonymous user should be challenged with a 401 response
+status.
+
+  >>> print http(r"""
+  ... GET /%s/test_script HTTP/1.1
+  ... """ % (folder_name), handle_errors=False)
+  HTTP/1.1 401 Unauthorized...
+
+With the right credentials though the request should succeed:
+
+  >>> print http(r"""
+  ... GET /%s/test_script HTTP/1.1
+  ... Authorization: Basic %s:%s
+  ... """ % (folder_name, username, password), handle_errors=False)
+  HTTP/1.1 200 OK
+  ...
+  Access Granted
+
+Now a PROPFIND request, simulating a WebDAV client. Anonymous user
+should be challenged with a 401 response status:
+
+  >>> print http(r"""
+  ... PROPFIND /%s/test_script HTTP/1.1
+  ... """ % (folder_name), handle_errors=False)
+  HTTP/1.1 401 Unauthorized...
+
+And with the right credentials the request should succeed:
+
+  >>> print http(r"""
+  ... PROPFIND /%s/test_script HTTP/1.1
+  ... Authorization: Basic %s:%s
+  ... """ % (folder_name, username, password), handle_errors=False)
+  HTTP/1.1 207 Multi-Status...
+
+  >>> print http(r"""
+  ... GET /%s/test_script/manage_DAVget HTTP/1.1
+  ... Authorization: Basic %s:%s
+  ... """ % (folder_name, username, password), handle_errors=False)
+  HTTP/1.1 200 OK...
+
+And a XML-RPC Request. Again, Anonymous user should be challenged with
+a 401 response status.
+
+  >>> print http(r"""
+  ... POST /%s HTTP/1.1
+  ... Content-Type: text/xml; charset="utf-8"
+  ... <?xml version="1.0"?>
+  ... <methodCall>
+  ... <methodName>test_script</methodName>
+  ... </methodCall>
+  ... """ % (folder_name), handle_errors=False)
+  HTTP/1.1 401 Unauthorized...
+
+And with valid credentials the reqeuest should succeed:
+
+  >>> print http(r"""
+  ... POST /%s HTTP/1.1
+  ... Content-Type: text/xml; charset="utf-8"
+  ... Authorization: Basic %s:%s
+  ... <?xml version="1.0"?>
+  ... <methodCall>
+  ... <methodName>test_script</methodName>
+  ... </methodCall>
+  ... """ % (folder_name, username, password), handle_errors=False)
+  HTTP/1.1 200 OK
+  Content-Length: 140
+  Content-Type: text/xml
+  <BLANKLINE>
+  <?xml version='1.0'?>
+  <methodResponse>
+  <params>
+  <param>
+  <value><string>Access Granted</string></value>
+  </param>
+  </params>
+  </methodResponse>
+
+Adding a Cookie Auth Helper now to test the correct behaviour of the
+Challenge Protocol Helper.
+
+  >>> dispatcher = pas.manage_addProduct['PluggableAuthService']
+  >>> dispatcher.addCookieAuthHelper('cookie_auth',
+  ...                                cookie_name='__ac')
+
+  >>> plugins.activatePlugin(IExtractionPlugin, 'cookie_auth')
+  >>> plugins.activatePlugin(IChallengePlugin, 'cookie_auth')
+
+Re-activate HTTP Auth Helper so that it appears **after** Cookie Auth
+Helper:
+
+  >>> plugins.deactivatePlugin(IExtractionPlugin, 'http_auth')
+  >>> plugins.activatePlugin(IExtractionPlugin, 'http_auth')
+  >>> plugins.deactivatePlugin(IChallengePlugin, 'http_auth')
+  >>> plugins.activatePlugin(IChallengePlugin, 'http_auth')
+
+Now, invalid credentials should result in a 302 response status for a
+normal (eg: browser) request:
+
+  >>> print http(r"""
+  ... GET /%s/test_script HTTP/1.1
+  ... """ % (folder_name), handle_errors=False)
+  HTTP/1.1 302 Moved Temporarily...
+
+And the same for a WebDAV request:
+
+  >>> print http(r"""
+  ... PROPFIND /%s/test_script HTTP/1.1
+  ... """ % (folder_name), handle_errors=False)
+  HTTP/1.1 302 Moved Temporarily...
+
+  >>> print http(r"""
+  ... GET /%s/test_script/manage_DAVget HTTP/1.1
+  ... """ % (folder_name), handle_errors=False)
+  HTTP/1.1 302 Moved Temporarily...
+
+And for a XML-RPC request:
+
+  >>> print http(r"""
+  ... POST /%s HTTP/1.1
+  ... Content-Type: text/xml; charset="utf-8"
+  ... <?xml version="1.0"?>
+  ... <methodCall>
+  ... <methodName>test_script</methodName>
+  ... </methodCall>
+  ... """ % (folder_name), handle_errors=False)
+  HTTP/1.1 302 Moved Temporarily...
+
+However, not all WebDAV and XML-RPC clients understand the
+redirect. Even worse, they will not be able to display the login form
+that is the target of this redirect.
+
+For this reason we should disable the Cookie Auth Helper for
+non-browser requests. In fact, we might only want plugins that
+understand the 'http' authorization protocol to issue challenges for
+WebDAV and XML-RPC.
+
+To do this, we use the Challenge Protocol Chooser plugin together with
+the Request Type Sniffer plugin.
+
+  >>> from Products.PluggableAuthService.interfaces.plugins import \
+  ...   IRequestTypeSniffer, IChallengeProtocolChooser
+
+  >>> dispatcher = pas.manage_addProduct['PluggableAuthService']
+
+  >>> dispatcher.addRequestTypeSnifferPlugin('sniffer')
+  >>> plugins.activatePlugin(IRequestTypeSniffer, 'sniffer')
+
+  >>> mapping = {'WebDAV': ['http'],
+  ...            'XML-RPC': ['http'],
+  ...		 'Browser': []}
+
+  >>> dispatcher.addChallengeProtocolChooserPlugin('chooser',
+  ...                                              mapping=mapping)
+  >>> plugins.activatePlugin(IChallengeProtocolChooser, 'chooser')
+
+Now, invalid credentials should result in a 302 response status for a
+normal (eg: browser) request:
+
+  >>> print http(r"""
+  ... GET /%s/test_script HTTP/1.1
+  ... """ % (folder_name), handle_errors=False)
+  HTTP/1.1 302 Moved Temporarily...
+
+A WebDAV request should result in a 401 response status:
+
+  >>> print http(r"""
+  ... PROPFIND /%s/test_script HTTP/1.1
+  ... """ % (folder_name), handle_errors=False)
+  HTTP/1.1 401 Unauthorized...
+
+  >>> print http(r"""
+  ... GET /%s/test_script/manage_DAVget HTTP/1.1
+  ... """ % (folder_name), handle_errors=False)
+  HTTP/1.1 401 Unauthorized...
+
+And a XML-RPC request should also result in a 401 response status:
+
+  >>> print http(r"""
+  ... POST /%s HTTP/1.1
+  ... Content-Type: text/xml; charset="utf-8"
+  ... <?xml version="1.0"?>
+  ... <methodCall>
+  ... <methodName>test_script</methodName>
+  ... </methodCall>
+  ... """ % (folder_name), handle_errors=False)
+  HTTP/1.1 401 Unauthorized...


=== Products/PluggableAuthService/plugins/tests/test_ChallengeProtocolChooser.py 1.3 => 1.4 ===
--- /dev/null	Wed Aug 17 16:53:44 2005
+++ Products/PluggableAuthService/plugins/tests/test_ChallengeProtocolChooser.py	Wed Aug 17 16:53:14 2005
@@ -0,0 +1,48 @@
+##############################################################################
+#
+# Copyright (c) 2001 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 ChallengeProtocolChooser
+
+$Id$
+"""
+import unittest
+
+from Products.PluggableAuthService.tests.conformance \
+    import IChallengeProtocolChooser_conformance
+
+class ChallengeProtocolChooser( unittest.TestCase
+                                , IChallengeProtocolChooser_conformance 
+                              ):
+
+
+    def _getTargetClass( self ):
+
+        from Products.PluggableAuthService.plugins.ChallengeProtocolChooser \
+            import ChallengeProtocolChooser
+
+        return ChallengeProtocolChooser
+
+    def _makeOne( self, id='test', *args, **kw ):
+
+        return self._getTargetClass()( id, *args, **kw )
+
+
+if __name__ == "__main__":
+    unittest.main()
+        
+def test_suite():
+    return unittest.TestSuite((
+        unittest.makeSuite( ChallengeProtocolChooser ),
+        ))
+        


=== Products/PluggableAuthService/plugins/tests/test_RequestTypeSniffer.py 1.3 => 1.4 ===
--- /dev/null	Wed Aug 17 16:53:44 2005
+++ Products/PluggableAuthService/plugins/tests/test_RequestTypeSniffer.py	Wed Aug 17 16:53:14 2005
@@ -0,0 +1,48 @@
+##############################################################################
+#
+# Copyright (c) 2001 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 RequestTypeSniffer
+
+$Id$
+"""
+import unittest
+
+from Products.PluggableAuthService.tests.conformance \
+    import IRequestTypeSniffer_conformance
+
+class RequestTypeSniffer( unittest.TestCase
+                          , IRequestTypeSniffer_conformance 
+                        ):
+
+
+    def _getTargetClass( self ):
+
+        from Products.PluggableAuthService.plugins.RequestTypeSniffer \
+            import RequestTypeSniffer
+
+        return RequestTypeSniffer
+
+    def _makeOne( self, id='test', *args, **kw ):
+
+        return self._getTargetClass()( id, *args, **kw )
+
+
+if __name__ == "__main__":
+    unittest.main()
+        
+def test_suite():
+    return unittest.TestSuite((
+        unittest.makeSuite( RequestTypeSniffer ),
+        ))
+        


=== Products/PluggableAuthService/plugins/tests/test_doctests.py 1.3 => 1.4 ===
--- /dev/null	Wed Aug 17 16:53:44 2005
+++ Products/PluggableAuthService/plugins/tests/test_doctests.py	Wed Aug 17 16:53:14 2005
@@ -0,0 +1,32 @@
+##############################################################################
+#
+# Copyright (c) 2001 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.
+#
+##############################################################################
+
+import unittest
+from Testing import ZopeTestCase
+
+ZopeTestCase.installProduct('PythonScripts')
+ZopeTestCase.installProduct('PluginRegistry')
+ZopeTestCase.installProduct('PluggableAuthService')
+
+def test_suite():
+    suite = unittest.TestSuite()
+    package = 'Products.PluggableAuthService.plugins.tests'
+    tests = [
+        ZopeTestCase.FunctionalDocFileSuite('ChallengeProtocolChooser.txt',
+                                            package=package),
+        ]
+    for t in tests:
+        suite.addTest(t)
+    return suite



More information about the Zope-CVS mailing list