[Checkins] SVN: Products.PluggableAuthService/trunk/ Merged branch maurits-login-transform.
Maurits van Rees
cvs-admin at zope.org
Tue Jan 22 10:36:27 UTC 2013
Log message for revision 129077:
Merged branch maurits-login-transform.
Changed:
U Products.PluggableAuthService/trunk/Products/PluggableAuthService/PluggableAuthService.py
U Products.PluggableAuthService/trunk/Products/PluggableAuthService/interfaces/authservice.py
U Products.PluggableAuthService/trunk/Products/PluggableAuthService/interfaces/plugins.py
U Products.PluggableAuthService/trunk/Products/PluggableAuthService/plugins/BasePlugin.py
U Products.PluggableAuthService/trunk/Products/PluggableAuthService/plugins/ZODBUserManager.py
U Products.PluggableAuthService/trunk/Products/PluggableAuthService/plugins/tests/test_ZODBUserManager.py
U Products.PluggableAuthService/trunk/Products/PluggableAuthService/tests/test_PluggableAuthService.py
U Products.PluggableAuthService/trunk/Products/PluggableAuthService/tests/test_exportimport.py
U Products.PluggableAuthService/trunk/buildout.cfg
-=-
Modified: Products.PluggableAuthService/trunk/Products/PluggableAuthService/PluggableAuthService.py
===================================================================
--- Products.PluggableAuthService/trunk/Products/PluggableAuthService/PluggableAuthService.py 2013-01-22 10:34:39 UTC (rev 129076)
+++ Products.PluggableAuthService/trunk/Products/PluggableAuthService/PluggableAuthService.py 2013-01-22 10:36:26 UTC (rev 129077)
@@ -181,6 +181,18 @@
maxlistusers = -1 # Don't allow local role form to try to list us!
+ # Method for transforming a login name. This needs to be the name
+ # of a method on this plugin. See the applyTransform method.
+ login_transform = ''
+
+ _properties = (
+ dict(id='title', type='string', mode='w',
+ label='Title'),
+ dict(id='login_transform', type='string', mode='w',
+ label='Transform to apply to login name'),
+ )
+
+
def getId( self ):
return self._id
@@ -195,6 +207,7 @@
"""
plugins = self._getOb( 'plugins' )
+ name = self.applyTransform( name )
user_info = self._verifyUser( plugins, login=name )
if not user_info:
@@ -294,7 +307,10 @@
if search_name:
if kw.get('id') is not None:
del kw['id'] # don't even bother searching by id
- kw['login'] = kw['name']
+ # Note: name can be a sequence.
+ kw['login'] = self.applyTransform( kw['name'] )
+ if kw.get('login', None):
+ kw['login'] = self.applyTransform( kw['login'] )
plugins = self._getOb( 'plugins' )
enumerators = plugins.listPlugins( IUserEnumerationPlugin )
@@ -400,9 +416,14 @@
if not kw.has_key('title'):
kw['title'] = search_name
kw['login'] = search_name
-
+
+ # For groups we search the original name
+ # (e.g. Administrators), for users we apply the transform,
+ # which could lowercase the name.
+ groups = [ d.copy() for d in self.searchGroups( **kw ) ]
+ if kw.get('login', None):
+ kw['login'] = self.applyTransform( kw['login'] )
users = [ d.copy() for d in self.searchUsers( **kw ) ]
- groups = [ d.copy() for d in self.searchGroups( **kw ) ]
if groups_first:
result = groups + users
@@ -613,6 +634,7 @@
return [ ( user_id, name ) ]
# Now see if the user ids can be retrieved from the cache
+ credentials['login'] = self.applyTransform( credentials.get('login') )
view_name = createViewName('_extractUserIds',
credentials.get('login'))
keywords = createKeywords(**credentials)
@@ -723,6 +745,7 @@
""" Allow IUserFactoryPlugins to create, or fall back to default.
"""
+ name = self.applyTransform( name )
factories = plugins.listPlugins( IUserFactoryPlugin )
for factory_id, factory in factories:
@@ -744,6 +767,7 @@
# See if the user can be retrieved from the cache
view_name = createViewName('_findUser', user_id)
+ name = self.applyTransform( name )
keywords = createKeywords(user_id=user_id, name=name)
user = self.ZCacheable_get( view_name=view_name
, keywords=keywords
@@ -805,7 +829,7 @@
criteria[ 'id' ] = user_id
if login is not None:
- criteria[ 'login' ] = login
+ criteria[ 'login' ] = self.applyTransform( login )
view_name = createViewName('_verifyUser', user_id or login)
keywords = createKeywords(**criteria)
@@ -969,6 +993,7 @@
roleassigners = plugins.listPlugins( IRoleAssignerPlugin )
user = None
+ login = self.applyTransform( login )
if not (useradders and roleassigners):
raise NotImplementedError( "There are no plugins"
@@ -1041,6 +1066,74 @@
resp._unauthorized = self._unauthorized
resp._has_challenged = False
+ security.declarePublic( 'applyTransform' )
+ def applyTransform( self, value ):
+ """ Transform for login name.
+
+ Possibly transform the login, for example by making it lower
+ case.
+
+ value must be a string (or unicode) or it may be a sequence
+ (list, tuple), in which case we need to iterate over it.
+ """
+ if not value:
+ return value
+ transform = self._get_login_transform_method()
+ if not transform:
+ return value
+ if isinstance(value, basestring):
+ return transform(value)
+ result = []
+ for v in value:
+ result.append(transform(v))
+ if isinstance(value, tuple):
+ return tuple(result)
+ return result
+
+ security.declarePrivate( '_get_login_transform_method' )
+ def _get_login_transform_method( self ):
+ """ Get the transform method for the login name or None.
+ """
+ login_transform = getattr(self, 'login_transform', None)
+ if not login_transform:
+ return
+ transform = getattr(self, login_transform.strip(), None)
+ if transform is None:
+ logger.debug("Transform method %r not found in plugin %r.",
+ self.login_transform, self)
+ return
+ return transform
+
+ security.declarePrivate( '_setPropValue' )
+ def _setPropValue(self, id, value):
+ if id == 'login_transform':
+ orig_value = getattr(self, id)
+ super(PluggableAuthService, self)._setPropValue(id, value)
+ if id == 'login_transform' and value and value != orig_value:
+ logger.debug("login_transform changed from %r to %r. "
+ "Updating existing login names.", orig_value, value)
+ self.updateAllLoginNames()
+
+ security.declarePublic( 'lower' )
+ def lower( self, value ):
+ """ Transform for login name.
+
+ Strip the value and lowercase it.
+
+ To use this, set login_tranform to 'lower'.
+ """
+ return value.strip().lower()
+
+ security.declarePublic( 'upper' )
+ def upper( self, value ):
+ """ Transform for login name.
+
+ Strip the value and uppercase it.
+
+ To use this, set login_tranform to 'upper'.
+ """
+ return value.strip().upper()
+
#
# Response override
#
@@ -1133,6 +1226,7 @@
but the credentials are not stored in the CookieAuthHelper cookie
but somewhere else, like in a Session.
"""
+ login = self.applyTransform(login)
plugins = self._getOb('plugins')
cred_updaters = plugins.listPlugins(ICredentialsUpdatePlugin)
@@ -1164,6 +1258,94 @@
for resetter_id, resetter in cred_resetters:
resetter.resetCredentials(request, response)
+
+ security.declareProtected( ManageUsers, 'updateLoginName')
+ def updateLoginName(self, user_id, login_name):
+ """Update login name of user.
+ """
+ logger.debug("Called updateLoginName, user_id=%r, login_name=%r",
+ user_id, login_name)
+ login_name = self.applyTransform(login_name)
+ # Note: we do not call self.getUserById(user_id) here to see
+ # if this user exists, or call getUserName() on the answer to
+ # compare it with the transformed login name, because the user
+ # may be reported with a login name that is transformed on the
+ # fly, for example in the _verifyUser call, even though the
+ # plugin has not actually transformed the login name yet in
+ # the backend.
+ self._updateLoginName(user_id, login_name)
+
+ security.declarePublic('updateOwnLoginName')
+ def updateOwnLoginName(self, login_name):
+ """Update own login name of authenticated user.
+ """
+ logger.debug("Called updateOwnLoginName, login_name=%r", login_name)
+ login_name = self.applyTransform(login_name)
+ user = getSecurityManager().getUser()
+ if aq_base(user) is nobody:
+ return
+ user_id = user.getId()
+ # Note: we do not compare the login name here. See the
+ # comment in updateLoginName above.
+ self._updateLoginName(user_id, login_name)
+
+ def _updateLoginName(self, user_id, login_name):
+ # Note: we do not compare the login name here. See the
+ # comment in updateLoginName above.
+ plugins = self._getOb('plugins')
+ updaters = plugins.listPlugins(IUserEnumerationPlugin)
+
+ # Call the updaters. One of them *must* succeed without an
+ # exception, even if it does not change anything. When a
+ # login name is already taken, we do not want to fail
+ # silently.
+ success = False
+ for updater_id, updater in updaters:
+ if not hasattr(updater, 'updateUser'):
+ # This was a later addition to the interface, so we
+ # are forgiving.
+ logger.warn("%s plugin lacks updateUser method of "
+ "IUserEnumerationPlugin.", updater_id)
+ continue
+ try:
+ updater.updateUser(user_id, login_name)
+ except _SWALLOWABLE_PLUGIN_EXCEPTIONS:
+ reraise(updater)
+ logger.debug('UpdateLoginNamePlugin %s error' % updater_id,
+ exc_info=True)
+ else:
+ success = True
+ logger.debug("login name changed to: %r", login_name)
+
+ if not success:
+ raise ValueError("Cannot update login name of user %r to %r. "
+ "Possibly duplicate." % (user_id, login_name))
+
+ security.declareProtected( ManageUsers, 'updateLoginName')
+ def updateAllLoginNames(self, quit_on_first_error=True):
+ """Update login names of all users to their canonical value.
+
+ This should be done after changing the login_transform
+ property of PAS.
+
+ You can set quit_on_first_error to False to report all errors
+ before quitting with an error. This can be useful if you want
+ to know how many problems there are, if any.
+ """
+ plugins = self._getOb('plugins')
+ updaters = plugins.listPlugins(IUserEnumerationPlugin)
+ for updater_id, updater in updaters:
+ if not hasattr(updater, 'updateEveryLoginName'):
+ # This was a later addition to the interface, so we
+ # are forgiving.
+ logger.warn("%s plugin lacks updateEveryLoginName method of "
+ "IUserEnumerationPlugin.", updater_id)
+ continue
+ # Note: do not swallow any exceptions here.
+ updater.updateEveryLoginName(
+ quit_on_first_error=quit_on_first_error)
+
+
classImplements( PluggableAuthService
, (IPluggableAuthService, IObjectManager, IPropertyManager)
)
Modified: Products.PluggableAuthService/trunk/Products/PluggableAuthService/interfaces/authservice.py
===================================================================
--- Products.PluggableAuthService/trunk/Products/PluggableAuthService/interfaces/authservice.py 2013-01-22 10:34:39 UTC (rev 129076)
+++ Products.PluggableAuthService/trunk/Products/PluggableAuthService/interfaces/authservice.py 2013-01-22 10:36:26 UTC (rev 129077)
@@ -210,11 +210,31 @@
default implementation redirects to HTTP_REFERER).
"""
- def resetCredentials(self, request, response):
+ def resetCredentials(request, response):
"""Reset credentials by informing all active resetCredentials
plugins
"""
+ def updateLoginName(user_id, login_name):
+ """Update login name of user.
+ """
+
+ def updateOwnLoginName(login_name):
+ """Update own login name of authenticated user.
+ """
+
+ def updateAllLoginNames(quit_on_first_error=True):
+ """Update login names of all users to their canonical value.
+
+ This should be done after changing the login_transform
+ property of PAS.
+
+ You can set quit_on_first_error to False to report all errors
+ before quitting with an error. This can be useful if you want
+ to know how many problems there are, if any.
+ """
+
+
# The IMutableUserFolder and IEnumerableFolder are not supported
# out-of-the-box by the pluggable authentication service. These
# interfaces describe contracts that other standard Zope user folders
Modified: Products.PluggableAuthService/trunk/Products/PluggableAuthService/interfaces/plugins.py
===================================================================
--- Products.PluggableAuthService/trunk/Products/PluggableAuthService/interfaces/plugins.py 2013-01-22 10:34:39 UTC (rev 129076)
+++ Products.PluggableAuthService/trunk/Products/PluggableAuthService/interfaces/plugins.py 2013-01-22 10:36:26 UTC (rev 129077)
@@ -345,6 +345,21 @@
scaling issues for some implementations.
"""
+ def updateUser( user_id, login_name ):
+ """ Update the login name of the user with id user_id.
+ """
+
+ def updateEveryLoginName(quit_on_first_error=True):
+ """Update login names of all users to their canonical value.
+
+ This should be done after changing the login_transform
+ property of PAS.
+
+ You can set quit_on_first_error to False to report all errors
+ before quitting with an error. This can be useful if you want
+ to know how many problems there are, if any.
+ """
+
class IGroupEnumerationPlugin( Interface ):
""" Allow querying groups by ID, and searching for groups.
@@ -364,8 +379,8 @@
o Return mappings for groups matching the given criteria.
o 'id' in combination with 'exact_match' true, will
- return at most one mapping per supplied ID ('id' and 'login'
- may be sequences).
+ return at most one mapping per supplied ID ('id'
+ may be a sequence).
o If 'exact_match' is False, then 'id' may be treated by
the plugin as "contains" searches (more complicated searches
@@ -415,8 +430,8 @@
o Return mappings for roles matching the given criteria.
o 'id' in combination with 'exact_match' true, will
- return at most one mapping per supplied ID ('id' and 'login'
- may be sequences).
+ return at most one mapping per supplied ID ('id'
+ may be a sequence).
o If 'exact_match' is False, then 'id' may be treated by
the plugin as "contains" searches (more complicated searches
Modified: Products.PluggableAuthService/trunk/Products/PluggableAuthService/plugins/BasePlugin.py
===================================================================
--- Products.PluggableAuthService/trunk/Products/PluggableAuthService/plugins/BasePlugin.py 2013-01-22 10:34:39 UTC (rev 129076)
+++ Products.PluggableAuthService/trunk/Products/PluggableAuthService/plugins/BasePlugin.py 2013-01-22 10:36:26 UTC (rev 129077)
@@ -117,6 +117,23 @@
view_name = createViewName('_findUser', id)
pas.ZCacheable_invalidate(view_name)
+ security.declarePublic( 'applyTransform' )
+ def applyTransform( self, value ):
+ """ Transform for login name.
+
+ Possibly transform the login, for example by making it lower
+ case.
+
+ Normally this is done in PAS itself, but in some cases a
+ method in a plugin may need to do it itself, when there is no
+ method in PAS that calls that method.
+ """
+ pas = self._getPAS()
+ if pas is not None:
+ return pas.applyTransform(value)
+ return value
+
+
classImplements(BasePlugin, *implementedBy(SimpleItem))
InitializeClass(BasePlugin)
Modified: Products.PluggableAuthService/trunk/Products/PluggableAuthService/plugins/ZODBUserManager.py
===================================================================
--- Products.PluggableAuthService/trunk/Products/PluggableAuthService/plugins/ZODBUserManager.py 2013-01-22 10:34:39 UTC (rev 129076)
+++ Products.PluggableAuthService/trunk/Products/PluggableAuthService/plugins/ZODBUserManager.py 2013-01-22 10:36:26 UTC (rev 129077)
@@ -16,6 +16,7 @@
$Id$
"""
import copy
+import logging
try:
from hashlib import sha1 as sha
except:
@@ -46,7 +47,9 @@
from Products.PluggableAuthService.utils import createViewName
from Products.PluggableAuthService.utils import csrf_only
+logger = logging.getLogger('PluggableAuthService')
+
class IZODBUserManager(Interface):
""" Marker interface.
"""
@@ -323,6 +326,62 @@
self._login_to_userid[login_name] = user_id
self._userid_to_login[user_id] = login_name
+ security.declarePrivate('updateEveryLoginName')
+ def updateEveryLoginName(self, quit_on_first_error=True):
+ # Update all login names to their canonical value. This
+ # should be done after changing the login_transform property
+ # of pas. You can set quit_on_first_error to False to report
+ # all errors before quitting with an error. This can be
+ # useful if you want to know how many problems there are, if
+ # any.
+ pas = self._getPAS()
+ transform = pas._get_login_transform_method()
+ if not transform:
+ logger.warn("PAS has a non-existing, empty or wrong "
+ "login_transform property.")
+ return
+
+ # Make a fresh mapping, as we do not want to add or remove
+ # items to the original mapping while we are iterating over
+ # it.
+ new_login_to_userid = OOBTree()
+ errors = []
+ for old_login_name, user_id in self._login_to_userid.items():
+ new_login_name = transform(old_login_name)
+ if new_login_name in new_login_to_userid:
+ logger.error("User id %s: login name %r already taken.",
+ user_id, new_login_name)
+ errors.append(new_login_name)
+ if quit_on_first_error:
+ break
+ new_login_to_userid[new_login_name] = user_id
+ if new_login_name != old_login_name:
+ self._userid_to_login[user_id] = new_login_name
+ # Also, remove from the cache
+ view_name = createViewName('enumerateUsers', user_id)
+ self.ZCacheable_invalidate(view_name=view_name)
+ logger.debug("User id %s: changed login name from %r to %r.",
+ user_id, old_login_name, new_login_name)
+
+ # If there were errors, we do not want to save any changes.
+ if errors:
+ logger.error("There were %d errors when updating login names. "
+ "quit_on_first_error was %r", len(errors),
+ quit_on_first_error)
+ # Make sure the exception we raise is not swallowed.
+ self._dont_swallow_my_exceptions = True
+ raise ValueError("Transformed login names are not unique: %s." %
+ ', '.join(errors))
+
+ # Make sure we did not lose any users.
+ assert(len(self._login_to_userid.keys())
+ == len(new_login_to_userid.keys()))
+ # Empty the main cache.
+ view_name = createViewName('enumerateUsers')
+ self.ZCacheable_invalidate(view_name=view_name)
+ # Store the new login mapping.
+ self._login_to_userid = new_login_to_userid
+
security.declarePrivate( 'removeUser' )
def removeUser( self, user_id ):
Modified: Products.PluggableAuthService/trunk/Products/PluggableAuthService/plugins/tests/test_ZODBUserManager.py
===================================================================
--- Products.PluggableAuthService/trunk/Products/PluggableAuthService/plugins/tests/test_ZODBUserManager.py 2013-01-22 10:34:39 UTC (rev 129076)
+++ Products.PluggableAuthService/trunk/Products/PluggableAuthService/plugins/tests/test_ZODBUserManager.py 2013-01-22 10:36:26 UTC (rev 129077)
@@ -26,6 +26,7 @@
from Products.PluggableAuthService.plugins.tests.helpers \
import makeRequestAndResponse
+
class DummyUser:
def __init__( self, id ):
@@ -34,6 +35,28 @@
def getId( self ):
return self._id
+
+class FakePAS(object):
+
+ def _get_login_transform_method(self):
+ return None
+
+ def applyTransform(self, value):
+ return value
+
+
+class FakeLowerCasePAS(object):
+
+ def _get_login_transform_method(self):
+ return self.lower
+
+ def lower(self, value):
+ return value.lower()
+
+ def applyTransform(self, value):
+ return value.lower()
+
+
class ZODBUserManagerTests( unittest.TestCase
, IAuthenticationPlugin_conformance
, IUserEnumerationPlugin_conformance
@@ -470,6 +493,52 @@
self.assertRaises(ValueError,
zum.updateUser, 'user1', 'user2 at example.com')
+ def test_updateEveryLoginName(self):
+
+ zum = self._makeOne()
+ zum._getPAS = lambda: FakePAS()
+
+ # Create two users and make sure we can authenticate with it
+ zum.addUser( 'User1', 'User1 at Example.Com', 'password' )
+ zum.addUser( 'User2', 'User2 at Example.Com', 'password' )
+ info1 = { 'login' : 'User1 at Example.Com', 'password' : 'password' }
+ info2 = { 'login' : 'User2 at Example.Com', 'password' : 'password' }
+ user_id, login = zum.authenticateCredentials(info1)
+ self.assertEqual(user_id, 'User1')
+ self.assertEqual(login, 'User1 at Example.Com')
+ user_id, login = zum.authenticateCredentials(info2)
+ self.assertEqual(user_id, 'User2')
+ self.assertEqual(login, 'User2 at Example.Com')
+
+ # Give all users a new login, using the applyTransform method
+ # of PAS. There should be no changes.
+ zum.updateEveryLoginName()
+ self.failUnless(zum.authenticateCredentials(info1))
+ self.failUnless(zum.authenticateCredentials(info2))
+
+ # Use a PAS configured to transform login names to lower case.
+ zum._getPAS = lambda: FakeLowerCasePAS()
+
+ # Update all login names
+ zum.updateEveryLoginName()
+
+ # The old mixed case logins no longer work. Note that if you
+ # query PAS (via the validate or _extractUserIds method), PAS
+ # is responsible for transforming the login before passing it
+ # to our plugin.
+ self.failIf(zum.authenticateCredentials(info1))
+ self.failIf(zum.authenticateCredentials(info2))
+
+ # Authentication with all lowercase login works.
+ info1 = { 'login' : 'user1 at example.com', 'password' : 'password' }
+ info2 = { 'login' : 'user2 at example.com', 'password' : 'password' }
+ user_id, login = zum.authenticateCredentials(info1)
+ self.assertEqual(user_id, 'User1')
+ self.assertEqual(login, 'user1 at example.com')
+ user_id, login = zum.authenticateCredentials(info2)
+ self.assertEqual(user_id, 'User2')
+ self.assertEqual(login, 'user2 at example.com')
+
def test_enumerateUsersWithOptionalMangling(self):
zum = self._makeOne()
Modified: Products.PluggableAuthService/trunk/Products/PluggableAuthService/tests/test_PluggableAuthService.py
===================================================================
--- Products.PluggableAuthService/trunk/Products/PluggableAuthService/tests/test_PluggableAuthService.py 2013-01-22 10:34:39 UTC (rev 129076)
+++ Products.PluggableAuthService/trunk/Products/PluggableAuthService/tests/test_PluggableAuthService.py 2013-01-22 10:36:26 UTC (rev 129077)
@@ -13,13 +13,12 @@
##############################################################################
import unittest
-from Acquisition import Implicit, aq_base, aq_parent
+from Acquisition import Implicit, aq_base
from AccessControl.SecurityManagement import newSecurityManager
from AccessControl.SecurityManagement import noSecurityManager
from AccessControl.SecurityManager import setSecurityPolicy
from OFS.ObjectManager import ObjectManager
-from OFS.Folder import Folder
-from zExceptions import Unauthorized, Redirect
+from zExceptions import Unauthorized
from Products.PluggableAuthService.utils import directlyProvides
from zope.interface import implements
@@ -59,10 +58,17 @@
, 'pluginid' : self.PLUGINID
} ]
- if kw.get( 'id' ) == _id:
+ # Both id and login can be strings or sequences.
+ user_id = kw.get( 'id' )
+ if isinstance( user_id, basestring ):
+ user_id = [ user_id ]
+ if user_id and _id in user_id:
return tuple(result)
- if kw.get( 'login' ) == self._login:
+ login = kw.get( 'login' )
+ if isinstance( login, basestring ):
+ login = [ login ]
+ if login and self._login in login:
return tuple(result)
return ()
@@ -107,6 +113,18 @@
return tuple(results)
+ def updateUser(self, user_id, login_name):
+ for info in self.users:
+ if info['id'] == user_id:
+ info['login'] = login_name
+ return
+
+ def updateEveryLoginName(self, quit_on_first_error=True):
+ # Let's lowercase all login names.
+ for info in self.users:
+ info['login'] = info['login'].lower()
+
+
class WantonUserEnumerator(DummyMultiUserEnumerator):
def enumerateUsers( self, *args, **kw):
# Always returns everybody.
@@ -151,6 +169,7 @@
self._group_id = group_id
self.identifier = None
+
class DummyGroupPlugin(DummyPlugin):
def __init__(self, id, groups=()):
@@ -263,10 +282,6 @@
self._dict[ key ] = value
- def has_key( self, key ):
-
- return self._dict.has_key(key)
-
def _hold(self, something):
self._held.append(something)
@@ -525,6 +540,20 @@
directlyProvides( cp, IChallengePlugin )
return cp
+ def _makeMultiUserEnumerator( self, *users ):
+ # users should be something like this:
+ # [{'id': 'Foo', 'login': 'foobar'},
+ # {'id': 'Bar', 'login': 'BAR'}]
+
+ from Products.PluggableAuthService.interfaces.plugins \
+ import IUserEnumerationPlugin
+
+ enumerator = DummyMultiUserEnumerator('enumerator', *users)
+ directlyProvides( enumerator, IUserEnumerationPlugin )
+
+ return enumerator
+
+
def test_empty( self ):
zcuf = self._makeOne()
@@ -733,7 +762,7 @@
def test__extractUserIds_authenticate_emergency_user_with_broken_extractor( self ):
from Products.PluggableAuthService.interfaces.plugins \
- import IExtractionPlugin, IAuthenticationPlugin
+ import IExtractionPlugin
from AccessControl.User import UnrestrictedUser
@@ -940,8 +969,89 @@
finally:
PluggableAuthService.emergency_user = old_eu
+ def test__extractUserIds_transform( self ):
+ from Products.PluggableAuthService.interfaces.plugins \
+ import IExtractionPlugin, IAuthenticationPlugin
+ plugins = self._makePlugins()
+ zcuf = self._makeOne( plugins )
+
+ login = DummyPlugin()
+ directlyProvides( login, IExtractionPlugin, IAuthenticationPlugin )
+ login.extractCredentials = _extractLogin
+ login.authenticateCredentials = _authLogin
+ zcuf._setObject( 'login', login )
+ # Make login names lowercase. User ids are not affected, but
+ # our dummy _authLogin simply reports a tuple with twice the
+ # login name.
+ zcuf.login_transform = 'lower'
+
+ plugins = zcuf._getOb( 'plugins' )
+ plugins.activatePlugin( IExtractionPlugin, 'login' )
+ plugins.activatePlugin( IAuthenticationPlugin, 'login' )
+
+ # Mixed case here.
+ request = self._makeRequest( form={ 'login' : 'Foo'
+ , 'password' : 'Bar' } )
+
+ user_ids = zcuf._extractUserIds( request=request
+ , plugins=zcuf.plugins
+ )
+
+ self.assertEqual( len( user_ids ), 1 )
+ # Lower case here.
+ self.assertEqual( user_ids[0][0], 'foo' )
+
+ def test__extractUserIds_emergency_user_always_wins_in_transform( self ):
+
+ from Products.PluggableAuthService.interfaces.plugins \
+ import IExtractionPlugin, IAuthenticationPlugin
+
+ from AccessControl.User import UnrestrictedUser
+
+ from Products.PluggableAuthService import PluggableAuthService
+
+ old_eu = PluggableAuthService.emergency_user
+
+ # Mixed case here. We want to test if an emergency user with
+ # mixed (or upper) case login name is found also when the
+ # login_transform is to lower case the login.
+ eu = UnrestrictedUser( 'Foo', 'Bar', ( 'manage', ), () )
+
+ PluggableAuthService.emergency_user = eu
+
+ try:
+ plugins = self._makePlugins()
+ zcuf = self._makeOne( plugins )
+
+ login = DummyPlugin()
+ directlyProvides( login, IExtractionPlugin, IAuthenticationPlugin )
+ login.extractCredentials = lambda req: {'login': 'baz', 'password': ''}
+ login.authenticateCredentials = _authLogin
+
+ zcuf._setObject( 'login', login )
+ zcuf.login_transform = 'lower'
+
+ plugins = zcuf._getOb( 'plugins' )
+
+ plugins.activatePlugin( IExtractionPlugin, 'login' )
+ plugins.activatePlugin( IAuthenticationPlugin, 'login' )
+
+ request = self._makeRequest( form={ 'login' : eu.getUserName()
+ , 'password' : eu._getPassword() } )
+
+ # This should authenticate the emergency user and not 'baz'
+ user_ids = zcuf._extractUserIds( request=request
+ , plugins=zcuf.plugins
+ )
+
+ self.assertEqual( len( user_ids ), 1 )
+ self.assertEqual( user_ids[0][0], 'Foo' )
+ finally:
+ PluggableAuthService.emergency_user = old_eu
+
+
def _isNotCompetent_test( self, decisions, result):
from Products.PluggableAuthService.interfaces.plugins \
import INotCompetentPlugin
@@ -1229,7 +1339,67 @@
None)
self.assertEqual(zcuf._verifyUser(plugins), None)
+ def test__verifyUser_login_transform_lower( self ):
+ from Products.PluggableAuthService.interfaces.plugins \
+ import IUserEnumerationPlugin
+
+ plugins = self._makePlugins()
+ zcuf = self._makeOne( plugins )
+ zcuf.login_transform = 'lower'
+
+ enumerator = DummyMultiUserEnumerator(
+ 'enumerator',
+ {'id': 'Foo', 'login': 'foobar'},
+ {'id': 'Bar', 'login': 'BAR'})
+ directlyProvides( enumerator, IUserEnumerationPlugin )
+ zcuf._setObject( 'enumerator', enumerator )
+
+ plugins = zcuf._getOb( 'plugins' )
+
+ plugins.activatePlugin( IUserEnumerationPlugin, 'enumerator' )
+
+ # No matter what we try as login parameter, it is always lower
+ # cased before verifying a user.
+ self.failIf(zcuf._verifyUser(plugins, login='BAR'))
+ self.failIf(zcuf._verifyUser(plugins, login='Bar'))
+ self.failIf(zcuf._verifyUser(plugins, login='bar'))
+ self.failUnless(
+ zcuf._verifyUser(plugins, login='FOOBAR')['id'] == 'Foo')
+ self.failUnless(
+ zcuf._verifyUser(plugins, login='Foobar')['id'] == 'Foo')
+ self.failUnless(
+ zcuf._verifyUser(plugins, login='foobar')['id'] == 'Foo')
+
+ def test__verifyUser_login_transform_upper( self ):
+
+ from Products.PluggableAuthService.interfaces.plugins \
+ import IUserEnumerationPlugin
+
+ plugins = self._makePlugins()
+ zcuf = self._makeOne( plugins )
+ zcuf.login_transform = 'upper'
+
+ enumerator = DummyMultiUserEnumerator(
+ 'enumerator',
+ {'id': 'Foo', 'login': 'foobar'},
+ {'id': 'Bar', 'login': 'BAR'})
+ directlyProvides( enumerator, IUserEnumerationPlugin )
+ zcuf._setObject( 'enumerator', enumerator )
+
+ plugins = zcuf._getOb( 'plugins' )
+
+ plugins.activatePlugin( IUserEnumerationPlugin, 'enumerator' )
+
+ # No matter what we try as login parameter, it is always upper
+ # cased before verifying a user.
+ self.failUnless(zcuf._verifyUser(plugins, login='BAR')['id'] == 'Bar')
+ self.failUnless(zcuf._verifyUser(plugins, login='Bar')['id'] == 'Bar')
+ self.failUnless(zcuf._verifyUser(plugins, login='bar')['id'] == 'Bar')
+ self.failIf(zcuf._verifyUser(plugins, login='FOOBAR'))
+ self.failIf(zcuf._verifyUser(plugins, login='Foobar'))
+ self.failIf(zcuf._verifyUser(plugins, login='foobar'))
+
def test__findUser_no_plugins( self ):
plugins = self._makePlugins()
@@ -1294,6 +1464,42 @@
self.failUnless( faux_user.__class__ is FauxUser )
+ def test__findUser_with_userfactory_plugin_and_transform( self ):
+
+ from Products.PluggableAuthService.interfaces.plugins \
+ import IUserFactoryPlugin
+
+ plugins = self._makePlugins()
+ zcuf = self._makeOne( plugins )
+ zcuf.login_transform = 'lower'
+
+ bar = DummyPlugin()
+ directlyProvides( bar, IUserFactoryPlugin )
+
+ def _makeUser( user_id, name ):
+ user = FauxUser( user_id )
+ user._name = name
+ return user
+
+ bar.createUser = _makeUser
+
+ zcuf._setObject( 'bar', bar )
+
+ plugins = zcuf._getOb( 'plugins' )
+
+ real_user = zcuf._findUser( plugins, 'Mixed', 'Case' )
+ self.failIf( real_user.__class__ is FauxUser )
+
+ plugins.activatePlugin( IUserFactoryPlugin , 'bar' )
+
+ faux_user = zcuf._findUser( plugins, 'Mixed', 'Case' )
+
+ self.assertEqual( faux_user.getId(), 'Mixed' )
+ # This is lower case:
+ self.assertEqual( faux_user.getUserName(), 'case' )
+
+ self.failUnless( faux_user.__class__ is FauxUser )
+
def test__findUser_with_plugins( self ):
from Products.PluggableAuthService.interfaces.plugins \
@@ -1613,6 +1819,35 @@
self.assertEqual( user2.getId(), 'bar/bar')
self.assertEqual( user2.getUserName(), 'bar at example.com' )
+ def test_getUser_login_transform( self ):
+ from Products.PluggableAuthService.interfaces.plugins \
+ import IUserEnumerationPlugin
+
+ plugins = self._makePlugins()
+ zcuf = self._makeOne( plugins )
+ zcuf.login_transform = 'lower'
+
+ # The login_transform is applied in PAS, so we need to lower
+ # case the login ourselves in this test when passing it to a
+ # plugin.
+ bar = self._makeUserEnumerator( 'bar', 'bar at example.com' )
+ 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')
+ self.assertEqual( user.getUserName(), 'bar at example.com' )
+
+ user2 = zcuf.getUser('bar at example.com')
+ self.assertEqual( user2.getId(), 'bar/bar')
+ self.assertEqual( user2.getUserName(), 'bar at example.com' )
+
+ user3 = zcuf.getUser('Bar at Example.Com')
+ self.assertEqual( user3.getId(), 'bar/bar')
+ self.assertEqual( user3.getUserName(), 'bar at example.com' )
+
def test_simple_getUserGroups_with_Groupplugin(self):
from Products.PluggableAuthService.interfaces.plugins \
@@ -1784,6 +2019,60 @@
validated = wrapped.validate( request )
self.assertEqual( validated.getUserName(), 'olivier' )
+ def test_validate_simple_authenticated_transform( self ):
+
+ from Products.PluggableAuthService.interfaces.plugins \
+ import IExtractionPlugin, \
+ IAuthenticationPlugin, \
+ IUserEnumerationPlugin, \
+ IRolesPlugin
+
+ plugins = self._makePlugins()
+ zcuf = self._makeOne( plugins )
+
+ login = DummyPlugin()
+ directlyProvides( login, IExtractionPlugin, IAuthenticationPlugin )
+ login.extractCredentials = _extractLogin
+ login.authenticateCredentials = _authLogin
+ zcuf._setObject( 'login', login )
+ # Lower case all logins.
+ zcuf.login_transform = 'lower'
+
+ olivier = DummyPlugin()
+ directlyProvides( olivier, IUserEnumerationPlugin, IRolesPlugin )
+ olivier.enumerateUsers = lambda id: id == 'foo' or None
+ olivier.getRolesForPrincipal = lambda user, req: (
+ user.getId() == 'olivier' and ( 'Hamlet', ) or () )
+
+ zcuf._setObject( 'olivier', olivier )
+
+ plugins = zcuf._getOb( 'plugins' )
+ plugins.activatePlugin( IExtractionPlugin, 'login' )
+ plugins.activatePlugin( IAuthenticationPlugin, 'login' )
+ plugins.activatePlugin( IUserEnumerationPlugin, 'olivier' )
+ plugins.activatePlugin( IRolesPlugin, 'olivier' )
+
+ rc, root, folder, object = self._makeTree()
+
+ index = FauxObject( 'index_html' )
+ index.__roles__ = ( 'Hamlet', )
+ acquired_index = index.__of__( root ).__of__( object )
+
+ request = self._makeRequest( ( 'folder', 'object', 'index_html' )
+ , RESPONSE=FauxResponse()
+ , PARENTS=[ object, folder, root ]
+ , PUBLISHED=acquired_index.__of__( object )
+ , form={ 'login' : 'OLIVIER'
+ , 'password' : 'arras'
+ }
+ )
+
+
+ wrapped = zcuf.__of__( root )
+
+ validated = wrapped.validate( request )
+ self.assertEqual( validated.getUserName(), 'olivier' )
+
def test_validate_with_anonymous_factory( self ):
from Products.PluggableAuthService.interfaces.plugins \
@@ -1976,6 +2265,89 @@
self.failIf( plugins.listPlugins( IAuthenticationPlugin ) )
self.failIf( plugins.listPlugins( IUserEnumerationPlugin ) )
+ def test_searchUsers( self ):
+
+ from Products.PluggableAuthService.interfaces.plugins \
+ import IUserEnumerationPlugin
+
+ plugins = self._makePlugins()
+ zcuf = self._makeOne( plugins )
+
+ foo = self._makeUserEnumerator( 'foo' )
+ zcuf._setObject( 'foo', foo )
+
+ plugins = zcuf._getOb( 'plugins' )
+ plugins.activatePlugin( IUserEnumerationPlugin, 'foo' )
+
+ # Search by id
+ self.failIf( zcuf.searchUsers( id='zope' ) )
+ self.failUnless( zcuf.searchUsers( id='foo' ) )
+ self.failUnless( len( zcuf.searchUsers( id='foo' )) == 1 )
+
+ # Search by login name
+ self.failIf( zcuf.searchUsers( name='zope' ) )
+ self.failUnless( zcuf.searchUsers( name='foo' ) )
+ self.failUnless( len( zcuf.searchUsers( name='foo' )) == 1 )
+
+ # Login name can be a sequence
+ self.failIf( zcuf.searchUsers( name=['zope'] ) )
+ self.failUnless( zcuf.searchUsers( name=['foo'] ) )
+ self.failUnless( len( zcuf.searchUsers( name=['foo'] )) == 1 )
+ self.failIf( zcuf.searchUsers( name=('zope', ) ) )
+ self.failUnless( zcuf.searchUsers( name=('foo', ) ) )
+ self.failUnless( len( zcuf.searchUsers( name=('foo', ) )) == 1 )
+ self.failUnless( zcuf.searchUsers( name=('foo', 'bar' ) ) )
+ self.failUnless( len( zcuf.searchUsers( name=('foo', 'bar') )) == 1 )
+
+ def test_searchUsers_transform( self ):
+
+ from Products.PluggableAuthService.interfaces.plugins \
+ import IUserEnumerationPlugin
+
+ plugins = self._makePlugins()
+ zcuf = self._makeOne( plugins )
+ zcuf.login_transform = 'lower'
+
+ # user id upper case, login name lower case
+ foo = self._makeUserEnumerator( 'FOO', 'foo' )
+ zcuf._setObject( 'foo', foo )
+ bar = self._makeUserEnumerator( 'BAR', 'bar' )
+ zcuf._setObject( 'bar', bar )
+
+ plugins = zcuf._getOb( 'plugins' )
+ # Note: we only activate 'foo' for now.
+ plugins.activatePlugin( IUserEnumerationPlugin, 'foo' )
+
+ # Search by id
+ self.failIf( zcuf.searchUsers( id='ZOPE' ) )
+ self.failUnless( zcuf.searchUsers( id='FOO' ) )
+ self.failUnless( len( zcuf.searchUsers( id='FOO' )) == 1 )
+
+ # Search by login name
+ self.failIf( zcuf.searchUsers( name='Zope' ) )
+ self.failUnless( zcuf.searchUsers( name='Foo' ) )
+ self.failUnless( len( zcuf.searchUsers( name='Foo' )) == 1 )
+
+ # Login name can be a sequence
+ self.failIf( zcuf.searchUsers( name=['Zope'] ) )
+ self.failUnless( zcuf.searchUsers( name=['Foo'] ) )
+ self.failUnless( len( zcuf.searchUsers( name=['Foo'] )) == 1 )
+ self.failIf( zcuf.searchUsers( name=('Zope', ) ) )
+ self.failUnless( zcuf.searchUsers( name=('Foo', ) ) )
+ self.failUnless( len( zcuf.searchUsers( name=('Foo', ) )) == 1 )
+
+ # Search for more ids or names.
+ self.failUnless( zcuf.searchUsers( id=['FOO', 'BAR', 'ZOPE'] ) )
+ self.failUnless( len( zcuf.searchUsers( id=['FOO', 'BAR', 'ZOPE'] )) == 1 )
+ self.failUnless( zcuf.searchUsers( name=('Foo', 'Bar' , 'Zope' ) ) )
+ self.failUnless( len( zcuf.searchUsers( name=('Foo', 'Bar', 'Zope') )) == 1 )
+
+ # Activate the bar plugin and try again.
+ plugins.activatePlugin( IUserEnumerationPlugin, 'bar' )
+ self.failUnless( len( zcuf.searchUsers( id=['FOO', 'BAR', 'ZOPE'] )) == 2 )
+ self.failUnless( len( zcuf.searchUsers( name=('Foo', 'Bar', 'Zope') )) == 2 )
+
+
def test_searchGroups( self ):
from Products.PluggableAuthService.interfaces.plugins \
@@ -2040,7 +2412,170 @@
self.failUnless(
len( zcuf.searchPrincipals(id='group')) == 1 )
+ def test_searchPrincipals_transform( self ):
+ from Products.PluggableAuthService.interfaces.plugins \
+ import IUserEnumerationPlugin
+ from Products.PluggableAuthService.interfaces.plugins \
+ import IGroupEnumerationPlugin
+
+ plugins = self._makePlugins()
+ zcuf = self._makeOne( plugins )
+ zcuf.login_transform = 'lower'
+
+ foo = self._makeUserEnumerator( 'foo' )
+ zcuf._setObject( 'foo', foo )
+ foobar = self._makeGroupEnumerator( 'Foobar' )
+ zcuf._setObject( 'foobar', foobar )
+
+ plugins = zcuf._getOb( 'plugins' )
+ plugins.activatePlugin( IUserEnumerationPlugin, 'foo' )
+ plugins.activatePlugin( IGroupEnumerationPlugin, 'foobar' )
+
+ self.failIf( zcuf.searchPrincipals( name='zope' ) )
+ # Note that groups are never found by name, only by id.
+ self.failUnless( len( zcuf.searchPrincipals( name='foo' ) ) == 1 )
+ user1 = zcuf.searchPrincipals( name='foo' )[0]
+ self.assertEqual(user1['principal_type'], 'user')
+ self.assertEqual(user1['id'], 'foo')
+ self.assertEqual(user1['login'], 'foo')
+
+ # Search for mixed case.
+ self.failUnless( len( zcuf.searchPrincipals( name='Foo' ) ) == 1 )
+ user2 = zcuf.searchPrincipals( name='Foo' )[0]
+ self.assertEqual(user1, user2)
+
+ # Search for upper case.
+ self.failUnless( len( zcuf.searchPrincipals( name='FOO' ) ) == 1 )
+ user3 = zcuf.searchPrincipals( name='FOO' )[0]
+ self.assertEqual(user1, user3)
+
+ def test_updateLoginName( self ):
+
+ from Products.PluggableAuthService.interfaces.plugins \
+ import IUserEnumerationPlugin
+
+ plugins = self._makePlugins()
+ zcuf = self._makeOne( plugins )
+
+ foo = self._makeMultiUserEnumerator(
+ {'id': 'JOE', 'login': 'Joe'},
+ {'id': 'BART', 'login': 'Bart'})
+ zcuf._setObject( 'foo', foo )
+
+ plugins = zcuf._getOb( 'plugins' )
+ plugins.activatePlugin( IUserEnumerationPlugin, 'foo' )
+
+ users = zcuf.searchUsers(login='Joe')
+ self.assertEqual(len(users), 1)
+ self.assertEqual(users[0]['id'], 'JOE')
+ self.assertEqual(users[0]['login'], 'Joe')
+
+ # Change the login name.
+ zcuf.updateLoginName('JOE', 'James')
+ users = zcuf.searchUsers(login='James')
+ self.assertEqual(len(users), 1)
+ self.assertEqual(users[0]['id'], 'JOE')
+ self.assertEqual(users[0]['login'], 'James')
+
+ # Try lowercase
+ zcuf.login_transform = 'lower'
+ zcuf.updateLoginName('JOE', 'James')
+
+ users = zcuf.searchUsers(login='James')
+ self.assertEqual(len(users), 1)
+ self.assertEqual(users[0]['id'], 'JOE')
+ self.assertEqual(users[0]['login'], 'james')
+
+ users = zcuf.searchUsers(login='james')
+ self.assertEqual(len(users), 1)
+ self.assertEqual(users[0]['id'], 'JOE')
+ self.assertEqual(users[0]['login'], 'james')
+
+ def test_updateOwnLoginName( self ):
+
+ from Products.PluggableAuthService.interfaces.plugins \
+ import IUserEnumerationPlugin
+
+ plugins = self._makePlugins()
+ zcuf = self._makeOne( plugins )
+
+ foo = self._makeMultiUserEnumerator(
+ {'id': 'bart', 'login': 'bart'},
+ {'id': 'joe', 'login': 'joe'})
+ zcuf._setObject( 'foo', foo )
+
+ plugins = zcuf._getOb( 'plugins' )
+ plugins.activatePlugin( IUserEnumerationPlugin, 'foo' )
+
+ users = zcuf.searchUsers(login='joe')
+ self.assertEqual(len(users), 1)
+ self.assertEqual(users[0]['id'], 'joe')
+ self.assertEqual(users[0]['login'], 'joe')
+ users = zcuf.searchUsers(login='bart')
+ self.assertEqual(len(users), 1)
+ self.assertEqual(users[0]['id'], 'bart')
+ self.assertEqual(users[0]['login'], 'bart')
+
+ # Changing the login name will not work when you are not logged in.
+ zcuf.updateOwnLoginName('james')
+ users = zcuf.searchUsers(login='james')
+ self.assertEqual(len(users), 0)
+
+ # Fake a login.
+ newSecurityManager(None, FauxUser('joe', 'joe'))
+ zcuf.updateOwnLoginName('james')
+
+ users = zcuf.searchUsers(login='james')
+ self.assertEqual(len(users), 1)
+ self.assertEqual(users[0]['id'], 'joe')
+ self.assertEqual(users[0]['login'], 'james')
+
+ # The login for bart has not changed.
+ users = zcuf.searchUsers(login='bart')
+ self.assertEqual(len(users), 1)
+ self.assertEqual(users[0]['id'], 'bart')
+ self.assertEqual(users[0]['login'], 'bart')
+
+ def test_updateAllLoginNames( self ):
+
+ from Products.PluggableAuthService.interfaces.plugins \
+ import IUserEnumerationPlugin
+
+ plugins = self._makePlugins()
+ zcuf = self._makeOne( plugins )
+
+ foo = self._makeMultiUserEnumerator(
+ {'id': 'JOE', 'login': 'Joe'},
+ {'id': 'BART', 'login': 'Bart'})
+ zcuf._setObject( 'foo', foo )
+
+ plugins = zcuf._getOb( 'plugins' )
+ plugins.activatePlugin( IUserEnumerationPlugin, 'foo' )
+
+ users = foo.enumerateUsers(login='Joe')
+ self.assertEqual(len(users), 1)
+ self.assertEqual(users[0]['id'], 'JOE')
+ self.assertEqual(users[0]['login'], 'Joe')
+ users = foo.enumerateUsers(login='Bart')
+ self.assertEqual(len(users), 1)
+
+ # Update all login names. The dummy updater makes every login
+ # name lowercase.
+ zcuf.updateAllLoginNames()
+
+ self.assertEqual(len(foo.enumerateUsers(login='Joe')), 0)
+ self.assertEqual(len(foo.enumerateUsers(login='joe')), 1)
+ self.assertEqual(len(foo.enumerateUsers(login='Bart')), 0)
+ self.assertEqual(len(foo.enumerateUsers(login='bart')), 1)
+
+ # PAS applies the login_transform when searching for users.
+ zcuf.login_transform = 'lower'
+ self.assertEqual(len(zcuf.searchUsers(login='Joe')), 1)
+ self.assertEqual(len(zcuf.searchUsers(login='joe')), 1)
+ self.assertEqual(len(zcuf.searchUsers(login='Bart')), 1)
+ self.assertEqual(len(zcuf.searchUsers(login='bart')), 1)
+
def test_no_challenger(self):
# make sure that the response's _unauthorized gets propogated
# if no challengers exist (or have fired)
@@ -2228,6 +2763,54 @@
extracted = creds_store.extractCredentials(request)
self.failUnless(len(extracted.keys()) == 0)
+ def test_applyTransform( self ):
+ zcuf = self._makeOne()
+ self.assertEqual(zcuf.applyTransform(' User '), ' User ')
+ zcuf.login_transform = 'lower'
+ self.assertEqual(zcuf.applyTransform(' User '), 'user')
+ self.assertEqual(zcuf.applyTransform(u' User '), u'user')
+ self.assertEqual(zcuf.applyTransform(''), '')
+ self.assertEqual(zcuf.applyTransform(None), None)
+ self.assertEqual(zcuf.applyTransform([' User ']), ['user'])
+ self.assertEqual(zcuf.applyTransform(('User', ' joe ', 'Diana')),
+ ('user', 'joe', 'diana'))
+ self.assertRaises(TypeError, zcuf.applyTransform, 123)
+ zcuf.login_transform = 'upper'
+ self.assertEqual(zcuf.applyTransform(' User '), 'USER')
+ # Let's not fail just because a user has accidentally left a
+ # space at the end of the login_transform name. That could
+ # lead to hard-to-debug behaviour.
+ zcuf.login_transform = ' upper '
+ self.assertEqual(zcuf.applyTransform(' User '), 'USER')
+ # We would want to test what happens when the login_transform
+ # attribute is not there, but the following only removes it
+ # from the instance, not the class. Oh well.
+ del zcuf.login_transform
+ self.assertEqual(zcuf.applyTransform(' User '), ' User ')
+ zcuf.login_transform = 'nonexisting'
+ self.assertEqual(zcuf.applyTransform(' User '), ' User ')
+
+ def test_get_login_transform_method( self ):
+ zcuf = self._makeOne()
+ self.assertEqual(zcuf._get_login_transform_method(), None)
+ zcuf.login_transform = 'lower'
+ self.assertEqual(zcuf._get_login_transform_method(), zcuf.lower)
+ zcuf.login_transform = 'upper'
+ self.assertEqual(zcuf._get_login_transform_method(), zcuf.upper)
+ # Let's not fail just because a user has accidentally left a
+ # space at the end of the login_transform name. That could
+ # lead to hard-to-debug behaviour.
+ zcuf.login_transform = ' upper '
+ self.assertEqual(zcuf._get_login_transform_method(), zcuf.upper)
+ # We would want to test what happens when the login_transform
+ # attribute is not there, but the following only removes it
+ # from the instance, not the class. Oh well.
+ del zcuf.login_transform
+ self.assertEqual(zcuf._get_login_transform_method(), None)
+ zcuf.login_transform = 'nonexisting'
+ self.assertEqual(zcuf._get_login_transform_method(), None)
+
+
if __name__ == "__main__":
unittest.main()
Modified: Products.PluggableAuthService/trunk/Products/PluggableAuthService/tests/test_exportimport.py
===================================================================
--- Products.PluggableAuthService/trunk/Products/PluggableAuthService/tests/test_exportimport.py 2013-01-22 10:34:39 UTC (rev 129076)
+++ Products.PluggableAuthService/trunk/Products/PluggableAuthService/tests/test_exportimport.py 2013-01-22 10:36:26 UTC (rev 129077)
@@ -128,9 +128,11 @@
self.assertEqual(filename, 'PAS/.properties')
self.assertEqual(content_type, 'text/plain')
lines = filter(None, [x.strip() for x in text.splitlines()])
- self.assertEqual(len(lines), 2)
+ lines = sorted(lines)
+ self.assertEqual(len(lines), 3)
self.assertEqual(lines[0], '[DEFAULT]')
- self.assertEqual(lines[1], 'title =')
+ self.assertEqual(lines[1], 'login_transform =')
+ self.assertEqual(lines[2], 'title =')
filename, text, content_type = context._wrote[2]
self.assertEqual(filename, 'PAS/pluginregistry.xml')
@@ -173,9 +175,11 @@
self.assertEqual(filename, 'PAS/.properties')
self.assertEqual(content_type, 'text/plain')
lines = filter(None, [x.strip() for x in text.splitlines()])
- self.assertEqual(len(lines), 2)
+ lines = sorted(lines)
+ self.assertEqual(len(lines), 3)
self.assertEqual(lines[0], '[DEFAULT]')
- self.assertEqual(lines[1], 'title =')
+ self.assertEqual(lines[1], 'login_transform =')
+ self.assertEqual(lines[2], 'title =')
filename, text, content_type = context._wrote[2]
self.assertEqual(filename, 'PAS/pluginregistry.xml')
Modified: Products.PluggableAuthService/trunk/buildout.cfg
===================================================================
--- Products.PluggableAuthService/trunk/buildout.cfg 2013-01-22 10:34:39 UTC (rev 129076)
+++ Products.PluggableAuthService/trunk/buildout.cfg 2013-01-22 10:36:26 UTC (rev 129077)
@@ -7,6 +7,8 @@
http://pypi.python.org/packages/source/P/Products.GenericSetup/
http://pypi.python.org/packages/source/P/Products.PluginRegistry/
http://pypi.python.org/packages/source/f/five.localsitemanager/
+extends =
+ http://download.zope.org/Zope2/index/2.13.19/versions.cfg
[interpreter]
recipe = zc.recipe.egg
More information about the checkins
mailing list