Skip to main content

Security Components for Zope 3 Applications

Project description

This package provides several components integrating the Zope security implementation into zope 3 applications.

Detailed Documentation

Global principal definition

Global principals are defined via ZCML. There are several kinds of principals that can be defined.

Authenticated Users

There are principals that can log in:

>>> zcml("""
...    <configure
...        xmlns="http://namespaces.zope.org/zope"
...        >
...
...      <principal
...         id="zope.manager"
...         title="Manager"
...         description="System Manager"
...         login="admin"
...         password_manager="SHA1"
...         password="40bd001563085fc35165329ea1ff5c5ecbdbbeef"
...         />
...
...    </configure>
... """)
>>> import pprint
>>> from zope.app.security.principalregistry import principalRegistry
>>> [p] = principalRegistry.getPrincipals('')
>>> p.id, p.title, p.description, p.getLogin(), p.validate('123')
('zope.manager', u'Manager', u'System Manager', u'admin', True)

The unauthenticated principal

There is the unauthenticated principal:

>>> zcml("""
...    <configure
...        xmlns="http://namespaces.zope.org/zope"
...        >
...
...      <unauthenticatedPrincipal
...         id="zope.unknown"
...         title="Anonymous user"
...         description="A person we don't know"
...         />
...
...    </configure>
... """)
>>> p = principalRegistry.unauthenticatedPrincipal()
>>> p.id, p.title, p.description
('zope.unknown', u'Anonymous user', u"A person we don't know")

The unauthenticated principal will also be registered as a utility. This is to provide easy access to the data defined for the principal so that other (more featureful) principal objects can be created for the same principal.

>>> from zope import component
>>> from zope.app.security import interfaces
>>> p = component.getUtility(interfaces.IUnauthenticatedPrincipal)
>>> p.id, p.title, p.description
('zope.unknown', u'Anonymous user', u"A person we don't know")

The unauthenticated group

An unauthenticated group can also be defined in ZCML:

>>> zcml("""
...    <configure
...        xmlns="http://namespaces.zope.org/zope"
...        >
...
...      <unauthenticatedGroup
...         id="zope.unknowngroup"
...         title="Anonymous users"
...         description="People we don't know"
...         />
...
...    </configure>
... """)

This directive creates a group and registers it as a utility providing IUnauthenticatedGroup:

>>> g = component.getUtility(interfaces.IUnauthenticatedGroup)
>>> g.id, g.title, g.description
('zope.unknowngroup', u'Anonymous users', u"People we don't know")

The unauthenticatedGroup directive also updates the group of the unauthenticated principal:

>>> p = principalRegistry.unauthenticatedPrincipal()
>>> g.id in p.groups
True
>>> p = component.getUtility(interfaces.IUnauthenticatedPrincipal)
>>> g.id in p.groups
True

If the unauthenticated principal is defined after the unauthenticated group, it will likewise have the group added to it:

>>> reset()
>>> zcml("""
...    <configure
...        xmlns="http://namespaces.zope.org/zope"
...        >
...
...      <unauthenticatedGroup
...         id="zope.unknowngroup2"
...         title="Anonymous users"
...         description="People we don't know"
...         />
...      <unauthenticatedPrincipal
...         id="zope.unknown2"
...         title="Anonymous user"
...         description="A person we don't know"
...         />
...
...    </configure>
... """)
>>> g = component.getUtility(interfaces.IUnauthenticatedGroup)
>>> g.id, g.title, g.description
('zope.unknowngroup2', u'Anonymous users', u"People we don't know")
>>> p = principalRegistry.unauthenticatedPrincipal()
>>> p.id, g.id in p.groups
('zope.unknown2', True)
>>> p = component.getUtility(interfaces.IUnauthenticatedPrincipal)
>>> p.id, g.id in p.groups
('zope.unknown2', True)

The unauthenticated group shows up as a principal in the principal registry:

>>> principalRegistry.getPrincipal(g.id) == g
True
>>> list(principalRegistry.getPrincipals("Anonymous")) == [g]
True

The authenticated group

There is an authenticated group:

