[Checkins] SVN: Products.PluggableAuthService/branches/shh-16-masquerading/ Merged revisions 97359-97360, 97388, 97400-97402, 97407, 97415, 97430, 97438-97439, 97442, 97452, 97528, 97552-97553, 97687, 97690 via svnmerge from

Andreas Zeidler az at zitc.de
Thu Jul 23 08:26:26 EDT 2009


Log message for revision 102141:
  Merged revisions 97359-97360,97388,97400-97402,97407,97415,97430,97438-97439,97442,97452,97528,97552-97553,97687,97690 via svnmerge from 
  svn+ssh://svn.zope.org/repos/main/Products.PluggableAuthService/branches/shh-15-masquerading
  
  ........
    r97359 | shh | 2009-02-27 22:16:43 +0100 (Fri, 27 Feb 2009) | 7 lines
    
    User masquerading. Adapted from a patch against PAS 1.0.4.
    
    Logging in as AUTHUSER/ROLEUSER (e.g. 'admin/jdoe') authenticates
    against AUTHUSER but returns ROLEUSER. As a security precaution,
    AUTHUSER must have the Manager role. Note: AUTHUSER and ROLEUSER
    must live in the same user folder.
  ........
    r97360 | shh | 2009-02-27 22:17:40 +0100 (Fri, 27 Feb 2009) | 1 line
    
    Slash is used for masquerading on this branch.
  ........
    r97388 | shh | 2009-02-28 23:37:48 +0100 (Sat, 28 Feb 2009) | 1 line
    
    Extract _canMasquerade method.
  ........
    r97400 | shh | 2009-03-01 20:51:22 +0100 (Sun, 01 Mar 2009) | 1 line
    
    Restore credentials even when the plugin raises an exception.
  ........
    r97401 | shh | 2009-03-01 20:54:30 +0100 (Sun, 01 Mar 2009) | 1 line
    
    Use API to create user object.
  ........
    r97402 | shh | 2009-03-01 21:39:41 +0100 (Sun, 01 Mar 2009) | 1 line
    
    Add test for denied masquerading.
  ........
    r97407 | shh | 2009-03-02 00:27:56 +0100 (Mon, 02 Mar 2009) | 2 lines
    
    Revert r97359. We don't need this - awesome.
  ........
    r97415 | shh | 2009-03-02 11:31:57 +0100 (Mon, 02 Mar 2009) | 1 line
    
    Test user names and failed validation.
  ........
    r97430 | shh | 2009-03-03 09:44:14 +0100 (Tue, 03 Mar 2009) | 3 lines
    
    Revert another lump of r97359. We only really need to deal with user
    extraction, so let's skip on enumeration and decoration.
  ........
    r97438 | shh | 2009-03-03 13:58:44 +0100 (Tue, 03 Mar 2009) | 1 line
    
    Add global on/off switch via environment variable.
  ........
    r97439 | shh | 2009-03-03 15:12:41 +0100 (Tue, 03 Mar 2009) | 1 line
    
    Removed too much last time around.
  ........
    r97442 | tomster | 2009-03-03 17:21:03 +0100 (Tue, 03 Mar 2009) | 1 line
    
    Enabled masquerading for Masquerader role.
  ........
    r97452 | shh | 2009-03-03 20:55:40 +0100 (Tue, 03 Mar 2009) | 1 line
    
    Fix line wrapping.
  ........
    r97528 | shh | 2009-03-05 13:45:33 +0100 (Thu, 05 Mar 2009) | 2 lines
    
    Make _canMasquerade aware of groups.
  ........
    r97552 | shh | 2009-03-06 01:01:16 +0100 (Fri, 06 Mar 2009) | 1 line
    
    Add ftests for basic and cookie authentication.
  ........
    r97553 | shh | 2009-03-06 01:08:15 +0100 (Fri, 06 Mar 2009) | 1 line
    
    User ids are never decorated (only logins are).
  ........
    r97687 | shh | 2009-03-09 12:05:26 +0100 (Mon, 09 Mar 2009) | 1 line
    
    Add test for cooke credentials being accepted.
  ........
    r97690 | shh | 2009-03-09 12:09:56 +0100 (Mon, 09 Mar 2009) | 1 line
    
    Return previously set value when an arg is passed to masquerading.
  ........
  

