[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>