From 38e832d589da107e0259584a06cf376b9eb7a9af Mon Sep 17 00:00:00 2001 From: chaoflow Date: Fri, 9 Jan 2009 09:32:42 +0000 Subject: [PATCH] paula.pasplugins: implementation of many interface - unclean working code git-svn-id: https://svn.plone.org/svn/collective/paula/trunk@78623 db7f04ef-aaf3-0310-a811-c281ed44c4ad --- .../src/paula/pasplugins/integration.txt | 54 +++++++++- .../src/paula/pasplugins/plugins/auth.py | 118 +++++++++++++++------ .../src/paula/pasplugins/plugins/groups.py | 5 +- .../src/paula/pasplugins/plugins/properties.py | 24 ++++- .../profiles/default/componentregistry.xml | 6 ++ .../src/paula/pasplugins/setuphandlers.py | 21 +++- .../src/paula/pasplugins/sheet.py | 13 ++- .../src/paula/pasplugins/tests/fake_pau_ap.py | 20 ++++ .../src/paula/pau_addons/interfaces.py | 11 +- paula.pau_addons/src/paula/pau_addons/utils.py | 99 ++++++++++++++++- 10 files changed, 321 insertions(+), 50 deletions(-) copy paula.pau_addons/src/paula/pau_addons/interfaces.py => paula.pasplugins/src/paula/pasplugins/sheet.py (78%) diff --git a/paula.pasplugins/src/paula/pasplugins/integration.txt b/paula.pasplugins/src/paula/pasplugins/integration.txt index 4c10a89..9125561 100644 --- a/paula.pasplugins/src/paula/pasplugins/integration.txt +++ b/paula.pasplugins/src/paula/pasplugins/integration.txt @@ -101,13 +101,28 @@ PAU is working as expected. The PAS plugins --------------- + >>> pas = portal.acl_users + >>> from paula.pasplugins.tests.fake_pau_ap import FAKE_CREDS >>> from paula.pasplugins.tests.fake_pau_ap import FAKE_LOGIN >>> from paula.pasplugins.tests.fake_pau_ap import FAKE_PASSWORD +Make sure our PAS plugins are always first + + >>> from paula.pasplugins.plugins.auth import paula_auth_ifs as aifs + >>> from paula.pasplugins.plugins.groups import paula_groups_ifs as gifs + >>> from paula.pasplugins.plugins.properties \ + ... import paula_properties_ifs as pifs + >>> [pas.plugins.listPlugins(x)[0][0] == 'paula_auth' for x in aifs] + [True, True, True, True, True] + >>> [pas.plugins.listPlugins(x)[0][0] == 'paula_groups' for x in gifs] + [True] + >>> [pas.plugins.listPlugins(x)[0][0] == 'paula_properties' for x in pifs] + [True] + Paula PAS auth plugin - >>> p = portal.acl_users.paula_auth.authenticateCredentials(FAKE_CREDS) + >>> p = pas.paula_auth.authenticateCredentials(FAKE_CREDS) >>> p ('fakelogin', 'fakelogin') @@ -127,6 +142,10 @@ Paula PAS properties plugin - the order might be unstable ('email', u'foo@bar.com') >>> psheet.propertyItems()[2] ('realname', u'fake user') + +Let's try to write properties + + >>> psheet.setProperty('fakelogin', 'foo', 'new value') Paula PAS group plugin @@ -145,16 +164,41 @@ Walk through the PAS API >>> pas.getUserById(FAKE_LOGIN) - >>> pas.searchUsers(id=FAKE_LOGIN)[0]['login'] + >>> pas.searchUsers(id=FAKE_LOGIN,exact_match=True)[0]['login'] 'fakelogin' - >>> pas.searchUsers(name=FAKE_LOGIN)[0]['login'] + >>> pas.searchUsers(name=FAKE_LOGIN,exact_match=True)[0]['login'] 'fakelogin' - >>> pas.searchPrincipals(id=FAKE_LOGIN)[0]['login'] + >>> pas.searchPrincipals(id=FAKE_LOGIN,exact_match=True)[0]['login'] 'fakelogin' - >>> pas.searchPrincipals(name=FAKE_LOGIN)[0]['login'] + >>> pas.searchPrincipals(name=FAKE_LOGIN,exact_match=True)[0]['login'] 'fakelogin' +Adding a user and changing the password: fakelogin should be "handled" by fake +authenticator, everything else should make it into source_users + + >>> pas._doAddUser('fakelogin', 'foo', [], []) + >>> list(pas.source_users.listUserIds()) + ['test_user_1_'] + + >>> pas.userSetPassword('fakelogin', 'foo') + >>> pas.userSetPassword('somebody', 'foo') + Traceback (most recent call last): + ... + RuntimeError: No user management plugins were able to successfully ... + + >>> pas._doAddUser('somebody', 'foo', [], []) + >>> list(pas.source_users.listUserIds()) + ['somebody', 'test_user_1_'] + + >>> pas.userSetPassword('fakelogin', 'foo') + >>> pas.userSetPassword('somebody', 'foo') + + >>> pas._doDelUser('somebody') + >>> list(pas.source_users.listUserIds()) + ['test_user_1_'] + + >>> pas._doDelUser('fakelogin') #XXX: This might be useful for a test of pas.validate() # >>> from Products.PluggableAuthService.tests.test_PluggableAuthService \ diff --git a/paula.pasplugins/src/paula/pasplugins/plugins/auth.py b/paula.pasplugins/src/paula/pasplugins/plugins/auth.py index 9f56bd7..20f15b3 100644 --- a/paula.pasplugins/src/paula/pasplugins/plugins/auth.py +++ b/paula.pasplugins/src/paula/pasplugins/plugins/auth.py @@ -22,16 +22,20 @@ __docformat__ = "plaintext" import UserDict -from AccessControl import ClassSecurityInfo from Globals import InitializeClass from Products.PageTemplates.PageTemplateFile import PageTemplateFile -#from Products.PlonePAS.interfaces.plugins import IUserIntrospection +from Products.PlonePAS.interfaces.capabilities import IDeleteCapability +from Products.PlonePAS.interfaces.capabilities import IPasswordSetCapability +from Products.PlonePAS.interfaces.plugins import IUserIntrospection +from Products.PlonePAS.interfaces.plugins import IUserManagement + from Products.PluggableAuthService.interfaces.plugins \ import IAuthenticationPlugin from Products.PluggableAuthService.interfaces.plugins import IUserAdderPlugin from Products.PluggableAuthService.interfaces.plugins \ import IUserEnumerationPlugin + from Products.PluggableAuthService.plugins.BasePlugin import BasePlugin from zope.app.security.interfaces import IAuthentication @@ -62,16 +66,21 @@ def addAuthenticationPlugin( dispatcher, id, title=None, REQUEST=None ): % dispatcher.absolute_url()) +paula_auth_ifs = ( + IAuthenticationPlugin, + IUserAdderPlugin, + IUserEnumerationPlugin, + IUserManagement, +# IUserIntrospection, + ) + class AuthenticationPlugin(BasePlugin): """ """ - security = ClassSecurityInfo() - implements( - IAuthenticationPlugin, - IUserEnumerationPlugin, - IUserAdderPlugin, -# IUserIntrospection, + IDeleteCapability, + IPasswordSetCapability, + *paula_auth_ifs ) meta_type = "Paula PAS Authentication Plugin" @@ -82,7 +91,6 @@ class AuthenticationPlugin(BasePlugin): # IAuthenticationPlugin # - security.declarePrivate('authenticateCredentials') def authenticateCredentials(self, credentials): """ credentials -> (userid, login) @@ -148,16 +156,62 @@ class AuthenticationPlugin(BasePlugin): # IUserAdderPlugin # - security.declarePrivate( 'enumerateUsers' ) def doAddUser(self, login, password): - # if successful add return True + """ Add a user record to a User Manager, with the given login + and password + o Return a Boolean indicating whether a user was added or not + """ + pau = getUtility(IAuthentication) + if pau.addUser(login, password): + return True return False + + # IDeleteCapability + # + def allowDeletePrincipal(self, id): + """True iff this plugin can delete a certain user/group. + """ + #XXX: We need to check with PAU whether we can delete the principal + pau = getUtility(IAuthentication) + return pau.allowDeletePrincipal(id) + + # IPasswordSetCapability + # + def allowPasswordSet(self, id): + """True iff this plugin can set the password of a certain user. + """ + pau = getUtility(IAuthentication) + return pau.allowPasswordSet(id) + + # IUserManagamenet + # + def doChangeUser(self, login, password, **kws): + """ + Change a user's password (differs from role) roles are set in + the pas engine api for the same but are set via a role + manager) + """ + pau = getUtility(IAuthentication) + if not pau.doChangeUser(login, password, **kws): + # maybe should be moved to the PAU auth plugins + raise RuntimeError + + def doDeleteUser(self, login): + """ + Remove a user record from a User Manager, with the given login + and password + + o Return a Boolean indicating whether a user was removed or + not + """ + pau = getUtility(IAuthentication) + return pau.delPrincipal(login) + # IUserEnumerationPlugin # - security.declarePrivate( 'enumerateUsers' ) def enumerateUsers( self , id=None , login=None @@ -168,14 +222,12 @@ class AuthenticationPlugin(BasePlugin): ): pau = getUtility(IAuthentication) - #XXX: currently we only know exact match - #if exact_match: - if exact_match or not exact_match: + if exact_match: #XXX: id and login are treated equal - fixme! try: principal = pau.getPrincipal( id or login) except PrincipalLookupError: - return ({},) + return () #XXX: do something with the data from the returned principal?! return ({ @@ -184,24 +236,24 @@ class AuthenticationPlugin(BasePlugin): 'pluginid': self.getId(), },) - # IUserIntrospection - # - #security.declarePrivate('getUserIds') - #def getUserIds(self): - # return ('fakelogin',) - - # IUserIntrospection - # - #security.declarePrivate('getUserNames') - #def getUserNames(self): - # return ('fakelogin',) + # XXX: no exact_match, we need to search for the user in PAU - # IUserIntrospection - # - #security.declarePrivate('getUsers') - #def getUsers(self): - # """ - # Return a list of users +# # IUserIntrospection +# # +# def getUserIds(self): +# # called eg when going into the memberdata tool contents +# return ('fakelogin',) +# +# # IUserIntrospection +# # +# def getUserNames(self): +# return ('fakelogin',) +# +# # IUserIntrospection +# # +# def getUsers(self): +# """ +# Return a list of users # # XXX DON'T USE THIS, it will kill performance # """ diff --git a/paula.pasplugins/src/paula/pasplugins/plugins/groups.py b/paula.pasplugins/src/paula/pasplugins/plugins/groups.py index b8fda36..a3008f3 100644 --- a/paula.pasplugins/src/paula/pasplugins/plugins/groups.py +++ b/paula.pasplugins/src/paula/pasplugins/plugins/groups.py @@ -55,13 +55,16 @@ def addGroupsPlugin( dispatcher, id, title=None, REQUEST=None ): 'paula.pasplugins.GroupsPlugin+added.' % dispatcher.absolute_url()) +paula_groups_ifs = ( + IGroupsPlugin, + ) class GroupsPlugin(BasePlugin): """ """ security = ClassSecurityInfo() - implements(IGroupsPlugin) + implements(*paula_groups_ifs) meta_type = "Paula PAS Groups Plugin" diff --git a/paula.pasplugins/src/paula/pasplugins/plugins/properties.py b/paula.pasplugins/src/paula/pasplugins/plugins/properties.py index d29bdff..6e7e779 100644 --- a/paula.pasplugins/src/paula/pasplugins/plugins/properties.py +++ b/paula.pasplugins/src/paula/pasplugins/plugins/properties.py @@ -25,7 +25,9 @@ from AccessControl import ClassSecurityInfo from Globals import InitializeClass from Products.PageTemplates.PageTemplateFile import PageTemplateFile + from Products.PlonePAS.sheet import MutablePropertySheet +from Products.PlonePAS.interfaces.plugins import IMutablePropertiesPlugin from Products.PluggableAuthService.plugins.BasePlugin import BasePlugin from Products.PluggableAuthService.interfaces.plugins \ @@ -33,6 +35,7 @@ from Products.PluggableAuthService.interfaces.plugins \ from zope.app.security.interfaces import IAuthentication from zope.app.security.interfaces import PrincipalLookupError + from zope.component import getUtility from zope.interface import alsoProvides, implements @@ -57,13 +60,19 @@ def addPropertiesPlugin( dispatcher, id, title=None, REQUEST=None ): 'paula.plonepas.PropertiesPlugin+added.' % dispatcher.absolute_url()) +paula_properties_ifs = ( + IPropertiesPlugin, + ) class PropertiesPlugin(BasePlugin): """ """ security = ClassSecurityInfo() - implements(IPropertiesPlugin) + implements( + IMutablePropertiesPlugin, + *paula_properties_ifs + ) meta_type = "Paula PAS Properties Plugin" @@ -150,6 +159,8 @@ class PropertiesPlugin(BasePlugin): # get principal from pau pau = getUtility(IAuthentication) try: + #XXX: cache at least per request! + #XXX: is called already in auth plugin principal = pau.getPrincipal(user.getId()) except PrincipalLookupError: return {} @@ -175,4 +186,15 @@ class PropertiesPlugin(BasePlugin): psheet = MutablePropertySheet(self.id, **properties) return psheet + def setPropertiesForUser(self, user, psheet): + """ + Set modified properties on the user persistently. + + Raise a ValueError if the property or property value is invalid + """ + #XXX: This is a hack - properties should not be handled by auth + pau = getUtility(IAuthentication) + props = dict(psheet.propertyItems()) + return pau.setPropertiesForUser(user, **props) + InitializeClass( PropertiesPlugin) diff --git a/paula.pasplugins/src/paula/pasplugins/profiles/default/componentregistry.xml b/paula.pasplugins/src/paula/pasplugins/profiles/default/componentregistry.xml index cfabf67..f579adf 100644 --- a/paula.pasplugins/src/paula/pasplugins/profiles/default/componentregistry.xml +++ b/paula.pasplugins/src/paula/pasplugins/profiles/default/componentregistry.xml @@ -2,9 +2,15 @@ + + diff --git a/paula.pasplugins/src/paula/pasplugins/setuphandlers.py b/paula.pasplugins/src/paula/pasplugins/setuphandlers.py index 7352c7b..3a80735 100644 --- a/paula.pasplugins/src/paula/pasplugins/setuphandlers.py +++ b/paula.pasplugins/src/paula/pasplugins/setuphandlers.py @@ -13,6 +13,10 @@ from Products.PlonePAS.Extensions.Install import activatePluginInterfaces from zope.app.security.interfaces import IAuthentication from zope.component import getUtility +from paula.pasplugins.plugins.auth import paula_auth_ifs +from paula.pasplugins.plugins.groups import paula_groups_ifs +from paula.pasplugins.plugins.properties import paula_properties_ifs + # only for testing purposes #from paula.pasplugins.tests.fake_pau_ap import AUTHPLUG_NAME @@ -23,25 +27,36 @@ def _setupPlugins(portal, out): uf = getToolByName(portal, 'acl_users') paula = uf.manage_addProduct['paula.pasplugins'] + plugins = uf.plugins + + def _move_to_top(interface, name): + while not plugins.listPlugins( + interface, + )[0][0] == name: + plugins.movePluginsUp(interface, [name]) + existing = uf.objectIds() if 'paula_auth' not in existing: paula.addAuthenticationPlugin('paula_auth') print >> out, "Added Paula PAS Authentication Plugin." activatePluginInterfaces(portal, 'paula_auth', out) - - #plugins = uf.plugins - #plugins.movePluginsUp(IAuthenticationPlugin, ['paula']) + for x in paula_auth_ifs: + _move_to_top(x, 'paula_auth') if 'paula_properties' not in existing: paula.addPropertiesPlugin('paula_properties') print >> out, "Added Paula PAS Properties Plugin." activatePluginInterfaces(portal, 'paula_properties', out) + for x in paula_properties_ifs: + _move_to_top(x, 'paula_properties') if 'paula_groups' not in existing: paula.addGroupsPlugin('paula_groups') print >> out, "Added Paula PAS Groups Plugin." activatePluginInterfaces(portal, 'paula_groups', out) + for x in paula_groups_ifs: + _move_to_top(x, 'paula_groups') credplugname = 'Paula: PAU CredentialsFromMappingPlugin' pau = getUtility(IAuthentication) diff --git a/paula.pau_addons/src/paula/pau_addons/interfaces.py b/paula.pasplugins/src/paula/pasplugins/sheet.py similarity index 78% copy from paula.pau_addons/src/paula/pau_addons/interfaces.py copy to paula.pasplugins/src/paula/pasplugins/sheet.py index 8dc5f95..2eefae1 100644 --- a/paula.pau_addons/src/paula/pau_addons/interfaces.py +++ b/paula.pasplugins/src/paula/pasplugins/sheet.py @@ -20,10 +20,13 @@ __author__ = "Florian Friesdorf " __docformat__ = "plaintext" -from zope.interface import Interface -from zope.interface.interfaces import IInterface -from zope.schema import TextLine +from Globals import Persistent +from Products.PlonePAS.sheet import MutablePropertySheet -class IPropertyInterface(IInterface): - """Interfaces providing this are interfaces defining properties. +class DataManager(object): + """ + """ + +class PersistentSheet(Persistent, MutablePropertySheet): + """ """ diff --git a/paula.pasplugins/src/paula/pasplugins/tests/fake_pau_ap.py b/paula.pasplugins/src/paula/pasplugins/tests/fake_pau_ap.py index c49f817..4ec31c0 100644 --- a/paula.pasplugins/src/paula/pasplugins/tests/fake_pau_ap.py +++ b/paula.pasplugins/src/paula/pasplugins/tests/fake_pau_ap.py @@ -108,3 +108,23 @@ class AuthenticatorPlugin(Acquisition.Explicit): login = id title = description = u"I am a fake user" return PrincipalInfo( id, login, title, description) + + + #XXX: not part of any interface at the moment + def addUser(self, login, password): + return login == FAKE_LOGIN + + def delPrincipal(self, id): + return id == FAKE_LOGIN + + def allowDeletePrincipal(self, id): + return id == FAKE_LOGIN + + def allowPasswordSet(self, id): + return id == FAKE_LOGIN + + def doChangeUser(self, login, password, **kws): + return login == FAKE_LOGIN + + def setPropertiesForUser(self, login, **props): + return login == FAKE_LOGIN diff --git a/paula.pau_addons/src/paula/pau_addons/interfaces.py b/paula.pau_addons/src/paula/pau_addons/interfaces.py index 8dc5f95..f7c437a 100644 --- a/paula.pau_addons/src/paula/pau_addons/interfaces.py +++ b/paula.pau_addons/src/paula/pau_addons/interfaces.py @@ -20,10 +20,19 @@ __author__ = "Florian Friesdorf " __docformat__ = "plaintext" -from zope.interface import Interface +from zope.app.authentication.interfaces import IPluggableAuthentication +from zope.app.security.interfaces import IAuthentication + from zope.interface.interfaces import IInterface from zope.schema import TextLine class IPropertyInterface(IInterface): """Interfaces providing this are interfaces defining properties. """ + +class IPaulaAuthentication(IAuthentication, IPluggableAuthentication): + """ + """ + def addUser(login, password): + """Add a user to the first authenticator plugin that supports it + """ diff --git a/paula.pau_addons/src/paula/pau_addons/utils.py b/paula.pau_addons/src/paula/pau_addons/utils.py index 14059d5..b1b7783 100644 --- a/paula.pau_addons/src/paula/pau_addons/utils.py +++ b/paula.pau_addons/src/paula/pau_addons/utils.py @@ -20,9 +20,13 @@ __author__ = "Florian Friesdorf " __docformat__ = "plaintext" -from zope.interface import alsoProvides +from zope.app.authentication.authentication import PluggableAuthentication +from zope.interface import alsoProvides, implements + +from paula.pau_addons.interfaces import IPaulaAuthentication from paula.pau_addons.interfaces import IPropertyInterface + def copy_properties( src, dest): """ A mockup destination @@ -92,3 +96,96 @@ def copy_properties( src, dest): # Principals exist at all. #prop = property( lambda self: getattr(principal, name)) #setattr(user, name, prop) + + +class PaulaAuthentication(PluggableAuthentication): + """ + """ + implements(IPaulaAuthentication) + + def addUser(self, login, password): + """ + """ + auths = self.getAuthenticatorPlugins() + for name, plugin in auths: + try: + method = plugin.addUser + except AttributeError: + continue + + if method(login, password): + return True + + return False + + def delPrincipal(self, id): + auths = self.getAuthenticatorPlugins() + for name, plugin in auths: + try: + method = plugin.delPrincipal + except AttributeError: + continue + + if method(login): + return True + + return False + + + def allowDeletePrincipal(self, id): + auths = self.getAuthenticatorPlugins() + for name, plugin in auths: + try: + method = plugin.allowDeletePrincipal + except AttributeError: + continue + + if method(id): + return True + + return False + + def allowPasswordSet(self, id): + """ + """ + auths = self.getAuthenticatorPlugins() + for name, plugin in auths: + try: + method = plugin.allowPasswordSet + except AttributeError: + continue + + if method(id): + return True + + return False + + def doChangeUser(self, login, password, **kws): + """ + """ + auths = self.getAuthenticatorPlugins() + for name, plugin in auths: + try: + method = plugin.doChangeUser + except AttributeError: + continue + + if method(login, password, **kws): + return True + + return False + + def setPropertiesForUser(self, login, **props): + #XXX: this is bad and should not go via the auth plugin!! + # it's also very specific to citymob + auths = self.getAuthenticatorPlugins() + for name, plugin in auths: + try: + method = plugin.setPropertiesForUser + except AttributeError: + continue + + if method(login, **props): + return True + + raise RuntimeError -- 2.11.4.GIT