Changed:
  _U  Products.PluggableAuthService/branches/shh-16-masquerading/
  U   Products.PluggableAuthService/branches/shh-16-masquerading/Products/PluggableAuthService/PluggableAuthService.py
  A   Products.PluggableAuthService/branches/shh-16-masquerading/Products/PluggableAuthService/doc/masquerading.txt
  U   Products.PluggableAuthService/branches/shh-16-masquerading/Products/PluggableAuthService/tests/pastc.py
  U   Products.PluggableAuthService/branches/shh-16-masquerading/Products/PluggableAuthService/tests/test_MoreCaching.py
  U   Products.PluggableAuthService/branches/shh-16-masquerading/Products/PluggableAuthService/tests/test_PluggableAuthService.py
  A   Products.PluggableAuthService/branches/shh-16-masquerading/Products/PluggableAuthService/tests/test_masquerading.py
  U   Products.PluggableAuthService/branches/shh-16-masquerading/Products/PluggableAuthService/utils.py

-=-

Property changes on: Products.PluggableAuthService/branches/shh-16-masquerading
___________________________________________________________________
Modified: svnmerge-integrated
   - /Products.PluggableAuthService/branches/shh-15-masquerading:1-97346 /Products.PluggableAuthService/branches/1.6:1-102136
   + /Products.PluggableAuthService/branches/shh-15-masquerading:1-97445,97447-102134,102137-102140 /Products.PluggableAuthService/branches/1.6:1-102136

Modified: Products.PluggableAuthService/branches/shh-16-masquerading/Products/PluggableAuthService/PluggableAuthService.py
===================================================================
--- Products.PluggableAuthService/branches/shh-16-masquerading/Products/PluggableAuthService/PluggableAuthService.py	2009-07-23 12:22:17 UTC (rev 102140)
+++ Products.PluggableAuthService/branches/shh-16-masquerading/Products/PluggableAuthService/PluggableAuthService.py	2009-07-23 12:26:25 UTC (rev 102141)
@@ -86,6 +86,8 @@
 from utils import createViewName
 from utils import createKeywords
 from utils import classImplements
+from utils import masquerading
+from utils import splitmasq
 
 security = ModuleSecurityInfo(
     'Products.PluggableAuthService.PluggableAuthService' )
@@ -597,14 +599,41 @@
                     for authenticator_id, auth in authenticators:
 
                         try:
+                            # Masquerading: Authenticate auth_user
+                            auth_user_credentials = credentials.copy()
+
+                            login = credentials.get('login', '')
+
+                            auth_user_login, role_user_login = splitmasq( login )
+
+                            if role_user_login is not None:
+                                auth_user_credentials['login'] = auth_user_login
+
                             uid_and_info = auth.authenticateCredentials(
-                                credentials )
+                                auth_user_credentials )
 
                             if uid_and_info is None:
                                 continue
 
                             user_id, info = uid_and_info
 
+                            if role_user_login is not None:
+
+                                # Masquerading: Check if auth_user is allowed to masquerade
+                                if self._canMasquerade( plugins, user_id, info, request ):
+                                    logger.info('Masquerading allowed: %s', login)
+                                else:
+                                    logger.warn('Masquerading denied: %s', login)
+                                    continue
+
+                                # Masquerading: Return role_user
+                                role_user_info = self._verifyUser( plugins, login=role_user_login )
+
+                                if role_user_info is None:
+                                    continue
+
+                                user_id, info = role_user_info['id'], role_user_info['login']
+
                         except _SWALLOWABLE_PLUGIN_EXCEPTIONS:
                             msg = 'AuthenticationPlugin %s error' % ( 
                                     authenticator_id, )
@@ -704,6 +733,25 @@
 
         return PropertiedUser( user_id, name ).__of__( self )
 
