[Zope3-checkins] CVS: Zope3/src/zope/app/services/pluggableauth - __init__.py:1.1.2.1 configure.zcml:1.1.2.1

Zachery Bir zbir@urbanape.com
Tue, 3 Jun 2003 09:34:07 -0400


Update of /cvs-repository/Zope3/src/zope/app/services/pluggableauth
In directory cvs.zope.org:/tmp/cvs-serv21295/src/zope/app/services/pluggableauth

Added Files:
      Tag: pluggable_authentication_service-branch
	__init__.py configure.zcml 
Log Message:
In directory src/zope/app/browser/services/pluggableauth:

  - src/zope/app/browser/services/pluggableauth/__init__.py

    Adding override for the PluggableAuthenticationService

  - src/zope/app/browser/services/pluggableauth/configure.zcml

    Configuration for the views for PluggableAuthenticationService, 
    BTreePrincipalSource, and SimplePrincipal

    XXX: TODO: - contents.html view for PluggableAuthenticationService
                 and BTreePrincipalSource

               - get BTreePrincipalSource to actually have additional 
                 zmi_views to be more like a container, for example, it 
                 currently does not have a "Contents" tab

               - views for the login challenge

In directory src/zope/app/container:

  - src/zope/app/container/ordered.py

    Implementation of an OrderedContainer

In directory src/zope/app/container/tests:

  - src/zope/app/container/tests/test_ordered.py

    Test module for OrderedContainer (all tests currently in docstrings)

In directory src/zope/app/interfaces/container:

  - src/zope/app/interfaces/container/__init__.py

    Added interface for OrderedContainer

In directory src/zope/app/interfaces/services/pluggableauth:

  - src/zope/app/interfaces/services/pluggableauth/__init__.py

    Interfaces for PluggableAuthenticationService, Read/WritePrincipalSource,
    and UserSchemafied

In directory src/zope/app/services:

  - src/zope/app/services/configure.zcml

    Included the pluggableauth package

In directory src/zope/app/services/pluggableauth:

  - src/zope/app/services/pluggableauth/__init__.py

    Implementation of the PluggableAuthenticationService, BTreePrincipalSource,
    and SimplePrincipal classes

    XXX: TODO: - Wrap all returned items from getPrincipals() in both
                 PluggableAuthenticationService and BTreePrincipalSource
                 so that the ids being handed up are tuplified (in the case
                 of BTreePrincipalSource) and triplified (in the case of
                 PluggableAuthenticationService) to ensure proper uniquity

  - src/zope/app/services/pluggableauth/configure.zcml

    Content directives for the above

In directory src/zope/app/services/tests:

  - src/zope/app/services/tests/test_pluggableauth.py

    Test module for PluggableAuthenticationService, BTreePrincipalSource, and
    SimplePrincipal classes (all available tests currently in docstrings)

    XXX: TODO: - write unit tests for the ContextMethods to ensure proper
                 delegation of Authentication responsibility (perhaps 
                 functional tests, instead?)


