[Checkins] SVN: PluggableAuthService/trunk/ Merged r69484:70143
from shh-authentication-caching branch.
Stefan H. Holek
stefan at epy.co.at
Wed Sep 13 07:45:07 EDT 2006
Log message for revision 70144:
Merged r69484:70143 from shh-authentication-caching branch.
Features Added
- Implemented authentication caching in _extractUserIds.
- Ported standard user folder tests from the AccessControl test suite.
Bugs Fixed
- ZODBUserManager: Already encrypted passwords were encrypted again in
addUser and updateUserPassword.
(http://www.zope.org/Collectors/Zope/1926)
- Made sure the emergency user via HTTP basic auth always wins, no matter
how borken the plugin landscape.
Changed:
U PluggableAuthService/trunk/PluggableAuthService.py
U PluggableAuthService/trunk/doc/CHANGES.txt
U PluggableAuthService/trunk/plugins/ZODBUserManager.py
U PluggableAuthService/trunk/plugins/tests/test_ZODBUserManager.py
A PluggableAuthService/trunk/tests/pastc.py
A PluggableAuthService/trunk/tests/test_MoreCaching.py
U PluggableAuthService/trunk/tests/test_PluggableAuthService.py
A PluggableAuthService/trunk/tests/test_UserFolder.py
A PluggableAuthService/trunk/tests/test_utils.py
U PluggableAuthService/trunk/utils.py
-=-
Modified: PluggableAuthService/trunk/PluggableAuthService.py
===================================================================
--- PluggableAuthService/trunk/PluggableAuthService.py 2006-09-13 11:22:29 UTC (rev 70143)
+++ PluggableAuthService/trunk/PluggableAuthService.py 2006-09-13 11:45:05 UTC (rev 70144)
@@ -85,6 +85,7 @@
from PropertiedUser import PropertiedUser
from utils import _wwwdir
from utils import createViewName
+from utils import createKeywords
from utils import classImplements
security = ModuleSecurityInfo(
@@ -536,9 +537,6 @@
a user; accumulate a list of the IDs of such users over all
our authentication and extraction plugins.
"""
- result = []
- user_ids = []
-
try:
extractors = plugins.listPlugins( IExtractionPlugin )
except _SWALLOWABLE_PLUGIN_EXCEPTIONS:
@@ -554,6 +552,8 @@
logger.debug('Authenticator plugin listing error', exc_info=True)
authenticators = ()
+ result = []
+
for extractor_id, extractor in extractors:
try:
@@ -568,24 +568,32 @@
try:
credentials[ 'extractor' ] = extractor_id # XXX: in key?
+ # Test if ObjectCacheEntries.aggregateIndex would work
items = credentials.items()
items.sort()
except _SWALLOWABLE_PLUGIN_EXCEPTIONS:
logger.debug( 'Credentials error: %s' % credentials
, exc_info=True
)
- else:
- user_ids = []
+ continue
- if not user_ids:
+ # First try to authenticate against the emergency
+ # user and return immediately if authenticated
+ user_id, name = self._tryEmergencyUserAuthentication(
+ credentials )
- # first try to authenticate against the emergency
- # user, and return immediately if authenticated
- user_id, name = self._tryEmergencyUserAuthentication(
- credentials )
+ if user_id is not None:
+ return [ ( user_id, name ) ]
- if user_id is not None:
- return [ ( user_id, name ) ]
+ # Now see if the user ids can be retrieved from the cache
+ view_name = createViewName('_extractUserIds', credentials.get('login'))
+ keywords = createKeywords(**credentials)
+ user_ids = self.ZCacheable_get( view_name=view_name
+ , keywords=keywords
+ , default=None
+ )
+ if user_ids is None:
+ user_ids = []
for authenticator_id, auth in authenticators:
@@ -607,14 +615,20 @@
if user_id is not None:
user_ids.append( (user_id, info) )
+ if user_ids:
+ self.ZCacheable_set( user_ids
+ , view_name=view_name
+ , keywords=keywords
+ )
+
result.extend( user_ids )
- if not user_ids:
- user_id, name = self._tryEmergencyUserAuthentication(
- DumbHTTPExtractor().extractCredentials( request ) )
+ # Emergency user via HTTP basic auth always wins
+ user_id, name = self._tryEmergencyUserAuthentication(
+ DumbHTTPExtractor().extractCredentials( request ) )
- if user_id is not None:
- result.append( ( user_id, name ) )
+ if user_id is not None:
+ return [ ( user_id, name ) ]
return result
@@ -700,10 +714,8 @@
return self._emergency_user
# See if the user can be retrieved from the cache
- view_name = '_findUser-%s' % user_id
- keywords = { 'user_id' : user_id
- , 'name' : name
- }
+ view_name = createViewName('_findUser', user_id)
+ keywords = createKeywords(user_id=user_id, name=name)
user = self.ZCacheable_get( view_name=view_name
, keywords=keywords
, default=None
@@ -761,8 +773,9 @@
if criteria:
view_name = createViewName('_verifyUser', user_id or login)
+ keywords = createKeywords(**criteria)
cached_info = self.ZCacheable_get( view_name=view_name
- , keywords=criteria
+ , keywords=keywords
, default=None
)
@@ -780,7 +793,7 @@
# Put the computed value into the cache
self.ZCacheable_set( info[0]
, view_name=view_name
- , keywords=criteria
+ , keywords=keywords
)
return info[0]
@@ -928,6 +941,7 @@
for useradder_id, useradder in useradders:
if useradder.doAddUser( login, password ):
+ # XXX: Adds user to cache, but without roles...
user = self.getUser( login )
break
Modified: PluggableAuthService/trunk/doc/CHANGES.txt
===================================================================
--- PluggableAuthService/trunk/doc/CHANGES.txt 2006-09-13 11:22:29 UTC (rev 70143)
+++ PluggableAuthService/trunk/doc/CHANGES.txt 2006-09-13 11:45:05 UTC (rev 70144)
@@ -1,5 +1,23 @@
PluggableAuthService changelog
+ PluggableAuthService 1.5 (unreleased)
+
+ Features Added
+
+ - Implemented authentication caching in _extractUserIds.
+
+ - Ported standard user folder tests from the AccessControl test suite.
+
+ Bugs Fixed
+
+ - ZODBUserManager: Already encrypted passwords were encrypted again in
+ addUser and updateUserPassword.
+ (http://www.zope.org/Collectors/Zope/1926)
+
+ - Made sure the emergency user via HTTP basic auth always wins, no matter
+ how borken the plugin landscape.
+
+
PluggableAuthService 1.4 (2006/08/28)
Bugs Fixed
Modified: PluggableAuthService/trunk/plugins/ZODBUserManager.py
===================================================================
--- PluggableAuthService/trunk/plugins/ZODBUserManager.py 2006-09-13 11:22:29 UTC (rev 70143)
+++ PluggableAuthService/trunk/plugins/ZODBUserManager.py 2006-09-13 11:45:05 UTC (rev 70144)
@@ -279,7 +279,7 @@
if self._login_to_userid.get( login_name ) is not None:
raise KeyError, 'Duplicate login name: %s' % login_name
- self._user_passwords[ user_id ] = AuthEncoding.pw_encrypt( password )
+ self._user_passwords[ user_id ] = self._pw_encrypt( password)
self._login_to_userid[ login_name ] = user_id
self._userid_to_login[ user_id ] = login_name
@@ -322,9 +322,19 @@
raise KeyError, 'Invalid user ID: %s' % user_id
if password:
- digested = AuthEncoding.pw_encrypt( password )
- self._user_passwords[ user_id ] = digested
+ self._user_passwords[ user_id ] = self._pw_encrypt( password )
+ security.declarePrivate( '_pw_encrypt' )
+ def _pw_encrypt( self, password ):
+ """Returns the AuthEncoding encrypted password
+
+ If 'password' is already encrypted, it is returned
+ as is and not encrypted again.
+ """
+ if AuthEncoding.is_encrypted( password ):
+ return password
+ return AuthEncoding.pw_encrypt( password )
+
#
# ZMI
#
Modified: PluggableAuthService/trunk/plugins/tests/test_ZODBUserManager.py
===================================================================
--- PluggableAuthService/trunk/plugins/tests/test_ZODBUserManager.py 2006-09-13 11:22:29 UTC (rev 70143)
+++ PluggableAuthService/trunk/plugins/tests/test_ZODBUserManager.py 2006-09-13 11:45:05 UTC (rev 70144)
@@ -426,7 +426,83 @@
info = zum.enumerateUsers(id='special__luser', exact_match=True)
self.assertEqual(len(info), 0)
+ def test_addUser_with_not_yet_encrypted_password(self):
+ # See collector #1869 && #1926
+ from AccessControl.AuthEncoding import is_encrypted
+ USER_ID = 'not_yet_encrypted'
+ PASSWORD = 'password'
+
+ self.failIf(is_encrypted(PASSWORD))
+
+ zum = self._makeOne()
+ zum.addUser(USER_ID, USER_ID, PASSWORD)
+
+ uid_and_info = zum.authenticateCredentials(
+ { 'login': USER_ID
+ , 'password': PASSWORD
+ })
+
+ self.assertEqual(uid_and_info, (USER_ID, USER_ID))
+
+ def test_addUser_with_preencrypted_password(self):
+ # See collector #1869 && #1926
+ from AccessControl.AuthEncoding import pw_encrypt
+
+ USER_ID = 'already_encrypted'
+ PASSWORD = 'password'
+
+ ENCRYPTED = pw_encrypt(PASSWORD)
+
+ zum = self._makeOne()
+ zum.addUser(USER_ID, USER_ID, ENCRYPTED)
+
+ uid_and_info = zum.authenticateCredentials(
+ { 'login': USER_ID
+ , 'password': PASSWORD
+ })
+
+ self.assertEqual(uid_and_info, (USER_ID, USER_ID))
+
+ def test_updateUserPassword_with_not_yet_encrypted_password(self):
+ from AccessControl.AuthEncoding import is_encrypted
+
+ USER_ID = 'not_yet_encrypted'
+ PASSWORD = 'password'
+
+ self.failIf(is_encrypted(PASSWORD))
+
+ zum = self._makeOne()
+ zum.addUser(USER_ID, USER_ID, '')
+ zum.updateUserPassword(USER_ID, PASSWORD)
+
+ uid_and_info = zum.authenticateCredentials(
+ { 'login': USER_ID
+ , 'password': PASSWORD
+ })
+
+ self.assertEqual(uid_and_info, (USER_ID, USER_ID))
+
+ def test_updateUserPassword_with_preencrypted_password(self):
+ from AccessControl.AuthEncoding import pw_encrypt
+
+ USER_ID = 'already_encrypted'
+ PASSWORD = 'password'
+
+ ENCRYPTED = pw_encrypt(PASSWORD)
+
+ zum = self._makeOne()
+ zum.addUser(USER_ID, USER_ID, '')
+ zum.updateUserPassword(USER_ID, ENCRYPTED)
+
+ uid_and_info = zum.authenticateCredentials(
+ { 'login': USER_ID
+ , 'password': PASSWORD
+ })
+
+ self.assertEqual(uid_and_info, (USER_ID, USER_ID))
+
+
if __name__ == "__main__":
unittest.main()
Copied: PluggableAuthService/trunk/tests/pastc.py (from rev 70143, PluggableAuthService/branches/shh-authentication-caching/tests/pastc.py)
Copied: PluggableAuthService/trunk/tests/test_MoreCaching.py (from rev 70143, PluggableAuthService/branches/shh-authentication-caching/tests/test_MoreCaching.py)
Modified: PluggableAuthService/trunk/tests/test_PluggableAuthService.py
===================================================================
--- PluggableAuthService/trunk/tests/test_PluggableAuthService.py 2006-09-13 11:22:29 UTC (rev 70143)
+++ PluggableAuthService/trunk/tests/test_PluggableAuthService.py 2006-09-13 11:45:05 UTC (rev 70144)
@@ -572,7 +572,7 @@
self.assertEqual( user_ids[0][0], 'qux' )
self.assertEqual( user_ids[1][0], 'foo' )
- def test__extractUserIds_broken_extractor( self ):
+ def test__extractUserIds_broken_extractor_before_good_extractor( self ):
from Products.PluggableAuthService.interfaces.plugins \
import IExtractionPlugin, IAuthenticationPlugin
@@ -595,8 +595,8 @@
plugins = zcuf._getOb( 'plugins' )
- plugins.activatePlugin( IExtractionPlugin, 'borked' )
- plugins.activatePlugin( IExtractionPlugin, 'login' )
+ plugins.activatePlugin( IExtractionPlugin, 'borked' ) # 1
+ plugins.activatePlugin( IExtractionPlugin, 'login' ) # 2
plugins.activatePlugin( IAuthenticationPlugin, 'login' )
request = self._makeRequest( form={ 'login' : 'foo'
@@ -609,11 +609,48 @@
self.assertEqual( len( user_ids ), 1 )
self.assertEqual( user_ids[0][0], 'foo' )
- def test_authenticate_emergency_user_with_broken_extractor( self ):
+ def test__extractUserIds_broken_extractor_after_good_extractor( 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 )
+
+ borked = DummyPlugin()
+ directlyProvides( borked, IExtractionPlugin )
+ borked.extractCredentials = lambda req: 'abc'
+
+ zcuf._setObject( 'borked', borked )
+
+ plugins = zcuf._getOb( 'plugins' )
+
+ plugins.activatePlugin( IExtractionPlugin, 'login' ) # 1
+ plugins.activatePlugin( IExtractionPlugin, 'borked' ) # 2
+ plugins.activatePlugin( IAuthenticationPlugin, 'login' )
+
+ request = self._makeRequest( form={ 'login' : 'foo'
+ , 'password' : 'bar' } )
+
+ user_ids = zcuf._extractUserIds( request=request
+ , plugins=zcuf.plugins
+ )
+
+ self.assertEqual( len( user_ids ), 1 )
+ self.assertEqual( user_ids[0][0], 'foo' )
+
+ def test__extractUserIds_authenticate_emergency_user_with_broken_extractor( self ):
+
+ from Products.PluggableAuthService.interfaces.plugins \
+ import IExtractionPlugin, IAuthenticationPlugin
+
from AccessControl.User import UnrestrictedUser
from Products.PluggableAuthService import PluggableAuthService
@@ -624,21 +661,61 @@
PluggableAuthService.emergency_user = eu
+ try:
+ plugins = self._makePlugins()
+ zcuf = self._makeOne( plugins )
+
+ borked = DummyPlugin()
+ directlyProvides( borked, IExtractionPlugin )
+ borked.extractCredentials = lambda req: 'abc'
+
+ zcuf._setObject( 'borked', borked )
+
+ plugins = zcuf._getOb( 'plugins' )
+
+ plugins.activatePlugin( IExtractionPlugin, 'borked' )
+
+ request = self._makeRequest( form={ 'login' : eu.getUserName()
+ , 'password' : eu._getPassword() } )
+
+ 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 test__extractUserIds_broken_authenticator_before_good_authenticator( 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 )
+
borked = DummyPlugin()
- directlyProvides( borked, IExtractionPlugin )
- borked.extractCredentials = lambda req: 'abc'
+ directlyProvides( borked, IAuthenticationPlugin )
+ borked.authenticateCredentials = lambda creds: creds['nonesuch']
zcuf._setObject( 'borked', borked )
plugins = zcuf._getOb( 'plugins' )
- plugins.activatePlugin( IExtractionPlugin, 'borked' )
+ plugins.activatePlugin( IExtractionPlugin, 'login' )
+ plugins.activatePlugin( IAuthenticationPlugin, 'borked' ) # 1
+ plugins.activatePlugin( IAuthenticationPlugin, 'login' ) # 2
- request = self._makeRequest( form={ 'login' : eu.getUserName()
- , 'password' : eu._getPassword() } )
+ request = self._makeRequest( form={ 'login' : 'foo'
+ , 'password' : 'bar' } )
user_ids = zcuf._extractUserIds( request=request
, plugins=zcuf.plugins
@@ -647,10 +724,8 @@
self.assertEqual( len( user_ids ), 1 )
self.assertEqual( user_ids[0][0], 'foo' )
- PluggableAuthService.emergency_user = old_eu
+ def test__extractUserIds_broken_authenticator_after_good_authenticator( self ):
- def test__extractUserIds_broken_authenticator( self ):
-
from Products.PluggableAuthService.interfaces.plugins \
import IExtractionPlugin, IAuthenticationPlugin
@@ -673,8 +748,8 @@
plugins = zcuf._getOb( 'plugins' )
plugins.activatePlugin( IExtractionPlugin, 'login' )
- plugins.activatePlugin( IAuthenticationPlugin, 'borked' )
- plugins.activatePlugin( IAuthenticationPlugin, 'login' )
+ plugins.activatePlugin( IAuthenticationPlugin, 'login' ) # 1
+ plugins.activatePlugin( IAuthenticationPlugin, 'borked' ) # 2
request = self._makeRequest( form={ 'login' : 'foo'
, 'password' : 'bar' } )
@@ -686,6 +761,101 @@
self.assertEqual( len( user_ids ), 1 )
self.assertEqual( user_ids[0][0], 'foo' )
+ def test__extractUserIds_authenticate_emergency_user_with_broken_authenticator( self ):
+
+ from Products.PluggableAuthService.interfaces.plugins \
+ import IExtractionPlugin, IAuthenticationPlugin
+
+ from AccessControl.User import UnrestrictedUser
+
+ from Products.PluggableAuthService import PluggableAuthService
+
+ old_eu = PluggableAuthService.emergency_user
+
+ eu = UnrestrictedUser( 'foo', 'bar', ( 'manage', ), () )
+
+ PluggableAuthService.emergency_user = eu
+
+ try:
+ plugins = self._makePlugins()
+ zcuf = self._makeOne( plugins )
+
+ login = DummyPlugin()
+ directlyProvides( login, IExtractionPlugin )
+
+ # Make the first attempt at emergency user authentication fail
+ # (but not the extractor itself).
+ login.extractCredentials = lambda req: {'login': '', 'password': ''}
+
+ zcuf._setObject( 'login', login )
+
+ borked = DummyPlugin()
+ directlyProvides( borked, IAuthenticationPlugin )
+ borked.authenticateCredentials = lambda creds: creds['nonesuch']
+
+ zcuf._setObject( 'borked', borked )
+
+ plugins = zcuf._getOb( 'plugins' )
+
+ plugins.activatePlugin( IExtractionPlugin, 'login' )
+ plugins.activatePlugin( IAuthenticationPlugin, 'borked' )
+
+ request = self._makeRequest( form={ 'login' : eu.getUserName()
+ , 'password' : eu._getPassword() } )
+
+ 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 test__extractUserIds_emergency_user_always_wins( self ):
+
+ from Products.PluggableAuthService.interfaces.plugins \
+ import IExtractionPlugin, IAuthenticationPlugin
+
+ from AccessControl.User import UnrestrictedUser
+
+ from Products.PluggableAuthService import PluggableAuthService
+
+ old_eu = PluggableAuthService.emergency_user
+
+ 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 )
+
+ 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 test__getObjectContext_no_steps( self ):
zcuf = self._makeOne()
Copied: PluggableAuthService/trunk/tests/test_UserFolder.py (from rev 70143, PluggableAuthService/branches/shh-authentication-caching/tests/test_UserFolder.py)
Copied: PluggableAuthService/trunk/tests/test_utils.py (from rev 70143, PluggableAuthService/branches/shh-authentication-caching/tests/test_utils.py)
Modified: PluggableAuthService/trunk/utils.py
===================================================================
--- PluggableAuthService/trunk/utils.py 2006-09-13 11:22:29 UTC (rev 70143)
+++ PluggableAuthService/trunk/utils.py 2006-09-13 11:45:05 UTC (rev 70144)
@@ -13,6 +13,7 @@
#
##############################################################################
import os
+import sha
import unittest
from types import TupleType, ListType
@@ -197,13 +198,36 @@
return suite
+
+def makestr(s):
+ """Converts 's' to a non-Unicode string"""
+ if isinstance(s, unicode):
+ s = s.encode('utf-8')
+ return str(s)
+
def createViewName(method_name, user_handle=None):
"""
Centralized place for creating the "View Name" that identifies
- a ZCacheable record in a PASRAMCacheManager
+ a ZCacheable record in a RAMCacheManager
"""
if not user_handle:
- return method_name
+ return makestr(method_name)
else:
- return '%s-%s' % (method_name, user_handle)
+ return '%s-%s' % (makestr(method_name), makestr(user_handle))
+def createKeywords(**kw):
+ """
+ Centralized place for creating the keywords that identify
+ a ZCacheable record in a RAMCacheManager.
+
+ Keywords are hashed so we don't accidentally expose sensitive
+ information.
+ """
+ keywords = sha.new()
+
+ for k, v in kw.items():
+ keywords.update(makestr(k))
+ keywords.update(makestr(v))
+
+ return {'keywords': keywords.hexdigest()}
+
More information about the Checkins
mailing list