+    security.declarePrivate( '_canMasquerade' )
+    def _canMasquerade( self, plugins, user_id, name=None, request=None ):
+
+        """ Return True if masquerading is enabled and user_id has the
+            Manager or Masquerader role.
+        """
+        if not masquerading():
+            return False
+
+        user = self._findUser( plugins, user_id, name, request )
+
+        if user is not None:
+            roles = user.getRoles()
+
+            if 'Manager' in roles or 'Masquerader' in roles:
+                return True
+
+        return False
+
     security.declarePrivate( '_findUser' )
     def _findUser( self, plugins, user_id, name=None, request=None ):
 
@@ -765,6 +813,11 @@
             # plugin enumerators.
             return None
 
+        # Masquerading: Lookup role_user
+        auth_user_login, role_user_login = splitmasq( login )
+        if role_user_login is not None:
+            login = role_user_login
+
         criteria = {'exact_match': True}
 
         if user_id is not None:

Copied: Products.PluggableAuthService/branches/shh-16-masquerading/Products/PluggableAuthService/doc/masquerading.txt (from rev 97442, Products.PluggableAuthService/branches/shh-15-masquerading/Products/PluggableAuthService/doc/masquerading.txt)
===================================================================
--- Products.PluggableAuthService/branches/shh-16-masquerading/Products/PluggableAuthService/doc/masquerading.txt	                        (rev 0)
+++ Products.PluggableAuthService/branches/shh-16-masquerading/Products/PluggableAuthService/doc/masquerading.txt	2009-07-23 12:26:25 UTC (rev 102141)
@@ -0,0 +1,11 @@
+Masquerading
+============
+
+If the environment variable ``PAS_MASQUERADING`` is set to ``on``, masquerading
+is enabled.
+
+Then, logging in as AUTHUSER/ROLEUSER (e.g. 'admin/jdoe') authenticates against 
+AUTHUSER but returns ROLEUSER. As a security precaution, AUTHUSER must have
+the Manager or the Masquerader role. 
+
+Note: AUTHUSER and ROLEUSER must live in the same user folder.

Modified: Products.PluggableAuthService/branches/shh-16-masquerading/Products/PluggableAuthService/tests/pastc.py
===================================================================
--- Products.PluggableAuthService/branches/shh-16-masquerading/Products/PluggableAuthService/tests/pastc.py	2009-07-23 12:22:17 UTC (rev 102140)
+++ Products.PluggableAuthService/branches/shh-16-masquerading/Products/PluggableAuthService/tests/pastc.py	2009-07-23 12:26:25 UTC (rev 102141)
@@ -24,15 +24,20 @@
 from Testing.ZopeTestCase import user_password
 from Testing.ZopeTestCase import user_role
 
-from base64 import encodestring
-user_auth = encodestring('%s:%s' % (user_name, user_password)).rstrip()
-
 from Products.PluggableAuthService.interfaces.plugins import \
     IAuthenticationPlugin, IUserEnumerationPlugin, IRolesPlugin, \
     IRoleEnumerationPlugin, IRoleAssignerPlugin, \
+    IGroupsPlugin, IGroupEnumerationPlugin, \
     IChallengePlugin, IExtractionPlugin, IUserAdderPlugin
 
+from base64 import encodestring
 
+def mkauth(name, password):
+    return encodestring('%s:%s' % (name, password)).rstrip()
+
+user_auth = mkauth(user_name, user_password)
+
+
 class PASTestCase(ZopeTestCase.ZopeTestCase):
     """ZopeTestCase with a PAS instead of the default user folder
     """
@@ -46,6 +51,7 @@
         factory.addHTTPBasicAuthHelper('http_auth')
         factory.addZODBUserManager('users')
         factory.addZODBRoleManager('roles')
+        factory.addZODBGroupManager('groups')
         plugins = pas.plugins
         plugins.activatePlugin(IChallengePlugin, 'http_auth')
         plugins.activatePlugin(IExtractionPlugin, 'http_auth')
@@ -55,6 +61,8 @@
         plugins.activatePlugin(IRolesPlugin, 'roles')
         plugins.activatePlugin(IRoleAssignerPlugin, 'roles')
         plugins.activatePlugin(IRoleEnumerationPlugin, 'roles')