=== Added File Zope3/src/zope/app/services/pluggableauth/__init__.py ===
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (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.
#
##############################################################################
"""Pluggable Authentication service implementation.

$Id: __init__.py,v 1.1.2.1 2003/06/03 13:34:06 urbanape Exp $
"""

import random
import sys
import datetime
from persistence import Persistent
from zodb.btrees.IOBTree import IOBTree
from zodb.btrees.OIBTree import OIBTree
from zope.interface import implements
from zope.app.services.servicenames import Authentication
from zope.app.container.btree import BTreeContainer
from zope.app.container.ordered import OrderedContainer
from zope.app.interfaces.container import IContainer
from zope.app.interfaces.container import IContainerNamesContainer
from zope.app.interfaces.container import IOrderedContainer
from zope.app.interfaces.container import IAddNotifiable
from zope.app.interfaces.services.pluggableauth import IUserSchemafied
from zope.app.interfaces.security import ILoginPassword
from zope.app.interfaces.services.pluggableauth \
     import IPluggableAuthenticationService
from zope.app.interfaces.services.pluggableauth import IPrincipalSource
from zope.app.interfaces.services.pluggableauth import IReadPrincipalSource
from zope.app.interfaces.services.service import ISimpleService
from zope.app.component.nextservice import queryNextService
from zope.app.zapi import queryView
from zope.app.traversing import getPath
from zope.context import ContextMethod

def gen_key():
    """Return a random int (1, MAXINT), suitable for use as a BTree key."""

    return random.randint(0,sys.maxint)

class PluggableAuthenticationService(OrderedContainer):

    implements(IPluggableAuthenticationService, ISimpleService,
               IOrderedContainer, IAddNotifiable)

    def __init__(self):

        OrderedContainer.__init__(self)
        self.earmark = Earmark()

    def afterAddHook(self, object, container):
        """ See IAddNotifiable. """

        path = getPath(container)
        self.earmark.addPath(path)
    afterAddHook = ContextMethod(afterAddHook)

    def authenticate(self, request):
        """ See IAuthenticationService. """

        for ps in self.values():
            loginView = queryView(ps, "login", request)
            if loginView is not None:
                principal = loginView.authenticate()
                if principal is not None:
                    return principal

        next = queryNextService(self, Authentication, None)
        if next is not None:
            return next.authenticate(request)

        return None
    authenticate = ContextMethod(authenticate)

    def unauthenticatedPrincipal(self):
        """ See IAuthenticationService. """
        return None # XXX Do we need to implement or use another?

    def unauthorized(self, id, request):
        """ See IAuthenticationService. """

        if len(self) > 0:
            loginView = queryView(self[0], "login", request)
            if loginView is not None:
                return loginView.unauthorized()

        next = queryNextService(self, Authentication, None)
        if next is not None:
            return next.unauthorized(request)

        return None
    unauthorized = ContextMethod(unauthorized)

    def getPrincipal(self, id):
        """ See IAuthenticationService. """
        
        next = None
        
        try:
            auth_svc_earmark, principal_src, principal_id = id
        except ValueError:
            next = queryNextService(self, Authentication, None)

        if auth_svc_earmark != self.earmark:
            next = queryNextService(self, Authentication, None)

        if next is not None:
            return next.getPrincipal(id)

        source = self[principal_src]
        return source.getPrincipal(principal_id)
    getPrincipal = ContextMethod(getPrincipal)

    def getPrincipals(self, name):
        """ See IAuthenticationService. """

        for ps in self.values():
            for p in ps.getPrincipals(name):
                yield p

        next = queryNextService(self, Authentication, None)
        if next is not None:
            for p in next.getPrincipals(name):
                yield p
    getPrincipals = ContextMethod(getPrincipals)

    def addPrincipalSource(self, id, principal_source):
        """ See IPluggableAuthenticationService.

        >>> pas = PluggableAuthenticationService()
        >>> sps = SimplePrincipalSource()
        >>> pas.addPrincipalSource('simple', sps)
        >>> sps2 = SimplePrincipalSource()
        >>> pas.addPrincipalSource('not_quite_so_simple', sps2)
        >>> pas.keys()
        ['simple', 'not_quite_so_simple']
        """

        if not IReadPrincipalSource.isImplementedBy(principal_source):
            raise TypeError("Source must implement IReadPrincipalSource")

        self.setObject(id, principal_source)

    def removePrincipalSource(self, id):
        """ See IPluggableAuthenticationService.

        >>> pas = PluggableAuthenticationService()
        >>> sps = SimplePrincipalSource()
        >>> pas.addPrincipalSource('simple', sps)
        >>> sps2 = SimplePrincipalSource()
        >>> pas.addPrincipalSource('not_quite_so_simple', sps2)
        >>> sps3 = SimplePrincipalSource()
        >>> pas.addPrincipalSource('simpler', sps3)
        >>> pas.keys()
        ['simple', 'not_quite_so_simple', 'simpler']
        >>> pas.removePrincipalSource('not_quite_so_simple')
        >>> pas.keys()
        ['simple', 'simpler']
        """

        del self[id]

class BTreePrincipalSource(Persistent):
    """An efficient, scalable provider of Authentication Principals."""

    implements(IPrincipalSource, IContainerNamesContainer)

    def __init__(self):

        self._principals_by_number = IOBTree()
        self._numbers_by_login = OIBTree()

    def getPrincipal(self, id):
        """ See IReadPrincipalSource.

        >>> sps = BTreePrincipalSource()
        >>> prin1 = SimplePrincipal(1, 'gandalf', 'shadowfax')
        >>> sps.setObject('gandalf', prin1)
        >>> principal = sps.getPrincipal('gandalf')
        >>> principal.login
        'gandalf'
        """

        return self[id]

    def getPrincipals(self, name):
        """ See IReadPrincipalSource.

        >>> sps = BTreePrincipalSource()
        >>> prin1 = SimplePrincipal(1, 'gandalf', 'shadowfax')
        >>> sps.setObject('gandalf', prin1)
        >>> prin1 = SimplePrincipal(1, 'frodo', 'ring')
        >>> sps.setObject('frodo', prin1)
        >>> prin1 = SimplePrincipal(1, 'pippin', 'pipe')
        >>> sps.setObject('pippin', prin1)
        >>> prin1 = SimplePrincipal(1, 'sam', 'garden')
        >>> sps.setObject('sam', prin1)
        >>> prin1 = SimplePrincipal(1, 'merry', 'food')
        >>> sps.setObject('merry', prin1)
        >>> [p.login for p in sps.getPrincipals('a')]
        ['gandalf', 'sam']
        >>> [p.login for p in sps.getPrincipals('')]
        ['frodo', 'gandalf', 'merry', 'pippin', 'sam']
        >>> [p.login for p in sps.getPrincipals('sauron')]
        []
        """

        for k in self.keys():
            if k.find(name) != -1:
                yield self[k]

    def __del__(self, key):
        """ See IContainer.

        >>> sps = BTreePrincipalSource()
        >>> prin1 = SimplePrincipal(1, 'gandalf', 'shadowfax')
        >>> sps.setObject('gandalf', prin1)
        >>> prin1 = SimplePrincipal(1, 'frodo', 'ring')
        >>> sps.setObject('frodo', prin1)
        >>> prin1 = SimplePrincipal(1, 'pippin', 'pipe')
        >>> sps.setObject('pippin', prin1)
        >>> prin1 = SimplePrincipal(1, 'sam', 'garden')
        >>> sps.setObject('sam', prin1)
        >>> prin1 = SimplePrincipal(1, 'merry', 'food')
        >>> sps.setObject('merry', prin1)
        >>> [p.login for p in sps.getPrincipals('')]
        ['frodo', 'gandalf', 'merry', 'pippin', 'sam']
        >>> sps.removePrincipal('gandalf')
        >>> [p.login for p in sps.getPrincipals('')]
        ['frodo', 'merry', 'pippin', 'sam']
        >>> sps.getPrincipal('gandalf')
        Traceback (most recent call last):
        ...
        KeyError: 'gandalf'
        """

        number = self._numbers_by_login[key]

        del self._principals_by_number[number]
        del self._numbers_by_login[key]

    def setObject(self, id, object):
        """ See IContainer

        >>> sps = BTreePrincipalSource()
        >>> prin = SimplePrincipal(1, 'gandalf', 'shadowfax')
        >>> sps.setObject('gandalf', prin)
        >>> sps.getPrincipal('gandalf').login
        'gandalf'
        """

        store = self._principals_by_number

        key = gen_key()
        while not store.insert(key, object):
            key = gen_key()

        object.id = key
        self._numbers_by_login[object.login] = key

        return object.login

    def keys(self):
        """ See IContainer.

        >>> sps = BTreePrincipalSource()
        >>> sps.keys()
        []
        >>> prin = SimplePrincipal(1, 'arthur', 'tea')
        >>> key = sps.setObject('arthur', prin)
        >>> sps.keys()
        ['arthur']
        >>> prin = SimplePrincipal(1, 'ford', 'towel')
        >>> key = sps.setObject('ford', prin)
        >>> sps.keys()
        ['arthur', 'ford']
        """

        return list(self._numbers_by_login.keys())

    def __iter__(self):
        """ See IContainer.

        >>> sps = BTreePrincipalSource()
        >>> sps.keys()
        []
        >>> prin = SimplePrincipal(1, 'trillian', 'heartOfGold')
        >>> key = sps.setObject('trillian', prin)
        >>> prin = SimplePrincipal(1, 'zaphod', 'gargleblaster')
        >>> key = sps.setObject('zaphod', prin)
        >>> [i for i in sps]
        ['trillian', 'zaphod']
        """

        return iter(self.keys())

    def __getitem__(self, key):
        """ See IContainer

        >>> sps = BTreePrincipalSource()
        >>> prin = SimplePrincipal(1, 'gag', 'justzisguy')
        >>> key = sps.setObject('gag', prin)
        >>> sps['gag'].login
        'gag'
        """

        number = self._numbers_by_login[key]
        return self._principals_by_number[number]

    def get(self, key, default=None):
        """ See IContainer

        >>> sps = BTreePrincipalSource()
        >>> prin = SimplePrincipal(1, 'slartibartfast', 'fjord')
        >>> key = sps.setObject('slartibartfast', prin)
        >>> principal = sps.get('slartibartfast')
        >>> sps.get('marvin', 'No chance, dude.')
        'No chance, dude.'
        """

        try:
            number = self._numbers_by_login[key]
        except KeyError:
            return default

        return self._principals_by_number[number]

    def values(self):
        """ See IContainer.

        >>> sps = BTreePrincipalSource()
        >>> sps.keys()
        []
        >>> prin = SimplePrincipal(1, 'arthur', 'tea')
        >>> key = sps.setObject('arthur', prin)
        >>> [user.login for user in sps.values()]
        ['arthur']
        >>> prin = SimplePrincipal(1, 'ford', 'towel')
        >>> key = sps.setObject('ford', prin)
        >>> [user.login for user in sps.values()]
        ['arthur', 'ford']
        """

        return [self._principals_by_number[n]
                for n in self._numbers_by_login.values()]

    def __len__(self):
        """ See IContainer

        >>> sps = BTreePrincipalSource()
        >>> int(len(sps) == 0)
        1
        >>> prin = SimplePrincipal(1, 'trillian', 'heartOfGold')
        >>> key = sps.setObject('trillian', prin)
        >>> int(len(sps) == 1)
        1
        """

        return len(self._principals_by_number)

    def items(self):
        """ See IContainer.

        >>> sps = BTreePrincipalSource()
        >>> sps.keys()
        []
        >>> prin = SimplePrincipal(1, 'zaphod', 'gargleblaster')
        >>> key = sps.setObject('zaphod', prin)
        >>> [(k, v.login) for k, v in sps.items()]
        [('zaphod', 'zaphod')]
        >>> prin = SimplePrincipal(1, 'marvin', 'paranoid')
        >>> key = sps.setObject('marvin', prin)
        >>> [(k, v.login) for k, v in sps.items()]
        [('marvin', 'marvin'), ('zaphod', 'zaphod')]
        """

        # We're being expensive here (see values() above) for convenience
        return [(p.login, p) for p in self.values()]

    def __contains__(self, key):
        """ See IContainer.

        >>> sps = BTreePrincipalSource()
        >>> prin = SimplePrincipal(1, 'hotblack', 'sundive')
        >>> key = sps.setObject('hotblack', prin)
        >>> int('hotblack' in sps)
        1
        >>> int('desiato' in sps)
        0
        """

        return self._numbers_by_login.has_key(key)

    has_key = __contains__

class SimplePrincipal(Persistent):
    """A no-frills IUserSchemafied implementation."""

    implements(IUserSchemafied)

    def __init__(self, login, password, title='', description=''):

        self.id = ''
        self.login = login
        self.password = password
        self.title = title
        self.description = description

    def validate(self, test_password):
        """ See IReadUser.

        >>> pal = SimplePrincipal(1, 'gandalf', 'shadowfax', 'The Grey Wizard',
        ...                       'Cool old man with neato fireworks. '
        ...                       'Has a nice beard.')
        >>> int(pal.validate('shdaowfax'))
        0
        >>> int(pal.validate('shadowfax'))
        1
        """

        return test_password == self.password

class Earmark(Persistent):
    """A distinguishing feature by which an object can be uniquely id'd."""

    def __init__(self):

        self.paths = []
        self.times = []
        self.original_time = datetime.datetime.utcnow()

    # XXX we may want to bring in additional descriptive metadata eventually

    def addPath(self, path):
        """ Update the history of places this Earmark has been.

        >>> em = Earmark()
        >>> em.addPath('/was/here')
        >>> em.addPath('/am/now/here')
        >>> em.paths
        ['/was/here', '/am/now/here']
        """
        
        self.paths.append(path)
        self.times.append(datetime.datetime.utcnow())


=== Added File Zope3/src/zope/app/services/pluggableauth/configure.zcml ===
<zopeConfigure xmlns="http://namespaces.zope.org/zope"
    xmlns:browser="http://namespaces.zope.org/browser">

<content class=".PluggableAuthenticationService">
    <factory
        id="zope.app.services.PluggableAuthenticationService"
        permission="zope.ManageServices"
        />
    <require
        permission="zope.ManageServices"
        interface="
  zope.app.interfaces.services.pluggableauth.IPluggableAuthenticationService"
        />
    <allow
        interface="zope.app.interfaces.container.IReadContainer"
        />
    <require
        permission="zope.ManageServices"
        interface="zope.app.interfaces.container.IWriteContainer"
        />
    <require
        permission="zope.ManageServices"
        interface="zope.app.interfaces.container.IAddNotifiable"
        />
    <require
        permission="zope.ManageServices"
        interface="zope.app.interfaces.services.service.ISimpleService"
        />
</content>

<content class=".BTreePrincipalSource">
    <factory
        id="zope.app.principalsources.BTreePrincipalSource"
        permission="zope.ManageServices"
        />
    <allow
        interface="zope.app.interfaces.container.IReadContainer"
        />
    <require
        permission="zope.ManageServices"
        interface="zope.app.interfaces.container.IWriteContainer"
        />
    <require
        permission="zope.ManageServices"
        interface="
  zope.app.interfaces.services.pluggableauth.IWritePrincipalSource"
        />
    <allow
        interface="
  zope.app.interfaces.services.pluggableauth.IReadPrincipalSource"
        />
</content>

<content class=".SimplePrincipal">
    <factory
        id="zope.app.principals.SimplePrincipal"
        permission="zope.ManageServices"
        />
    <allow
        interface="zope.app.interfaces.services.pluggableauth.IUserSchemafied"
        />
    <require
        permission="zope.ManageServices"
        set_schema="zope.app.interfaces.services.pluggableauth.IUserSchemafied"
        />
</content>

</zopeConfigure>