[Checkins] SVN: zope.pluggableauth/trunk/src/zope/pluggableauth/factories.py Using principalfolder as a starting point of the factories module.

Souheil CHELFOUH souheil at chelfouh.com
Sun Jan 24 13:35:52 EST 2010


Log message for revision 108442:
  Using principalfolder as a starting point of the factories module.
  Needs cleanup.
  

Changed:
  A   zope.pluggableauth/trunk/src/zope/pluggableauth/factories.py

-=-
Copied: zope.pluggableauth/trunk/src/zope/pluggableauth/factories.py (from rev 108440, zope.app.authentication/trunk/src/zope/app/authentication/principalfolder.py)
===================================================================
--- zope.pluggableauth/trunk/src/zope/pluggableauth/factories.py	                        (rev 0)
+++ zope.pluggableauth/trunk/src/zope/pluggableauth/factories.py	2010-01-24 18:35:52 UTC (rev 108442)
@@ -0,0 +1,566 @@
+##############################################################################
+#
+# Copyright (c) 2004 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.
+#
+##############################################################################
+"""ZODB-based Authentication Source
+
+$Id$
+"""
+__docformat__ = "reStructuredText"
+
+from persistent import Persistent
+from zope import interface
+from zope import component
+from zope.event import notify
+from zope.schema import Text, TextLine, Password, Choice
+from zope.publisher.interfaces import IRequest
+
+from zope.authentication.interfaces import IAuthentication
+from zope.container.interfaces import DuplicateIDError
+from zope.container.contained import Contained
+from zope.container.constraints import contains, containers
+from zope.container.btree import BTreeContainer
+from zope.password.interfaces import IPasswordManager
+from zope.app.authentication.i18n import ZopeMessageFactory as _
+
+from zope.app.authentication import interfaces
+
+
+class IInternalPrincipal(interface.Interface):
+    """Principal information"""
+
+    login = TextLine(
+        title=_("Login"),
+        description=_("The Login/Username of the principal. "
+                      "This value can change."))
+
+    def setPassword(password, passwordManagerName=None):
+        pass
+
+    password = Password(
+        title=_("Password"),
+        description=_("The password for the principal."))
+
+    passwordManagerName = Choice(
+        title=_("Password Manager"),
+        vocabulary="Password Manager Names",
+        description=_("The password manager will be used"
+            " for encode/check the password"),
+        default="SSHA",
+        # TODO: The password manager name may be changed only
+        # if the password changed
+        readonly=True
+        )
+
+    title = TextLine(
+        title=_("Title"),
+        description=_("Provides a title for the principal."))
+
+    description = Text(
+        title=_("Description"),
+        description=_("Provides a description for the principal."),
+        required=False,
+        missing_value='',
+        default=u'')
+
+
+class IInternalPrincipalContainer(interface.Interface):
+    """A container that contains internal principals."""
+
+    prefix = TextLine(
+        title=_("Prefix"),
+        description=_(
+        "Prefix to be added to all principal ids to assure "
+        "that all ids are unique within the authentication service"),
+        missing_value=u"",
+        default=u'',
+        readonly=True)
+
+    def getIdByLogin(login):
+        """Return the principal id currently associated with login.
+
+        The return value includes the container prefix, but does not
+        include the PAU prefix.
+
+        KeyError is raised if no principal is associated with login.
+
+        """
+
+    contains(IInternalPrincipal)
+
+
+class IInternalPrincipalContained(interface.Interface):
+    """Principal information"""
+
+    containers(IInternalPrincipalContainer)
+
+
+class ISearchSchema(interface.Interface):
+    """Search Interface for this Principal Provider"""
+
+    search = TextLine(
+        title=_("Search String"),
+        description=_("A Search String"),
+        required=False,
+        default=u'',
+        missing_value=u'')
+
+
+class InternalPrincipal(Persistent, Contained):
+    """An internal principal for Persistent Principal Folder."""
+
+    interface.implements(IInternalPrincipal, IInternalPrincipalContained)
+
+    # If you're searching for self._passwordManagerName, or self._password
+    # probably you just need to evolve the database to new generation
+    # at /++etc++process/@@generations.html
+
+    # NOTE: All changes needs to be synchronized with the evolver at
+    # zope.app.zopeappgenerations.evolve2
+
+    def __init__(self, login, password, title, description=u'',
+            passwordManagerName="SSHA"):
+        self._login = login
+        self._passwordManagerName = passwordManagerName
+        self.password = password
+        self.title = title
+        self.description = description
+
+    def getPasswordManagerName(self):
+        return self._passwordManagerName
+
+    passwordManagerName = property(getPasswordManagerName)
+
+    def _getPasswordManager(self):
+        return component.getUtility(
+            IPasswordManager, self.passwordManagerName)
+
+    def getPassword(self):
+        return self._password
+
+    def setPassword(self, password, passwordManagerName=None):
+        if passwordManagerName is not None:
+            self._passwordManagerName = passwordManagerName
+        passwordManager = self._getPasswordManager()
+        self._password = passwordManager.encodePassword(password)
+
+    password = property(getPassword, setPassword)
+
+    def checkPassword(self, password):
+        passwordManager = self._getPasswordManager()
+        return passwordManager.checkPassword(self.password, password)
+
+    def getPassword(self):
+        return self._password
+
+    def getLogin(self):
+        return self._login
+
+    def setLogin(self, login):
+        oldLogin = self._login
+        self._login = login
+        if self.__parent__ is not None:
+            try:
+                self.__parent__.notifyLoginChanged(oldLogin, self)
+            except ValueError:
+                self._login = oldLogin
+                raise
+
+    login = property(getLogin, setLogin)
+
+
+class PrincipalInfo(object):
+    """An implementation of IPrincipalInfo used by the principal folder.
+
+    A principal info is created with id, login, title, and description:
+
+      >>> info = PrincipalInfo('users.foo', 'foo', 'Foo', 'An over-used term.')
+      >>> info
+      PrincipalInfo('users.foo')
+      >>> info.id
+      'users.foo'
+      >>> info.login
+      'foo'
+      >>> info.title
+      'Foo'
+      >>> info.description
+      'An over-used term.'
+
+    """
+    interface.implements(interfaces.IPrincipalInfo)
+
+    def __init__(self, id, login, title, description):
+        self.id = id
+        self.login = login
+        self.title = title
+        self.description = description
+
+    def __repr__(self):
+        return 'PrincipalInfo(%r)' % self.id
+
+
+class PrincipalFolder(BTreeContainer):
+    """A Persistent Principal Folder and Authentication plugin.
+
+    See principalfolder.txt for details.
+    """
+
+    interface.implements(interfaces.IAuthenticatorPlugin,
+                         interfaces.IQuerySchemaSearch,
+                         IInternalPrincipalContainer)
+
+    schema = ISearchSchema
+
+    def __init__(self, prefix=''):
+        self.prefix = unicode(prefix)
+        super(PrincipalFolder, self).__init__()
+        self.__id_by_login = self._newContainerData()
+
+    def notifyLoginChanged(self, oldLogin, principal):
+        """Notify the Container about changed login of a principal.
+
+        We need this, so that our second tree can be kept up-to-date.
+        """
+        # A user with the new login already exists
+        if principal.login in self.__id_by_login:
+            raise ValueError('Principal Login already taken!')
+
+        del self.__id_by_login[oldLogin]
+        self.__id_by_login[principal.login] = principal.__name__
+
+    def __setitem__(self, id, principal):
+        """Add principal information.
+
+        Create a Principal Folder
+
+            >>> pf = PrincipalFolder()
+
+        Create a principal with 1 as id
+        Add a login attr since __setitem__ is in need of one
+
+            >>> principal = Principal(1)
+            >>> principal.login = 1
+
+        Add the principal within the Principal Folder
+
+            >>> pf.__setitem__(u'1', principal)
+
+        Try to add another principal with the same id.
+        It should raise a DuplicateIDError
+
+            >>> try:
+            ...     pf.__setitem__(u'1', principal)
+            ... except DuplicateIDError, e:
+            ...     pass
+            >>>
+        """
+        # A user with the new login already exists
+        if principal.login in self.__id_by_login:
+            raise DuplicateIDError('Principal Login already taken!')
+
+        super(PrincipalFolder, self).__setitem__(id, principal)
+        self.__id_by_login[principal.login] = id
+
+    def __delitem__(self, id):
+        """Remove principal information."""
+        principal = self[id]
+        super(PrincipalFolder, self).__delitem__(id)
+        del self.__id_by_login[principal.login]
+
+    def authenticateCredentials(self, credentials):
+        """Return principal info if credentials can be authenticated
+        """
+        if not isinstance(credentials, dict):
+            return None
+        if not ('login' in credentials and 'password' in credentials):
+            return None
+        id = self.__id_by_login.get(credentials['login'])
+        if id is None:
+            return None
+        internal = self[id]
+        if not internal.checkPassword(credentials["password"]):
+            return None
+        return PrincipalInfo(self.prefix + id, internal.login, internal.title,
+                             internal.description)
+
+    def principalInfo(self, id):
+        if id.startswith(self.prefix):
+            internal = self.get(id[len(self.prefix):])
+            if internal is not None:
+                return PrincipalInfo(id, internal.login, internal.title,
+                                     internal.description)
+
+    def getIdByLogin(self, login):
+        return self.prefix + self.__id_by_login[login]
+
+    def search(self, query, start=None, batch_size=None):
+        """Search through this principal provider."""
+        search = query.get('search')
+        if search is None:
+            return
+        search = search.lower()
+        n = 1
+        for i, value in enumerate(self.values()):
+            if (search in value.title.lower() or
+                search in value.description.lower() or
+                search in value.login.lower()):
+                if not ((start is not None and i < start)
+                        or (batch_size is not None and n > batch_size)):
+                    n += 1
+                    yield self.prefix + value.__name__
+
+class Principal(object):
+    """A group-aware implementation of zope.security.interfaces.IPrincipal.
+
+    A principal is created with an ID:
+
+      >>> p = Principal(1)
+      >>> p
+      Principal(1)
+      >>> p.id
+      1
+
+    title and description may also be provided:
+
+      >>> p = Principal('george', 'George', 'A site member.')
+      >>> p
+      Principal('george')
+      >>> p.id
+      'george'
+      >>> p.title
+      'George'
+      >>> p.description
+      'A site member.'
+
+    The `groups` is a simple list, filled in by plugins.
+
+      >>> p.groups
+      []
+
+    The `allGroups` attribute is a readonly iterable of the full closure of the
+    groups in the `groups` attribute--that is, if the principal is a direct
+    member of the 'Administrators' group, and the 'Administrators' group is
+    a member of the 'Reviewers' group, then p.groups would be 
+    ['Administrators'] and list(p.allGroups) would be
+    ['Administrators', 'Reviewers'].
+
+    To illustrate this, we'll need to set up a dummy authentication utility,
+    and a few principals.  Our main principal will also gain some groups, as if
+    plugins had added the groups to the list.  This is all setup--skip to the
+    next block to actually see `allGroups` in action.
+    
+      >>> p.groups.extend(
+      ...     ['content_administrators', 'zope_3_project',
+      ...      'list_administrators', 'zpug'])
+      >>> editor = Principal('editors', 'Content Editors')
+      >>> creator = Principal('creators', 'Content Creators')
+      >>> reviewer = Principal('reviewers', 'Content Reviewers')
+      >>> reviewer.groups.extend(['editors', 'creators'])
+      >>> usermanager = Principal('user_managers', 'User Managers')
+      >>> contentAdmin = Principal(
+      ...     'content_administrators', 'Content Administrators')
+      >>> contentAdmin.groups.extend(['reviewers', 'user_managers'])
+      >>> zope3Dev = Principal('zope_3_project', 'Zope 3 Developer')
+      >>> zope3ListAdmin = Principal(
+      ...     'zope_3_list_admin', 'Zope 3 List Administrators')
+      >>> zope3ListAdmin.groups.append('zope_3_project') # duplicate, but
+      ... # should only appear in allGroups once
+      >>> listAdmin = Principal('list_administrators', 'List Administrators')
+      >>> listAdmin.groups.append('zope_3_list_admin')
+      >>> zpugMember = Principal('zpug', 'ZPUG Member')
+      >>> martians = Principal('martians', 'Martians') # not in p's allGroups
+      >>> group_data = dict((p.id, p) for p in (
+      ...     editor, creator, reviewer, usermanager, contentAdmin,
+      ...     zope3Dev, zope3ListAdmin, listAdmin, zpugMember, martians))
+      >>> class DemoAuth(object):
+      ...     interface.implements(IAuthentication)
+      ...     def getPrincipal(self, id):
+      ...         return group_data[id]
+      ...
+      >>> demoAuth = DemoAuth()
+      >>> component.provideUtility(demoAuth)
+
+    Now, we have a user with the following groups (lowest level are p's direct
+    groups, and lines show membership):
+
+      editors  creators
+         \------/
+             |                                     zope_3_project (duplicate)
+          reviewers  user_managers                          |
+               \---------/                           zope_3_list_admin
+                    |                                       |
+          content_administrators   zope_3_project   list_administrators   zpug
+
+    The allGroups value includes all of the shown groups, and with
+    'zope_3_project' only appearing once.
+
+      >>> p.groups # doctest: +NORMALIZE_WHITESPACE
+      ['content_administrators', 'zope_3_project', 'list_administrators',
+       'zpug']
+      >>> list(p.allGroups) # doctest: +NORMALIZE_WHITESPACE
+      ['content_administrators', 'reviewers', 'editors', 'creators',
+       'user_managers', 'zope_3_project', 'list_administrators',
+       'zope_3_list_admin', 'zpug']
+    """
+    interface.implements(interfaces.IPrincipal)
+
+    def __init__(self, id, title=u'', description=u''):
+        self.id = id
+        self.title = title
+        self.description = description
+        self.groups = []
+
+    def __repr__(self):
+        return 'Principal(%r)' % self.id
+
+    @property
+    def allGroups(self):
+        if self.groups:
+            seen = set()
+            principals = component.getUtility(IAuthentication)
+            stack = [iter(self.groups)]
+            while stack:
+                try:
+                    group_id = stack[-1].next()
+                except StopIteration:
+                    stack.pop()
+                else:
+                    if group_id not in seen:
+                        yield group_id
+                        seen.add(group_id)
+                        group = principals.getPrincipal(group_id)
+                        stack.append(iter(group.groups))
+
+class AuthenticatedPrincipalFactory(object):
+    """Creates 'authenticated' principals.
+
+    An authenticated principal is created as a result of an authentication
+    operation.
+
+    To use the factory, create it with the info (interfaces.IPrincipalInfo) of
+    the principal to create and a request:
+
+      >>> info = PrincipalInfo('users.mary', 'mary', 'Mary', 'The site admin.')
+      >>> from zope.publisher.base import TestRequest
+      >>> request = TestRequest('/')
+      >>> factory = AuthenticatedPrincipalFactory(info, request)
+
+    The factory must be called with a pluggable-authentication object:
+
+      >>> class Auth:
+      ...     prefix = 'auth.'
+      >>> auth = Auth()
+
+      >>> principal = factory(auth)
+
+    The factory uses the pluggable authentication and the info to
+    create a principal with the same ID, title, and description:
+
+      >>> principal.id
+      'auth.users.mary'
+      >>> principal.title
+      'Mary'
+      >>> principal.description
+      'The site admin.'
+
+    It also fires an AuthenticatedPrincipalCreatedEvent:
+
+      >>> from zope.component.eventtesting import getEvents
+      >>> [event] = getEvents(interfaces.IAuthenticatedPrincipalCreated)
+      >>> event.principal is principal, event.authentication is auth
+      (True, True)
+      >>> event.info
+      PrincipalInfo('users.mary')
+      >>> event.request is request
+      True
+
+    Listeners can subscribe to this event to perform additional operations
+    when the authenticated principal is created.
+
+    For information on how factories are used in the authentication process,
+    see README.txt.
+    """
+    component.adapts(interfaces.IPrincipalInfo, IRequest)
+
+    interface.implements(interfaces.IAuthenticatedPrincipalFactory)
+
+    def __init__(self, info, request):
+        self.info = info
+        self.request = request
+
+    def __call__(self, authentication):
+        principal = Principal(authentication.prefix + self.info.id,
+                              self.info.title,
+                              self.info.description)
+        notify(interfaces.AuthenticatedPrincipalCreated(
+            authentication, principal, self.info, self.request))
+        return principal
+
+
+class FoundPrincipalFactory(object):
+    """Creates 'found' principals.
+
+    A 'found' principal is created as a result of a principal lookup.
+
+    To use the factory, create it with the info (interfaces.IPrincipalInfo) of
+    the principal to create:
+
+      >>> info = PrincipalInfo('users.sam', 'sam', 'Sam', 'A site user.')
+      >>> factory = FoundPrincipalFactory(info)
+
+    The factory must be called with a pluggable-authentication object:
+
+      >>> class Auth:
+      ...     prefix = 'auth.'
+      >>> auth = Auth()
+
+      >>> principal = factory(auth)
+
+    The factory uses the pluggable-authentication object and the info
+    to create a principal with the same ID, title, and description:
+
+      >>> principal.id
+      'auth.users.sam'
+      >>> principal.title
+      'Sam'
+      >>> principal.description
+      'A site user.'
+
+    It also fires a FoundPrincipalCreatedEvent:
+
+      >>> from zope.component.eventtesting import getEvents
+      >>> [event] = getEvents(interfaces.IFoundPrincipalCreated)
+      >>> event.principal is principal, event.authentication is auth
+      (True, True)
+      >>> event.info
+      PrincipalInfo('users.sam')
+
+    Listeners can subscribe to this event to perform additional operations
+    when the 'found' principal is created.
+
+    For information on how factories are used in the authentication process,
+    see README.txt.
+    """
+    component.adapts(interfaces.IPrincipalInfo)
+
+    interface.implements(interfaces.IFoundPrincipalFactory)
+
+    def __init__(self, info):
+        self.info = info
+
+    def __call__(self, authentication):
+        principal = Principal(authentication.prefix + self.info.id,
+                              self.info.title,
+                              self.info.description)
+        notify(interfaces.FoundPrincipalCreated(authentication,
+                                                principal, self.info))
+        return principal



More information about the checkins mailing list