>>> reset()
>>> zcml("""
...    <configure
...        xmlns="http://namespaces.zope.org/zope"
...        >
...
...      <unauthenticatedPrincipal
...         id="zope.unknown3"
...         title="Anonymous user"
...         description="A person we don't know"
...         />
...      <principal
...         id="zope.manager2"
...         title="Manager"
...         description="System Manager"
...         login="admin"
...         password="123"
...         />
...      <authenticatedGroup
...         id="zope.authenticated"
...         title="Authenticated users"
...         description="People we know"
...         />
...      <principal
...         id="zope.manager3"
...         title="Manager 3"
...         login="admin3"
...         password="123"
...         />
...
...    </configure>
... """)

It defines an IAuthenticatedGroup utility:

>>> g = component.getUtility(interfaces.IAuthenticatedGroup)
>>> g.id, g.title, g.description
('zope.authenticated', u'Authenticated users', u'People we know')

It also adds it self to the groups of any non-group principals already defined, and, when non-group principals are defined, they put themselves in the group if it’s defined:

>>> principals = list(principalRegistry.getPrincipals(''))
>>> principals.sort(lambda p1, p2: cmp(p1.id, p2.id))
>>> for p in principals:
...    print p.id, p.groups == [g.id]
zope.authenticated False
zope.manager2 True
zope.manager3 True

Excluding unauthenticated principals, of course:

>>> p = principalRegistry.unauthenticatedPrincipal()
>>> p.id, g.id in p.groups
('zope.unknown3', False)
>>> p = component.getUtility(interfaces.IUnauthenticatedPrincipal)
>>> p.id, g.id in p.groups
('zope.unknown3', False)

The everybody group

Finally, there is an everybody group:

>>> reset()
>>> zcml("""
...    <configure
...        xmlns="http://namespaces.zope.org/zope"
...        >
...
...      <unauthenticatedPrincipal
...         id="zope.unknown4"
...         title="Anonymous user"
...         description="A person we don't know"
...         />
...      <principal
...         id="zope.manager4"
...         title="Manager"
...         description="System Manager"
...         login="admin"
...         password="123"
...         />
...      <everybodyGroup
...         id="zope.everybody"
...         title="Everybody"
...         description="All People"
...         />
...      <principal
...         id="zope.manager5"
...         title="Manager 5"
...         login="admin5"
...         password="123"
...         />
...
...    </configure>
... """)

The everybodyGroup directive defines an IEveryoneGroup utility:

>>> g = component.getUtility(interfaces.IEveryoneGroup)
>>> g.id, g.title, g.description
('zope.everybody', u'Everybody', u'All People')

It also adds it self to the groups of any non-group principals already defined, and, when non-group principals are defined, they put themselves in the group if it’s defined:

>>> principals = list(principalRegistry.getPrincipals(''))
>>> principals.sort(lambda p1, p2: cmp(p1.id, p2.id))
>>> for p in principals:
...    print p.id, p.groups == [g.id]
zope.everybody False
zope.manager4 True
zope.manager5 True

Including unauthenticated principals, of course:

>>> p = principalRegistry.unauthenticatedPrincipal()
>>> p.id, g.id in p.groups
('zope.unknown4', True)
>>> p = component.getUtility(interfaces.IUnauthenticatedPrincipal)
>>> p.id, g.id in p.groups
('zope.unknown4', True)

Note that it is up to IAuthentication implementations to associate these groups with their principals, as appropriate.

The system_user

There is also a system_user that is defined in the code. It will be returned from the getPrincipal method of the registry.

>>> import zope.security.management
>>> import zope.app.security.principalregistry
>>> auth = zope.app.security.principalregistry.PrincipalRegistry()
>>> system_user = auth.getPrincipal(u'zope.security.management.system_user')
>>> system_user is zope.security.management.system_user
True

Logout Support

Logout support is defined by a simple interface ILogout:

>>> from zope.app.security.interfaces import ILogout

that has a single ‘logout’ method.

The current use of ILogout is to adapt an IAuthentication component to ILogout To illustrate, we’ll create a simple logout implementation that adapts IAuthentication:

>>> class SimpleLogout(object):
...
...     adapts(IAuthentication)
...     implements(ILogout)
...
...     def __init__(self, auth):
...         pass
...
...     def logout(self, request):
...         print 'User has logged out'
>>> provideAdapter(SimpleLogout)

and something to represent an authentication utility:

>>> class Authentication(object):
...
...     implements(IAuthentication)
>>> auth = Authentication()

To perform a logout, we adapt auth to ILogout and call ‘logout’:

>>> logout = ILogout(auth)
>>> logout.logout(TestRequest())
User has logged out

The ‘NoLogout’ Adapter

The class:

>>> from zope.app.security import NoLogout

can be registered as a fallback provider of ILogout for IAuthentication components that are not otherwise adaptable to ILogout. NoLogout’s logout method is a no-op:

>>> NoLogout(auth).logout(TestRequest())

Logout User Interface

Because some authentication protocols do not formally support logout, it may not be possible for a user to logout once he or she has logged in. In such cases, it would be inappropriate to present a user interface for logging out.

Because logout support is site-configurable, Zope provides an adapter that, when registered, indicates that the site is configured for logout:

>>> from zope.app.security import LogoutSupported

This class merely serves as a flag as it implements ILogoutSupported:

>>> from zope.app.security.interfaces import ILogoutSupported
>>> ILogoutSupported.implementedBy(LogoutSupported)
True

For more information on login/logout UI, see zope/app/security/browser/loginlogout.txt.

The Query View for Authentication Utilities

A regular authentication service will not provide the ISourceQueriables interface, but it is a queriable itself, since it provides the simple getPrincipals(name) method:

>>> class Principal:
...     def __init__(self, id):
...         self.id = id
>>> class MyAuthUtility:
...     data = {'jim': Principal(42), 'don': Principal(0),
...             'stephan': Principal(1)}
...
...     def getPrincipals(self, name):
...         return [principal
...                 for id, principal in self.data.items()
...                 if name in id]

Now that we have our queriable, we create the view for it:

>>> from zope.app.security.browser.auth import AuthUtilitySearchView
>>> from zope.publisher.browser import TestRequest
>>> request = TestRequest()
>>> view = AuthUtilitySearchView(MyAuthUtility(), request)

This allows us to render a search form.

>>> print view.render('test') # doctest: +NORMALIZE_WHITESPACE
<h4>principals.zcml</h4>
<div class="row">
<div class="label">
Search String
</div>
<div class="field">
<input type="text" name="test.searchstring" />
</div>
</div>
<div class="row">
<div class="field">
<input type="submit" name="test.search" value="Search" />
</div>
</div>

If we ask for results:

>>> view.results('test')

We don’t get any, since we did not provide any. But if we give input:

>>> request.form['test.searchstring'] = 'n'

we still don’t get any:

>>> view.results('test')

because we did not press the button. So let’s press the button:

>>> request.form['test.search'] = 'Search'

so that we now get results (!):

>>> ids = list(view.results('test'))
>>> ids.sort()
>>> ids
[0, 1]

Login/Logout Snippet

The class LoginLogout:

>>> from zope.app.security.browser.auth import LoginLogout

is used as a view to generate an HTML snippet suitable for logging in or logging out based on whether or not the current principal is authenticated.

When the current principal is unauthenticated, it provides IUnauthenticatedPrincipal:

>>> from zope.app.security.interfaces import IUnauthenticatedPrincipal
>>> from zope.app.security.principalregistry import UnauthenticatedPrincipal
>>> anonymous = UnauthenticatedPrincipal('anon', '', '')
>>> IUnauthenticatedPrincipal.providedBy(anonymous)
True

When LoginLogout is used for a request that has an unauthenticated principal, it provides the user with a link to ‘Login’:

>>> from zope.publisher.browser import TestRequest
>>> request = TestRequest()
>>> request.setPrincipal(anonymous)
>>> LoginLogout(None, request)()
u'<a href="@@login.html?nextURL=http%3A//127.0.0.1">[Login]</a>'

Logout, however, behaves differently. Not all authentication protocols (i.e. credentials extractors/challengers) support ‘logout’. Furthermore, we don’t know how an admin may have configured Zope’s authentication. Our solution is to rely on the admin to tell us explicitly that the site supports logout.

By default, the LoginLogout snippet will not provide a logout link for an unauthenticated principal. To illustrate, we’ll first setup a request with an unauthenticated principal:

>>> from zope.security.interfaces import IPrincipal
>>> from zope.interface import implements
>>> class Bob:
...     implements(IPrincipal)
...     id = 'bob'
...     title = description = ''
>>> bob = Bob()
>>> IUnauthenticatedPrincipal.providedBy(bob)
False
>>> request.setPrincipal(bob)

In this case, the default behavior is to return None for the snippet:

>>> print LoginLogout(None, request)()
None

To show a logout prompt, an admin must register a marker adapter that provides the interface:

>>> from zope.app.security.interfaces import ILogoutSupported

This flags to LoginLogout that the site supports logout. There is a ‘no-op’ adapter that can be registered for this:

>>> from zope.app.security import LogoutSupported
>>> from zope.app.testing import ztapi
>>> ztapi.provideAdapter(None, ILogoutSupported, LogoutSupported)

Now when we use LoginLogout with an unauthenticated principal, we get a logout prompt:

>>> LoginLogout(None, request)()
u'<a href="@@logout.html?nextURL=http%3A//127.0.0.1">[Logout]</a>'

Principal Terms

Principal Terms are used to support browser interfaces for searching principal sources. They provide access to tokens and titles for values. The principal terms view uses an authentication utility to get principal titles. Let’s create an authentication utility to demonstrate how this works:

>>> class Principal:
...     def __init__(self, id, title):
...         self.id, self.title = id, title
>>> from zope.interface import implements
>>> from zope.app.security.interfaces import IAuthentication
>>> from zope.app.security.interfaces import PrincipalLookupError
>>> class AuthUtility:
...     implements(IAuthentication)
...     data = {'jim': 'Jim Fulton', 'stephan': 'Stephan Richter'}
...
...     def getPrincipal(self, id):
...         title = self.data.get(id)
...         if title is not None:
...             return Principal(id, title)
...         raise PrincipalLookupError

Now we need to install the authentication utility:

>>> from zope.app.testing import ztapi
>>> ztapi.provideUtility(IAuthentication, AuthUtility())

We need a principal source so that we can create a view from it.

>>> from zope.component import getUtility
>>> class PrincipalSource:
...     def __contains__(self, id):
...          auth = getUtility(IAuthentication)
...          try:
...              auth.getPrincipal(id)
...          except PrincipalLookupError:
...              return False
...          else:
...              return True

Now we can create an terms view:

>>> from zope.app.security.browser.principalterms import PrincipalTerms
>>> terms = PrincipalTerms(PrincipalSource(), None)

Now we can ask the terms view for terms:

>>> term = terms.getTerm('stephan')
>>> term.title
'Stephan Richter'
>>> term.token
'c3RlcGhhbg__'

If we ask for a term that does not exist, we get a lookup error:

>>> terms.getTerm('bob')
Traceback (most recent call last):
...
LookupError: bob

If we have a token, we can get the principal id for it.

>>> terms.getValue('c3RlcGhhbg__')
'stephan'

CHANGES

3.6.0 (2009-01-31)

  • Changed mailing list address to zope-dev at zope.org, because zope3-dev is retired now. Changed “cheeseshop” to “pypi” in the package homepage.

  • Moved the protectclass module to zope.security leaving only a compatibility module here that imports from the new location.

  • Moved the <module> directive implementation to zope.security.

  • Use zope.container instead of zope.app.container;.

3.5.3 (2008-12-11)

  • use zope.browser.interfaces.ITerms instead of zope.app.form.browser.interfaces.

3.5.2 (2008-07-31)

  • Bug: It turned out that checking for regex was not much better of an idea, since it causes deprecation warnings in Python 2.4. Thus let’s look for a library that was added in Python 2.5.

3.5.1 (2008-06-24)

  • Bug: The gopherlib module has been deprecated in Python 2.5. Whenever the ZCML of this package was included when using Python 2.5, a deprecation warning had been raised stating that gopherlib has been deprecated. Provided a simple condition to check whether Python 2.5 or later is installed by checking for the deleted regex module and thus optionally load the security declaration for gopherlib.

3.5.0 (2008-02-05)

  • Feature: zope.app.security.principalregistry.PrincipalRegistry.getPrincipal returns zope.security.management.system_user when its id is used for the search key.

3.4.0 (2007-10-27)

  • Initial release independent of the main Zope tree.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

zope.app.security-3.6.0.tar.gz (37.8 kB view hashes)

Uploaded Source

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page