+        plugins.activatePlugin(IGroupsPlugin, 'groups')
+        plugins.activatePlugin(IGroupEnumerationPlugin, 'groups')
 
     def _setupUser(self):
         """Creates the default user."""

Modified: Products.PluggableAuthService/branches/shh-16-masquerading/Products/PluggableAuthService/tests/test_MoreCaching.py
===================================================================
--- Products.PluggableAuthService/branches/shh-16-masquerading/Products/PluggableAuthService/tests/test_MoreCaching.py	2009-07-23 12:22:17 UTC (rev 102140)
+++ Products.PluggableAuthService/branches/shh-16-masquerading/Products/PluggableAuthService/tests/test_MoreCaching.py	2009-07-23 12:26:25 UTC (rev 102141)
@@ -283,7 +283,6 @@
         password = 'secret'
 
         factory = self.pas.manage_addProduct['PluggableAuthService']
-        factory.addZODBGroupManager( 'groups' )
         self.pas._doAddUser(user_id, password, [], [])
         groups = self.pas.groups
         groups.addGroup( group_id )
@@ -298,7 +297,6 @@
         password = 'secret'
 
         factory = self.pas.manage_addProduct['PluggableAuthService']
-        factory.addZODBGroupManager( 'groups' )
         self.pas._doAddUser(user_id, password, [], [])
         groups = self.pas.groups
         groups.addGroup( group_id )

Modified: Products.PluggableAuthService/branches/shh-16-masquerading/Products/PluggableAuthService/tests/test_PluggableAuthService.py
===================================================================
--- Products.PluggableAuthService/branches/shh-16-masquerading/Products/PluggableAuthService/tests/test_PluggableAuthService.py	2009-07-23 12:22:17 UTC (rev 102140)
+++ Products.PluggableAuthService/branches/shh-16-masquerading/Products/PluggableAuthService/tests/test_PluggableAuthService.py	2009-07-23 12:26:25 UTC (rev 102141)
@@ -1475,7 +1475,7 @@
         zcuf = self._makeOne( plugins )
 
         foo = self._makeUserEnumerator( 'foo' )
-        foo.identifier = 'foo/'
+        foo.identifier = 'foo:'
         zcuf._setObject( 'foo', foo )
 
         bar = self._makeUserEnumerator( 'bar', 'bar at example.com' )
@@ -1490,7 +1490,7 @@
         self.assertEqual( zcuf.getUser( 'zope' ), None )
 
         user = zcuf.getUser( 'foo' )
-        self.assertEqual( user.getId(), 'foo/foo' )
+        self.assertEqual( user.getId(), 'foo:foo' )
 
         self.assertEqual( zcuf.getUser( 'who_knows' ), None )
 
@@ -1506,17 +1506,17 @@
         zcuf = self._makeOne( plugins )
 
         bar = self._makeUserEnumerator( 'bar', 'bar at example.com' )
-        bar.identifier = 'bar/'
+        bar.identifier = 'bar:'
         zcuf._setObject( 'bar', bar )
 
         zcuf.plugins.activatePlugin(IUserEnumerationPlugin, 'bar')
         # Fetch the new user by ID and name, and check we get the same.
-        user = zcuf.getUserById('bar/bar')
-        self.assertEqual( user.getId(), 'bar/bar')
+        user = zcuf.getUserById('bar:bar')
+        self.assertEqual( user.getId(), 'bar:bar')
         self.assertEqual( user.getUserName(), 'bar at example.com' )
 
         user2 = zcuf.getUser('bar at example.com')
-        self.assertEqual( user2.getId(), 'bar/bar')
+        self.assertEqual( user2.getId(), 'bar:bar')
         self.assertEqual( user2.getUserName(), 'bar at example.com' )
 
     def test_simple_getUserGroups_with_Groupplugin(self):

Copied: Products.PluggableAuthService/branches/shh-16-masquerading/Products/PluggableAuthService/tests/test_masquerading.py (from rev 97442, Products.PluggableAuthService/branches/shh-15-masquerading/Products/PluggableAuthService/tests/test_masquerading.py)
===================================================================
--- Products.PluggableAuthService/branches/shh-16-masquerading/Products/PluggableAuthService/tests/test_masquerading.py	                        (rev 0)
+++ Products.PluggableAuthService/branches/shh-16-masquerading/Products/PluggableAuthService/tests/test_masquerading.py	2009-07-23 12:26:25 UTC (rev 102141)
@@ -0,0 +1,371 @@
+##############################################################################
+#
+# Copyright (c) 2009 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 base64 import decodestring
+from urllib import unquote
+
+from Testing.ZopeTestCase import Functional
+
+from Products.PluggableAuthService.tests import pastc
+
+from Products.PluggableAuthService.interfaces.plugins import IChallengePlugin
+from Products.PluggableAuthService.interfaces.plugins import IExtractionPlugin
+from Products.PluggableAuthService.interfaces.plugins import ICredentialsUpdatePlugin
+from Products.PluggableAuthService.interfaces.plugins import ICredentialsResetPlugin
+
+from Products.PluggableAuthService.utils import masquerading
+from Products.PluggableAuthService.utils import splitmasq
+
+from AccessControl.SecurityManagement import getSecurityManager
+from AccessControl.Permissions import view as View
+
+
+class SplitMasqTests(unittest.TestCase):
+
+    def testSimpleId(self):
+        self.assertEqual(splitmasq('fred'), ('fred', None))
+
+    def testMasqueradingId(self):
+        self.assertEqual(splitmasq('fred/wilma'), ('fred', 'wilma'))
+
+    def testStartsWithSlash(self):
+        self.assertEqual(splitmasq('/fred'), ('/fred', None))
+
+    def testEndsWithSlash(self):
+        self.assertEqual(splitmasq('fred/'), ('fred/', None))
+
+    def testSpuriousSlash(self):
+        self.assertEqual(splitmasq('fred//wilma'), ('fred', '/wilma'))
+
+    def testSpuriousId(self):
+        self.assertEqual(splitmasq('fred/wilma/pebbles'), ('fred', 'wilma/pebbles'))
+
+    def testNoneId(self):
+        self.assertEqual(splitmasq(None), (None, None))
+
+    def testEmptyId(self):
+        self.assertEqual(splitmasq(''), ('', None))
+
+
+class MasqueradingTests(pastc.PASTestCase):
+
+    def afterSetUp(self):
+        self.pas = self.folder.acl_users
+
+        # Create a masquerading user (Manager)
+        self.pas.users.addUser('fred_id', 'fred', 'r0ck')
+        self.pas.roles.assignRoleToPrincipal('Manager', 'fred_id')
+
+        # Create a masquerading user (Masquerader)
+        self.pas.users.addUser('barney_id', 'barney', 'p4per')
+        self.pas.roles.addRole('Masquerader')
+        self.pas.roles.assignRoleToPrincipal('Masquerader', 'barney_id')
+
+        # Create a masquerading user (Masquerader via group)
+        self.pas.users.addUser('pebbles_id', 'pebbles', 'sci55ors')
+        self.pas.groups.addGroup('flintstone_id', 'flintstone')
+        self.pas.groups.addPrincipalToGroup('pebbles_id', 'flintstone_id')
+        self.pas.roles.assignRoleToPrincipal('Masquerader', 'flintstone_id')
+
+        # Create a masqueraded user
+        self.pas.users.addUser('wilma_id', 'wilma', 'geheim')
+        self.pas.roles.assignRoleToPrincipal(pastc.user_role, 'wilma_id')
+
+        # Create a protected document
+        self.folder.manage_addDTMLMethod('doc', file='the document')
+        self.doc = self.folder.doc
+        self.doc.manage_permission(View, [pastc.user_role], acquire=False)
+
+        # Rig the request so it looks like we traversed to doc
+        request = self.app.REQUEST
+        request['PUBLISHED'] = self.doc
+        request['PARENTS'] = [self.folder, self.app]
+        request.steps = list(self.doc.getPhysicalPath())
+
+        # Start out as Anonymous User
+        self.logout()
+
+        # Enable masquerading
+        masquerading(True)
+
+    def afterClear(self):
+        # Disable masquerading
+        masquerading(False)
+
+    def test__extractUserIds_Manager(self):
+        request = self.app.REQUEST
+        request._auth = 'Basic %s' % pastc.mkauth('fred/wilma', 'r0ck')
+
+        uids = self.pas._extractUserIds(request, self.pas.plugins)
+        self.assertEqual(len(uids), 1)
+
+        user_id, info = uids[0]
+        self.assertEqual(user_id, 'wilma_id')
+        self.assertEqual(info, 'wilma')
+
+    def test__extractUserIds_Masquerader(self):
+        request = self.app.REQUEST
+        request._auth = 'Basic %s' % pastc.mkauth('barney/wilma', 'p4per')
+
+        uids = self.pas._extractUserIds(request, self.pas.plugins)
+        self.assertEqual(len(uids), 1)
+
+        user_id, info = uids[0]
+        self.assertEqual(user_id, 'wilma_id')
+        self.assertEqual(info, 'wilma')
+
+    def test__extractUserIds_Masquerader_via_group(self):
+        request = self.app.REQUEST
+        request._auth = 'Basic %s' % pastc.mkauth('pebbles/wilma', 'sci55ors')
+
+        uids = self.pas._extractUserIds(request, self.pas.plugins)
+        self.assertEqual(len(uids), 1)
+
+        user_id, info = uids[0]
+        self.assertEqual(user_id, 'wilma_id')
+        self.assertEqual(info, 'wilma')
+
+    def test__extractUserIds_masquerading_disabled(self):
+        request = self.app.REQUEST
+        request._auth = 'Basic %s' % pastc.mkauth('fred/wilma', 'r0ck')
+
+        masquerading(False)
+
+        uids = self.pas._extractUserIds(request, self.pas.plugins)
+        self.assertEqual(len(uids), 0)
+
+    def test__extractUserIds_masquerading_denied(self):
+        request = self.app.REQUEST
+        request._auth = 'Basic %s' % pastc.mkauth('wilma/fred', 'geheim')
+
+        uids = self.pas._extractUserIds(request, self.pas.plugins)
+        self.assertEqual(len(uids), 0)
+
+    def test__extractUserIds_bad_role_user(self):
+        request = self.app.REQUEST
+        request._auth = 'Basic %s' % pastc.mkauth('fred/betty', 'r0ck')
+
+        uids = self.pas._extractUserIds(request, self.pas.plugins)
+        self.assertEqual(len(uids), 0)
+
+    def test__verifyUser_by_login(self):
+        info = self.pas._verifyUser(self.pas.plugins, login='fred/wilma')
+        self.assertEqual(info['id'], 'wilma_id')
+        self.assertEqual(info['login'], 'wilma')
+
+    def test__verifyUser_bad_role_user(self):
+        info = self.pas._verifyUser(self.pas.plugins, login='fred/betty')
+        self.assertEqual(info, None)
+
+    def test_validate_Manager(self):
+        request = self.app.REQUEST
+        request._auth = 'Basic %s' % pastc.mkauth('fred/wilma', 'r0ck')
+
+        user = self.pas.validate(request)
+        self.failIfEqual(user, None)
+        self.assertEqual(user.getId(), 'wilma_id')
+        self.assertEqual(user.getUserName(), 'wilma')
+        self.assertEqual(user.getRoles(), ['Authenticated', pastc.user_role])
+
+        user = getSecurityManager().getUser()
+        self.failIfEqual(user, None)
+        self.assertEqual(user.getId(), 'wilma_id')
+        self.assertEqual(user.getUserName(), 'wilma')
+        self.assertEqual(user.getRoles(), ['Authenticated', pastc.user_role])
+
+    def test_validate_Masquerader(self):
+        request = self.app.REQUEST
+        request._auth = 'Basic %s' % pastc.mkauth('barney/wilma', 'p4per')
+
+        user = self.pas.validate(request)
+        self.failIfEqual(user, None)
+        self.assertEqual(user.getId(), 'wilma_id')
+        self.assertEqual(user.getUserName(), 'wilma')
+        self.assertEqual(user.getRoles(), ['Authenticated', pastc.user_role])
+
+        user = getSecurityManager().getUser()
+        self.failIfEqual(user, None)
+        self.assertEqual(user.getId(), 'wilma_id')
+        self.assertEqual(user.getUserName(), 'wilma')
+        self.assertEqual(user.getRoles(), ['Authenticated', pastc.user_role])
+
+    def test_validate_Masquerader_via_group(self):
+        request = self.app.REQUEST
+        request._auth = 'Basic %s' % pastc.mkauth('pebbles/wilma', 'sci55ors')
+
+        user = self.pas.validate(request)
+        self.failIfEqual(user, None)
+        self.assertEqual(user.getId(), 'wilma_id')
+        self.assertEqual(user.getUserName(), 'wilma')
+        self.assertEqual(user.getRoles(), ['Authenticated', pastc.user_role])
+
+        user = getSecurityManager().getUser()
+        self.failIfEqual(user, None)
+        self.assertEqual(user.getId(), 'wilma_id')
+        self.assertEqual(user.getUserName(), 'wilma')
+        self.assertEqual(user.getRoles(), ['Authenticated', pastc.user_role])
+
+    def test_validate_masquerading_disabled(self):
+        request = self.app.REQUEST
+        request._auth = 'Basic %s' % pastc.mkauth('fred/wilma', 'r0ck')
+
+        masquerading(False)
+
+        user = self.pas.validate(request)
+        self.assertEqual(user, None)
+
+        user = getSecurityManager().getUser()
+        self.failIfEqual(user, None)
+        self.assertEqual(user.getId(), None)
+        self.assertEqual(user.getUserName(), 'Anonymous User')
+        self.assertEqual(user.getRoles(), ('Anonymous',))
+
+    def test_validate_masquerading_denied(self):
+        request = self.app.REQUEST
+        request._auth = 'Basic %s' % pastc.mkauth('wilma/fred', 'geheim')
+
+        user = self.pas.validate(request)
+        self.assertEqual(user, None)
+
+        user = getSecurityManager().getUser()
+        self.failIfEqual(user, None)
+        self.assertEqual(user.getId(), None)
+        self.assertEqual(user.getUserName(), 'Anonymous User')
+        self.assertEqual(user.getRoles(), ('Anonymous',))
+
+    def test_validate_bad_role_user(self):
+        request = self.app.REQUEST
+        request._auth = 'Basic %s' % pastc.mkauth('fred/betty', 'r0ck')
+
+        user = self.pas.validate(request)
+        self.assertEqual(user, None)
+
+        user = getSecurityManager().getUser()
+        self.failIfEqual(user, None)
+        self.assertEqual(user.getId(), None)
+        self.assertEqual(user.getUserName(), 'Anonymous User')
+        self.assertEqual(user.getRoles(), ('Anonymous',))
+
+
+class BasicAuthTests(Functional, pastc.PASTestCase):
+
+    def afterSetUp(self):
+        self.pas = self.folder.acl_users
+        # Create a masquerading user (Manager)
+        self.pas.users.addUser('fred_id', 'fred', 'r0ck')
+        self.pas.roles.assignRoleToPrincipal('Manager', 'fred_id')
+        # Create a masqueraded user
+        self.pas.users.addUser('wilma_id', 'wilma', 'geheim')
+        self.pas.roles.assignRoleToPrincipal(pastc.user_role, 'wilma_id')
+        # Create a protected document
+        self.folder.manage_addDTMLMethod('doc', file='the document')
+        self.doc = self.folder.doc
+        self.doc.manage_permission(View, [pastc.user_role], acquire=False)
+        # Enable masquerading
+        masquerading(True)
+
+    def afterClear(self):
+        # Disable masquerading
+        masquerading(False)
+
+    def testCredentials(self):
+        doc_path = self.doc.absolute_url_path()
+
+        name = 'fred/wilma'
+        password = 'r0ck'
+
+        credentials = '%s:%s' % (name, password)
+
+        response = self.publish(doc_path)
+        self.assertEqual(response.getStatus(), 401)
+
+        response = self.publish(doc_path, basic=credentials)
+        self.assertEqual(response.getStatus(), 200)
+
+
+class CookieAuthTests(BasicAuthTests):
+
+    def afterSetUp(self):
+        BasicAuthTests.afterSetUp(self)
+        # Add a cookie_auth plugin
+        factory = self.pas.manage_addProduct['PluggableAuthService']
+        factory.addCookieAuthHelper('cookie_auth')
+        self.cookie_auth = self.pas.cookie_auth
+        # Activate it
+        plugins = self.pas.plugins
+        plugins.activatePlugin(IChallengePlugin, 'cookie_auth')
+        plugins.movePluginsUp(IChallengePlugin, ['cookie_auth'])
+        plugins.activatePlugin(IExtractionPlugin, 'cookie_auth')
+        plugins.activatePlugin(ICredentialsUpdatePlugin, 'cookie_auth')
+        plugins.activatePlugin(ICredentialsResetPlugin, 'cookie_auth')
+
+    def testCredentials(self):
+        doc_path = self.doc.absolute_url_path()
+        doc_url = self.doc.absolute_url()
+
+        cookie_auth_path = self.cookie_auth.absolute_url_path()
+        cookie_auth_url = self.cookie_auth.absolute_url()
+
+        name = 'fred/wilma'
+        password = 'r0ck'
+
+        # Accessing doc sends us to login_form
+        response = self.publish(doc_path)
+        self.assertEqual(response.getStatus(), 302)
+
+        location = response.getHeader('Location')
+        location, came_from = location.split('?')
+        self.assertEqual(location, cookie_auth_url+'/login_form')
+
+        # Fill the form and submit
+        login_path = cookie_auth_path+'/login'
+        credentials = '?__ac_name=%s&__ac_password=%s&%s' % (name, password, came_from)
+
+        # We are logged in and sent back to where we came_from
+        response = self.publish(login_path+credentials)
+        self.assertEqual(response.getStatus(), 302)
+
+        location = response.getHeader('Location')
+        self.assertEqual(location, doc_url)
+
+        # We also receive an auth cookie
+        cookie_name = self.cookie_auth.cookie_name
+        cookie_value = response.getCookie(cookie_name)['value']
+
+        name, password = decodestring(unquote(cookie_value)).split(':')
+        name = name.decode('hex')
+        password = password.decode('hex')
+
+        self.assertEqual(name, 'fred/wilma')
+        self.assertEqual(password, 'r0ck')
+
+        # Which we can then use to access doc
+        response = self.publish(doc_path, extra={cookie_name: cookie_value})
+        self.assertEqual(response.getStatus(), 200)
+
+
+def test_suite():
+    return unittest.TestSuite((
+        unittest.makeSuite(SplitMasqTests),
+        unittest.makeSuite(MasqueradingTests),
+        unittest.makeSuite(BasicAuthTests),
+        unittest.makeSuite(CookieAuthTests),
+    ))
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')
+

Modified: Products.PluggableAuthService/branches/shh-16-masquerading/Products/PluggableAuthService/utils.py
===================================================================
--- Products.PluggableAuthService/branches/shh-16-masquerading/Products/PluggableAuthService/utils.py	2009-07-23 12:22:17 UTC (rev 102140)
+++ Products.PluggableAuthService/branches/shh-16-masquerading/Products/PluggableAuthService/utils.py	2009-07-23 12:26:25 UTC (rev 102141)
@@ -210,3 +210,24 @@
 
     return {'keywords': keywords.hexdigest()}
 
+
+#
+#   Masquerading helpers
+#
+_ENVAR = 'PAS_MASQUERADING'
+
+def masquerading( enabled=None ):
+    value = os.environ.get(_ENVAR, 'off')
+    if enabled is not None:
+        os.environ[_ENVAR] = enabled and 'on' or 'off'
+    return value == 'on'
+
+_MASQ = '/'
+
+def splitmasq( user_id ):
+    if user_id is not None:
+        split = user_id.split(_MASQ, 1)
+        if len(split) == 2 and '' not in split:
+            return tuple(split)
+    return user_id, None
+



More information about the Checkins mailing list