z3c.authenticator


Namez3c.authenticator JSON
Version 2.0 PyPI version JSON
download
home_pagehttps://github.com/zopefoundation/z3c.authenticator
SummaryIAuthentication implementation for for Zope3
upload_time2023-02-09 10:23:37
maintainer
docs_urlNone
authorRoger Ineichen and the Zope Community
requires_python>=3.7
licenseZPL 2.1
keywords zope3 z3c authentication auth group
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            This package provides an IAuthentication implementation for Zope3. Note that
this implementation is independent of zope.app.authentication and it doesn't
depend on that package. This means it doesn't even use the credential or
authentication plugins offered from zope.app.authentication package.


.. contents::

=======================
IAuthentication Utility
=======================

The Authenticator package provides a framework for authenticating principals
and associating information with them. It uses plugins and subscribers to get
its work done.

For a simple authentication utility to be used, it should be registered as a
utility providing the `zope.authentication.interfaces.IAuthentication` interface.

Our target is to support a handy IAuthentication utility which offers a simple
API for custom IUser implementations and does not depend on the default
zope.app.authentication implementation.


Security
--------

The Authenticator supports unique id tokens for principals. This means
principal that get deleted and again added with the same id, login etc. do
not have the same id again. We support this by generate a user id token
generated by the host id, timestamp, a random string and the login attribute.


What's different from PluggableAuthentication
---------------------------------------------

We use a different pattern for IAuthenticatorPlugins in this implementation
than used in PluggableAuthentication from zope.app.authentication,
because the pluggable authentication is not very handy when it comes to
implementing custom principal information. The IPrincipalInfo hook supporting
not propagate the password of a IInternalPrincipal is droped in this
implementation.

In our implementation we offer a IFoundPrincipal and IAuthenticatedPrincipal
which are implemented as adapters for a IUser. These adapters do not offer
their context which is the real IUser.

The Authenticator doesn't use a prefix. The usage of a prefix is only
implemented in the IGroupContainer.

We do not use a prefix in the IUserContainer because of the used unique user
id tokens. This will make sure that the same principal id doesn't get used at
a later time (common criteria). There is a ``add`` method which creates
this id for you based on the login. The __setitem__ should not get used
directly for adding IUser instances anymore. We heavily restricted the
usage of this method. See the inline doc tests in __setitem__ for more info.


Authentication
==============

The primary job of Authenticator is to authenticate principals. It uses
two types of plug-ins in its work:

  - Credentials Plugins

  - Authenticator Plugins

Credentials plugins are responsible for extracting user credentials from a
request. A credentials plugin may in some cases issue a 'challenge' to obtain
credentials. For example, a 'session' credentials plugin reads credentials
from a session (the "extraction"). If it cannot find credentials, it will
redirect the user to a login form in order to provide them (the "challenge").

Authenticator plugins are responsible for authenticating the credentials
extracted by a credentials plugin. They are also typically able to create
principal objects for credentials they successfully authenticate.

Given a request object, the Authenticator returns a principal object, if it
can. The Authenticator utility does this by first iterating through its
credentials plugins to obtain a set of credentials. If it gets credentials, it
iterates through its authenticator plugins to authenticate them.

If an authenticator succeeds in authenticating a set of credentials, the
Authenticator uses the authenticator to create a principal
corresponding to the credentials. The authenticator notifies subscribers if
an authenticated principal is created. Subscribers are responsible for adding
data, especially groups, to the principal. Typically, if a subscriber adds
data, it should also add corresponding interface declarations.


FAQ
---

Here some useful hints:

How should I set permission for principals?

  You can apply roles to groups
  and apply permissions to roles. Or you can directly apply local permisssions
  to groups or to principals. After setting up these mappings you can grant roles to
  groups. I always recommend a principal - group and permission - role mapping,
  this gives you the most possible abstraction which is useful if it comes
  to managing permissions and principals without directly invoking principals and
  permissions themselves. But of course you can grant permissions to groups or the
  worst thing, directly to principals. Granting permissions to principals is only
  useful if it comes to selective local permission settings for selected
  principals, e.g. an ownership-like permission setup.

How can I set permissions for all principals?

  You can register one
  group as IEveryone utility. This IGroup utility get applied to all principals.

Can I apply local groups to unauthenticated principals?

  Yes this will work.
  Since the last refactoring I refactored the IGroup implementation which makes
  it compatible with the principalregistry API. This means you can now register
  one local group as an unnamed IUnauthenticatedGroup. You can also register one
  local group as an unnamed IAuthenticatedGroup utility which will get applied
  to every authenticated principal or an unnamed utility for
  IUnauthenticatedGroup.

Can I apply a local group to every principal?

  Yes, this is possible if you
  register a local unnamed utility providing IEveryoneGroup.


Principal
---------

First we create a principal:

  >>> from z3c.authenticator import interfaces
  >>> from z3c.authenticator.user import User
  >>> login = 'bob'
  >>> password = 'secret'
  >>> title = 'Bob'
  >>> p = User(login, password, title)

Such a principal provides the following attributes be default

  >>> p.login
  'bob'

  >>> p.password.decode('utf-8')
  'secret'

  >>> p.title
  'Bob'

and IUser:

  >>> interfaces.IUser.providedBy(p)
  True


Authenticator Plugin
--------------------

First set up a UserContainer which will store the principals:

  >>> from z3c.authenticator.user import UserContainer
  >>> authPlugin = UserContainer()

Now we have a UserContainer that provides an IUserContainer:

  >>> interfaces.IUserContainer.providedBy(authPlugin)
  True

Now we will add the created principal to the principal container using the
container's ``add`` method:

  >>> uid, user = authPlugin.add(p)

The method returns the user id and the user object. The id gets generated
from the host IP address, the time, a random string and the user login attr.
This token should be unique and guaranteed that it will never get generated twice.
This allows us to add, delete and add the same user again without having such a
user inheriting existing permissions. We can test this token by comparing it
only with the __name__ of the object in this test since the token will be
different every test run.

  >>> user.__name__ == uid
  True

The returned user is still our previous added IUser

  >>> user is p
  True

  >>> len(user.__name__)
  32

  >>> user.login
  'bob'

  >>> user.password.decode('utf-8')
  'secret'

  >>> user.title
  'Bob'

Let's register the UserContainer as a named IAuthenticatorPlugin utility:

  >>> import zope.component
  >>> zope.component.provideUtility(authPlugin,
  ...     provides=interfaces.IAuthenticatorPlugin,
  ...     name='My Authenticator Plugin')


Credentials Plugin
------------------

After seting up the user and user container, we'll create a simple credentials
plugin:

  >>> import zope.interface
  >>> import zope.component

  >>> @zope.interface.implementer(interfaces.ICredentialsPlugin)
  ... class MyCredentialsPlugin(object):
  ...
  ...
  ...     def extractCredentials(self, request):
  ...         return {'login': request.get('login', ''),
  ...                 'password': request.get('password', '')}
  ...
  ...     def challenge(self, request):
  ...         pass # challenge is a no-op for this plugin
  ...
  ...     def logout(self, request):
  ...         pass # logout is a no-op for this plugin

As a plugin, MyCredentialsPlugin needs to be registered as a named utility or
it could be stored in the Authenticator attribute credentialsPlugins.
Use the first and register the plugina utility:

  >>> myCredentialsPlugin = MyCredentialsPlugin()
  >>> zope.component.provideUtility(myCredentialsPlugin,
  ...     name='My Credentials Plugin')


AuthenticatedPrincipal and FoundPrincipal
-----------------------------------------

While authenticator plugins provide users, they are not responsible for
creating principals. This function is performed by the Authenticator:

  >>> from z3c.authenticator.principal import AuthenticatedPrincipal
  >>> from z3c.authenticator.principal import FoundPrincipal
  >>> zope.component.provideAdapter(AuthenticatedPrincipal,
  ...     provides=interfaces.IAuthenticatedPrincipal)

  >>> zope.component.provideAdapter(FoundPrincipal,
  ...     provides=interfaces.IFoundPrincipal)


Configuring the Authenticator
-----------------------------

Finally, we'll create the Authenticator itself:

  >>> from z3c.authenticator import authentication
  >>> auth = authentication.Authenticator()

and configure it with the two plugins:

  >>> auth.credentialsPlugins = ('My Credentials Plugin', )
  >>> auth.authenticatorPlugins = ('My Authenticator Plugin', )


Authenticate
------------

We can now use the Authenticator to authenticate a sample request:

  >>> from zope.publisher.browser import TestRequest
  >>> print(auth.authenticate(TestRequest()))
  None

In this case, we cannot authenticate an empty request. In the same way, we
will not be able to authenticate a request with the wrong credentials:

  >>> request = TestRequest(form={'login': 'let me in!', 'password': 'secret'})
  >>> print(auth.authenticate(request))
  None

However, if we provide the proper credentials:

  >>> request = TestRequest(form={'login': 'bob', 'password': 'secret'})
  >>> bob = auth.authenticate(request)
  >>> bob
  <AuthenticatedPrincipal...>

  >>> interfaces.IAuthenticatedPrincipal.providedBy(bob)
  True

we get an authenticated principal.


Changing login names
--------------------

Changing the login (i.e. username) of a principal is always a critical task because such a
login together with a password is the key to our implemenation. Let's try to
change the login and check if everything is correct. We can do this by getting the
principal from the UserContainer and changing the login on the IUser
implementation:

  >>> internal = authPlugin[bob.id]
  >>> internal.login = 'bob2'

Now we should be able to login with the new login:

  >>> request = TestRequest(form={'login': 'bob2', 'password': 'secret'})
  >>> bob2 = auth.authenticate(request)
  >>> bob2
  <AuthenticatedPrincipal ...>

  >>> bob2.title
  'Bob'

But not with the old one:

  >>> request = TestRequest(form={'login': 'bob', 'password': 'secret'})
  >>> auth.authenticate(request) == None
  True

The user bob has still the same id as bob2 since the user id token doesn't
get changed by changing the login:

  >>> bob.id == bob2.id
  True


Events
------

Authenticating a principal will create events.

  >>> from zope.component.eventtesting import getEvents
  >>> from zope.component.eventtesting import clearEvents

We can verify that the appropriate event was published:

  >>> clearEvents()
  >>> request = TestRequest(form={'login': 'bob2', 'password': 'secret'})
  >>> bobAgain = auth.authenticate(request)

And the principal attribute in the event provides the authenticated principal:

  >>> [event] = getEvents(interfaces.IAuthenticatedPrincipalCreated)
  >>> event.principal is bobAgain
  True

  >>> event.principal
  <AuthenticatedPrincipal ...>

  >>> event.request is request
  True

The principal has the id, title, and description.

  >>> event.principal.title
  'Bob'

  >>> event.principal.id == uid
  True

  >>> event.principal.description
  ''

We provide subscribers to these events that can be used for doing custom
processing. Note, the principal attibute provides an IAuthenticatedPrincipal:

  >>> def addInfo(event):
  ...     id = event.principal.id
  ...     event.principal.description = 'Description for: %s' % id

  >>> zope.component.provideHandler(addInfo,
  ...     [interfaces.IAuthenticatedPrincipalCreated])

Now, if we authenticate a principal, its description is set:

  >>> principal = auth.authenticate(request)
  >>> principal.description
  'Description for: ...'


Customization
-------------

Let's show you how the existing pattern can get used in a real use case. In
the next sample we'd like to provide an additional email attribute for principals.
First we have to define the interfaces declaring the email attribute:

  >>> class IMyEmail(zope.interface.Interface):
  ...     email = zope.schema.TextLine(
  ...         title='EMail',
  ...         description='The EMail')

  >>> class IMyUser(IMyEmail, interfaces.IUser):
  ...     """Custom IUser interface."""

  >>> class IMyFoundPrincipal(IMyEmail, interfaces.IFoundPrincipal):
  ...     """Custom IIMyFoundrincipal interface."""

  >>> class IMyAuthenticatedPrincipal(IMyEmail,
  ...     interfaces.IAuthenticatedPrincipal):
  ...     """Custom IAuthenticatedPrincipal interface."""

After the schema, we define a custom principal implementation implementing
this interface:

  >>> @zope.interface.implementer(IMyUser)
  ... class MyUser(User):
  ...     def __init__(self, login, password, title, description, email):
  ...         super(MyUser, self).__init__(login, password, title,
  ...                                           description)
  ...         self.email = email

Now we have to define the AuthenticatedPrincipal for MyUser:

  >>> @zope.interface.implementer(IMyAuthenticatedPrincipal)
  ... class MyAuthenticatedPrincipal(AuthenticatedPrincipal):
  ...     def __init__(self, principal):
  ...         super(MyAuthenticatedPrincipal, self).__init__(principal)
  ...         self.email = principal.email

And we have to define the FoundPrincipal for MyUser:

  >>> @zope.interface.implementer(IMyFoundPrincipal)
  ... class MyFoundPrincipal(FoundPrincipal):
  ...     def __init__(self, principal):
  ...         super(MyFoundPrincipal, self).__init__(principal)
  ...         self.email = principal.email

Note that you can provide different attributes for the found and authenticated
principals if needed. That's up to you what you like to do with these attributes
later.

Now we need to register our custom authenticated and found principal
adapters:

  >>> zope.component.provideAdapter(MyAuthenticatedPrincipal,
  ...     provides=interfaces.IAuthenticatedPrincipal)

  >>> zope.component.provideAdapter(MyFoundPrincipal,
  ...     provides=interfaces.IFoundPrincipal)

Now we can use them without any other event subscribers or other registration
in our principal container. Let's add a principal to this container:

  >>> p = MyUser('max', 'password', 'Max', '', 'max@foobar.com')
  >>> token, max = authPlugin.add(p)
  >>> len(token)
  32

  >>> max.__name__ == token
  True

  >>> max.password.decode('utf-8')
  'password'

  >>> max.title
  'Max'

  >>> max.email
  'max@foobar.com'

Let's try to authenticate...

  >>> request = TestRequest(form={'login': 'max', 'password': 'password'})
  >>> authenticated = auth.authenticate(request)

and check your authenticated principal:

  >>> interfaces.IAuthenticatedPrincipal.providedBy(authenticated)
  True

  >>> authenticated
  <MyAuthenticatedPrincipal ...>

  >>> authenticated.id == token
  True

  >>> authenticated.email
  'max@foobar.com'

Check getUserByLogin:

  >>> max = authPlugin.getUserByLogin('max')
  >>> max.__class__.__name__
  'MyUser'

  >>> authPlugin.getUserByLogin('max').login
  'max'

  >>> authPlugin.getUserByLogin('max').__name__ == token
  True


A handy feature for migration is that you can set your own ``token``.
Usually in z.a.authentication the ``token`` == login and we want to keep it
that way, unless you want to iterate through all permissions and whatever.
Note, the __name__ and the id in the container must be the *SAME* object.

  >>> login = 'migrateduser'
  >>> p = User(login, 'password', 'John')

Preset the ``token``

  >>> p.__name__ = login

Watch out, we use __setitem__ instead of add(), because add() would kill off
the preset ``token`` in __name__.

  >>> authPlugin[login] = p

Here we are, the user is set with the non-generated token.

  >>> 'migrateduser' in authPlugin.keys()
  True

  >>> authPlugin['migrateduser']
  <z3c.authenticator.user.User object at ...>

  >>> authPlugin.getUserByLogin('migrateduser')
  <z3c.authenticator.user.User object at ...>


Edge cases
----------

We can have Users with text logins, as we allow this with TextLine in IUser.

  >>> p = User('bob'+chr(233), 'password', 'title')

Adding it should not fail:

  >>> uid, user = authPlugin.add(p)



=====
Group
=====

Group container provide support for groups information stored in the ZODB. 
They are persistent, and must be contained within the IAuthentication that 
use them.


Group
-----

Like other users, groups are created when they are needed.

  >>> from z3c.authenticator import interfaces
  >>> from z3c.authenticator.group import Group
  >>> group1 = Group('groups')
  >>> group1
  <Group None>

  >>> interfaces.IGroup.providedBy(group1)
  True

  >>> group1.title
  'groups'

  >>> group1.description
  ''

  >>> group1.principals
  ()


GroupContainer
--------------

Group containers contain IGroup objects. A IAuthentication will adapt
IFoundGroup to this IGroup objects.

  >>> from z3c.authenticator.group import GroupContainer
  >>> groups = GroupContainer('groups.')

  >>> interfaces.IGroupContainer.providedBy(groups)
  True

We can add your previous created group to the group container using the 
addGroup method which returns the group id and group:

  >>> gid, g1 = groups.addGroup('g1', group1)
  >>> gid
  'groups.g1'

  >>> interfaces.IGroup.providedBy(g1)
  True

  >>> g1.__name__
  'groups.g1'

Note that when group is added, a GroupAdded event is generated:

  >>> from zope.component.eventtesting import getEvents
  >>> getEvents(interfaces.IGroupAdded)
  [<GroupAdded 'groups.g1'>]

Groups are defined with respect to an authentication service. Groups must be 
accessible via an authentication service and can contain principals accessible
via an authentication service. To illustrate the group interaction with the 
authentication service, we will setup a Authenticator utility:

  >>> from z3c.authenticator.authentication import Authenticator
  >>> authenticator = Authenticator()

Give them a location and register them as a IAuthentication utility :

  >>> import zope.component
  >>> from zope.authentication.interfaces import IAuthentication
  >>> rootFolder['authenticator'] = authenticator
  >>> zope.component.provideUtility(authenticator, IAuthentication)

We will create and register a new principals utility:

  >>> zope.component.provideUtility(authenticator, IAuthentication)

We also need to register the group athentication plugin:

  >>> zope.component.provideUtility(groups, 
  ...     provides=interfaces.IAuthenticatorPlugin, 
  ...     name='My Group Plugin')

After setup the group and group container, we will create a simple credentials 
plugin and add them to the authentication utility:

  >>> import zope.interface
  >>> from z3c.authenticator import interfaces

  >>> @zope.interface.implementer(interfaces.ICredentialsPlugin)
  ... class MyCredentialsPlugin(object):
  ...
  ...     def extractCredentials(self, request):
  ...         return {'login':request.get('login', ''), 
  ...                 'password':request.get('password', '')}
  ...
  ...     def challenge(self, request):
  ...         pass # challenge is a no-op for this plugin
  ...
  ...     def logout(self, request):
  ...         pass # logout is a no-op for this plugin

and configure and add the credential plugin to the Authenticator:

  >>> myCredentialsPlugin = MyCredentialsPlugin()
  >>> authenticator['credentials'] = myCredentialsPlugin
  >>> authenticator.credentialsPlugins = ('credentials', )

We also need a principal and a IAuthenticationPlugin:

  >>> from z3c.authenticator.user import User
  >>> p1 = User('p1', 'password', 'Principal 1')
  >>> p2 = User('p2', 'password', 'Principal 2')
  >>> p3 = User('p3', 'password', 'Principal 3')
  >>> p4 = User('p4', 'password', 'Principal 4')

  >>> from z3c.authenticator.user import UserContainer
  >>> users = UserContainer()
  >>> token1, p1 = users.add(p1)
  >>> token2, p2 = users.add(p2)
  >>> token3, p3 = users.add(p3)
  >>> token4, p4 = users.add(p4)

Add the GroupContainer and UserContainer to the Authenticator and 
set the correct plugin names

  >>> authenticator['users'] = users
  >>> authenticator['groups'] = groups
  >>> authenticator.authenticatorPlugins = ('users', 'groups')


Adding users to groups
----------------------

Now we can set the users on the group but first we need to register the
IFoundPrincipal adapter for groups. The FoundGroup adapter provides this
interface:

  >>> from z3c.authenticator.principal import FoundGroup
  >>> zope.component.provideAdapter(FoundGroup, 
  ...     provides=interfaces.IFoundPrincipal)

And we also need to provide the IFoundPrincipal and IAuthenticatedPrincipal
adapter for IPrincipal objects:

  >>> from z3c.authenticator.principal import AuthenticatedPrincipal
  >>> from z3c.authenticator.principal import FoundPrincipal
  >>> zope.component.provideAdapter(AuthenticatedPrincipal, 
  ...     provides=interfaces.IAuthenticatedPrincipal)

  >>> zope.component.provideAdapter(FoundPrincipal, 
  ...     provides=interfaces.IFoundPrincipal)

And we need the ``setGroupsForPrincipal`` subscriber:

  >>> from z3c.authenticator.group import setGroupsForPrincipal
  >>> zope.component.provideHandler(setGroupsForPrincipal, 
  ...     [interfaces.IPrincipalCreated])

  >>> g1.principals = [p1.__name__, p2.__name__]
  >>> g1.principals
  (..., ...)

  >>> g1.principals[0] == p1.__name__
  True

  >>> g1.principals[1] == p2.__name__
  True

Adding users fires an event.

  >>> getEvents(interfaces.IPrincipalsAddedToGroup)[-1]
  <PrincipalsAddedToGroup [..., ...] 'groups.g1'>

We can now look up groups for the users:

  >>> groups.getGroupsForPrincipal(p1.__name__)
  ('groups.g1',)

Note that the group id is a concatenation of the group-folder prefix
and the name of the group object within the folder.

If we delete a group:

  >>> del groups['groups.g1']

then the groups folder loses the group information for that group's users:

  >>> groups.getGroupsForPrincipal('p1')
  ()

but the principal information on the group is unchanged:

  >>> g1.principals
  (..., ...)

  >>> g1.principals[0] == p1.__name__
  True

  >>> g1.principals[1] == p2.__name__
  True

It also fires an event showing that the users are removed from the groups.

  >>> getEvents(interfaces.IPrincipalsRemovedFromGroup)[-1]
  <PrincipalsRemovedFromGroup [..., ...] 'groups.g1'>

Adding the group again within a different name will make the groups 
available for the principal. Let's use a different group name:

  >>> groups['groups.G1'] = g1

  >>> groups.getGroupsForPrincipal(p1.__name__)
  ('groups.G1',)

Here we see that the new name is reflected in the group information.

An event is fired, as usual.

  >>> getEvents(interfaces.IPrincipalsAddedToGroup)[-1]
  <PrincipalsAddedToGroup [..., ...] 'groups.G1'>

In terms of member events (members added and removed from groups), we have
now seen that events are fired when a group object is added and when it is 
removed from a group container; and we have seen that events are fired
when a principal is added to an already-registered groups.  Events are also
fired when a principal is removed from an already-registered groups.  Let's
quickly see some more examples.

  >>> g1.principals = (p1.__name__, p3.__name__, p4.__name__)
  >>> g1.principals
  (..., ..., ...)

  >>> g1.principals[0] == p1.__name__
  True

  >>> g1.principals[1] == p3.__name__
  True

  >>> g1.principals[2] == p4.__name__
  True

  >>> getEvents(interfaces.IPrincipalsAddedToGroup)[-1]
  <PrincipalsAddedToGroup [..., ...] 'groups.G1'>

  >>> getEvents(interfaces.IPrincipalsRemovedFromGroup)[-1]
  <PrincipalsRemovedFromGroup [...] 'groups.G1'>

  >>> g1.principals = (p1.__name__, p2.__name__)
  >>> g1.principals
  (..., ...)

  >>> g1.principals[0] == p1.__name__
  True

  >>> g1.principals[1] == p2.__name__
  True

  >>> getEvents(interfaces.IPrincipalsAddedToGroup)[-1]
  <PrincipalsAddedToGroup [...] 'groups.G1'>

  >>> getEvents(interfaces.IPrincipalsRemovedFromGroup)[-1]
  <PrincipalsRemovedFromGroup [..., ...] 'groups.G1'>

  >>> groups.getGroupsForPrincipal(p2.__name__)
  ('groups.G1',)


Groups in groups
----------------

Groups can contain groups:

  >>> g2 = Group('Group Two')
  >>> groups['groups.G2'] = g2
  >>> g2.principals
  ()
  
  >>> g2.principals = ['groups.G1']

  >>> g2.principals
  ('groups.G1',)

  >>> groups.getGroupsForPrincipal('groups.G2')
  ()

  >>> g1.principals
  (..., ...)

  >>> g1.principals[0] == p1.__name__
  True

  >>> g1.principals[1] == p2.__name__
  True

  >>> groups.getGroupsForPrincipal('groups.G1')
  ('groups.G2',)

  >>> old = getEvents(interfaces.IPrincipalsAddedToGroup)[-1]
  >>> old
  <PrincipalsAddedToGroup ['groups.G1'] 'groups.G2'>

Groups cannot contain cycles:

  >>> g1.principals
  (..., ...)

  >>> g1.principals[0] == p1.__name__
  True

  >>> g1.principals[1] == p2.__name__
  True

  >>> g2.principals
  ('groups.G1',)

  >>> g1.principals = (p1.__name__, p2.__name__, 'groups.G2')
  Traceback (most recent call last):
  ...
  z3c.authenticator.group.GroupCycle: (...)

  >>> g1.principals
  (..., ...)


  >>> g1.principals[0] == p1.__name__
  True

  >>> g1.principals[1] == p2.__name__
  True

Trying to do so does not fire an event.

  >>> getEvents(interfaces.IPrincipalsAddedToGroup)[-1] is old
  True

They need not be hierarchical:

  >>> ga = Group("Group A")
  >>> groups['groups.GA'] = ga

  >>> gb = Group("Group B")
  >>> groups['groups.GB'] = gb
  >>> gb.principals = ['groups.GA']

  >>> gc = Group("Group C")
  >>> groups['groups.GC'] = gc
  >>> gc.principals = ['groups.GA']

  >>> gd = Group("Group D")
  >>> groups['groups.GD'] = gd
  >>> gd.principals = ['groups.GA', 'groups.GB']

  >>> ga.principals = [p1.__name__]

Group containers provide a very simple search interface.  They perform
simple string searches on group titles and descriptions.

  >>> list(groups.search({'search': 'gro'}))
  ['groups.G1', 'groups.G2',
   'groups.GA', 'groups.GB', 'groups.GC', 'groups.GD']

  >>> list(groups.search({'search': 'two'}))
  ['groups.G2']

They also support batching:

  >>> list(groups.search({'search': 'gro'}, 2, 3))
  ['groups.GA', 'groups.GB', 'groups.GC']


If you don't supply a search key, no results will be returned:

  >>> list(groups.search({}))
  []


Identifying groups
------------------

The function, `setGroupsForPrincipal`, is a subscriber to
principal-creation events.  It adds any group-folder-defined groups to
users in those groups:

  >>> auth1 = authenticator.getPrincipal(p1.__name__)

  >>> auth1.groups
  ['groups.G1', 'groups.GA']

Of course, this applies to groups too:

  >>> g1 = authenticator.getPrincipal('groups.G1')
  >>> g1.id
  'groups.G1'

  >>> g1.groups
  ['groups.G2']

A FoundGroup provides IFoundGroup which is inherited from 
IFoundPrincipal and IGroup:

  >>> interfaces.IFoundGroup.providedBy(g1)
  True

  >>> interfaces.IFoundPrincipal.providedBy(g1)
  True

  >>> import zope.security.interfaces
  >>> zope.security.interfaces.IGroup.providedBy(g1)
  True


specialGroups
-------------

Two special groups, IAuthenticatedGroup, and IEveryoneGroup may apply to users
created by the IAuthentication utility.  There is a subscriber called 
``specialGroups``. This subscriber can set this special groups on any 
principal if IAuthenticatedGroup, or IEveryoneGroup utilities are 
provided. The subscriber knows also how to apply local groups to principals.
Note, principals means IAuthenticatedPrincipal, IFoundPrincipal or IFoundGroup.

If we notify the subscriber with the principal, nothing will happen
because the groups haven't been defined:

  >>> from z3c.authenticator.principal import FoundPrincipal
  >>> from z3c.authenticator.event import FoundPrincipalCreated
  >>> from z3c.authenticator.group import specialGroups
  >>> x = User('x', 'password', 'X')
  >>> found = FoundPrincipal(x)
  >>> event = FoundPrincipalCreated(authenticator, found)
  >>> specialGroups(event)
  >>> found.groups
  []

Now, if we define the Everybody group:

  >>> import zope.authentication.interfaces
  >>> @zope.interface.implementer(
  ...        zope.authentication.interfaces.IEveryoneGroup)
  ... class EverybodyGroup(Group):
  ...     pass

  >>> all = EverybodyGroup('groups.all')
  >>> groups['groups.all'] = all
  >>> zope.component.provideUtility(all,
  ...     zope.authentication.interfaces.IEveryoneGroup)

Then the group will be added to the principal:

  >>> specialGroups(event)
  >>> found.groups
  ['groups.all']

Similarly for the authenticated group:

  >>> @zope.interface.implementer(
  ...         zope.authentication.interfaces.IAuthenticatedGroup)
  ... class AuthenticatedGroup(Group):
  ...     pass

  >>> authenticated = AuthenticatedGroup('groups.authenticated')
  >>> groups['groups.authenticated'] = authenticated
  >>> zope.component.provideUtility(authenticated,
  ...     zope.authentication.interfaces.IAuthenticatedGroup)

Then the group will be added to the principal:

  >>> found.groups = []
  >>> specialGroups(event)
  >>> found.groups.sort()
  >>> found.groups
  ['groups.all', 'groups.authenticated']

It is important that we do not apply a group twice since the
UnauthenticatedPrincipal is a single instance in the securitypolicy. This issue
is fixed in version 0.6.1 and 0.7.1

  >>> specialGroups(event)
  >>> found.groups
  ['groups.all', 'groups.authenticated']


allGroups
---------

The `allGroups` attribute is a readonly iterable of the full closure of the
groups in the `groups` attribute. Let's define a new principal first:

  >>> p = User('p', 'password', 'Principal')
  >>> token, p = users.add(p)

And the groups:

  >>> ga = Group("Administrators")
  >>> gr = Group("Reviewers")
  >>> gid, ga = groups.addGroup('Administrators', ga)
  >>> gid, gr = groups.addGroup('Reviewers', gr)

If the principal is a direct member of the 'Administrators' group, 

  >>> ga.principals = [p.__name__]

then getGroupsForPrincipal would be ['Administrators']

  >>> groups.getGroupsForPrincipal(p.__name__)
  ('groups.Administrators',)
  
and if the 'Administrators' group is a member of the 'Reviewers' group, 

  >>> gr.principals = [ga.id]

then groups would be ['Administrators'] too.

  >>> groups.getGroupsForPrincipal(p.__name__)
  ('groups.Administrators',)

now let's use the setGroupsForPrincipal subscriber which knows how to apply
the groups to the found principal:

  >>> pFound = FoundPrincipal(p)
  >>> event = FoundPrincipalCreated(authenticator, pFound)
  >>> setGroupsForPrincipal(event)

As you can see and pFound.groups is ['Administrators'].

  >>> sorted(pFound.groups)
  ['groups.Administrators']

And pFound.allGroups is ['Administrators', 'Reviewers'].

  >>> sorted(pFound.allGroups)
  ['groups.Administrators', 'groups.Reviewers']


==========
Vocabulary
==========

The vocabulary module provides vocabularies for the authenticator plugins and
the credentials plugins.

The options should include the unique names of all of the plugins that provide
the appropriate interface (IAuthentiatorPlugin or ICredentialsPlugin, 
respectively) for the current context which is expected to be a IAuthenticator 
utility, hereafter referred to as a Authenticator.

These names may be for objects contained within the Authenticator 
("contained plugins"), or may be utilities registered for the specified 
interface, found in the context of the Authenticator 
("utility plugins"). Contained plugins mask utility plugins of the same name. 
They also may be names currently selected in the Authenticator that do 
not actually have a corresponding plugin at this time.

Here is a short example of how the vocabulary should work.  Let's say we're
working with authentication plugins.  We'll create some faux
authentication plugins, and register some of them as utilities and put
others in a faux Authenticator.

    >>> from z3c.authenticator import interfaces
    >>> import zope.interface
    >>> import zope.component
    >>> @zope.interface.implementer(interfaces.IAuthenticatorPlugin)
    ... class DemoPlugin(object):
    ...     def __init__(self, name):
    ...         self.name = name
    ...
    >>> utility_plugins = dict(
    ...     (i, DemoPlugin('Plugin %d' % i)) for i in range(4))
    >>> contained_plugins = dict(
    ...     (i, DemoPlugin('Plugin %d' % i)) for i in range(1, 5))
    >>> sorted(utility_plugins.keys())
    [0, 1, 2, 3]
    >>> for p in utility_plugins.values():
    ...     zope.component.provideUtility(p, name=p.name)
    ...
    >>> sorted(contained_plugins.keys()) # 1 will mask utility plugin 1
    [1, 2, 3, 4]
    >>> @zope.interface.implementer(interfaces.IAuthenticator)
    ... class DemoAuth(dict):
    ...     def __init__(self, *args, **kwargs):
    ...         super(DemoAuth, self).__init__(*args, **kwargs)
    ...         self.authenticatorPlugins = ('Plugin 3', 'Plugin X')
    ...         self.credentialsPlugins = ('Plugin 4', 'Plugin X')
    ...
    >>> auth = DemoAuth((p.name, p) for p in contained_plugins.values())
    
    >>> @zope.component.adapter(zope.interface.Interface)
    ... @zope.interface.implementer(zope.component.IComponentLookup)
    ... def getSiteManager(context):
    ...     return zope.component.getGlobalSiteManager()
    ...
    >>> zope.component.provideAdapter(getSiteManager)


authenticatorPlugins
--------------------

We are now ready to create a vocabulary that we can use.  The context is
our faux authentication utility, `auth`.

    >>> from z3c.authenticator import vocabulary
    >>> vocab = vocabulary.authenticatorPlugins(auth)

Iterating over the vocabulary results in all of the terms, in a relatively
arbitrary order of their names.  (This vocabulary should typically use a
widget that sorts values on the basis of localized collation order of the
term titles.)

    >>> [term.value for term in vocab] # doctest: +NORMALIZE_WHITESPACE
    ['Plugin 0', 'Plugin 1', 'Plugin 2', 'Plugin 3', 'Plugin 4',
     'Plugin X']

Similarly, we can use `in` to test for the presence of values in the
vocabulary.

    >>> ['Plugin %s' % i in vocab for i in range(-1, 6)]
    [False, True, True, True, True, True, False]
    >>> 'Plugin X' in vocab
    True

The length reports the expected value.

    >>> len(vocab)
    6

One can get a term for a given value using `getTerm()`; its token, in
turn, should also return the same effective term from `getTermByToken`.

    >>> values = ['Plugin 0', 'Plugin 1', 'Plugin 2', 'Plugin 3', 'Plugin 4',
    ...           'Plugin X']
    >>> for val in values:
    ...     term = vocab.getTerm(val)
    ...     assert term.value == val
    ...     term2 = vocab.getTermByToken(term.token)
    ...     assert term2.token == term.token
    ...     assert term2.value == val
    ...

The terms have titles, which are message ids that show the plugin title or id
and whether the plugin is a utility or just contained in the auth utility.
We'll give one of the plugins a dublin core title just to show the
functionality.

    >>> import zope.dublincore.interfaces
    >>> class ISpecial(zope.interface.Interface):
    ...     pass
    ...
    >>> zope.interface.directlyProvides(contained_plugins[1], ISpecial)
    >>> @zope.interface.implementer(
    ...         zope.dublincore.interfaces.IDCDescriptiveProperties)
    ... class DemoDCAdapter(object):
    ...     zope.component.adapts(ISpecial)
    ...     def __init__(self, context):
    ...         pass
    ...     title = 'Special Title'
    ...
    >>> zope.component.provideAdapter(DemoDCAdapter)

We need to regenerate the vocabulary, since it calculates all of its data at
once.

    >>> vocab = vocabulary.authenticatorPlugins(auth)

Now we'll check the titles.  We'll have to translate them to see what we
expect.

    >>> from zope import i18n
    >>> import pprint
    >>> pprint.pprint([i18n.translate(term.title) for term in vocab])
    ['Plugin 0 (a utility)',
     'Special Title (in contents)',
     'Plugin 2 (in contents)',
     'Plugin 3 (in contents)',
     'Plugin 4 (in contents)',
     'Plugin X (not found; deselecting will remove)']


credentialsPlugins
------------------

For completeness, we'll do the same review of the credentialsPlugins.

    >>> @zope.interface.implementer(interfaces.ICredentialsPlugin)
    ... class DemoPlugin(object):
    ...     def __init__(self, name):
    ...         self.name = name
    ...
    >>> utility_plugins = dict(
    ...     (i, DemoPlugin('Plugin %d' % i)) for i in range(4))
    >>> contained_plugins = dict(
    ...     (i, DemoPlugin('Plugin %d' % i)) for i in range(1, 5))
    >>> for p in utility_plugins.values():
    ...     zope.component.provideUtility(p, name=p.name)
    ...
    >>> auth = DemoAuth((p.name, p) for p in contained_plugins.values())
    >>> vocab = vocabulary.credentialsPlugins(auth)

Iterating over the vocabulary results in all of the terms, in a relatively
arbitrary order of their names.  (This vocabulary should typically use a
widget that sorts values on the basis of localized collation order of the term
titles.) Similarly, we can use `in` to test for the presence of values in the
vocabulary. The length reports the expected value.

    >>> [term.value for term in vocab] # doctest: +NORMALIZE_WHITESPACE
    ['Plugin 0', 'Plugin 1', 'Plugin 2', 'Plugin 3', 'Plugin 4',
     'Plugin X']
    >>> ['Plugin %s' % i in vocab for i in range(-1, 6)]
    [False, True, True, True, True, True, False]
    >>> 'Plugin X' in vocab
    True
    >>> len(vocab)
    6

One can get a term for a given value using `getTerm()`; its token, in
turn, should also return the same effective term from `getTermByToken`.

    >>> values = ['Plugin 0', 'Plugin 1', 'Plugin 2', 'Plugin 3', 'Plugin 4',
    ...           'Plugin X']
    >>> for val in values:
    ...     term = vocab.getTerm(val)
    ...     assert term.value == val
    ...     term2 = vocab.getTermByToken(term.token)
    ...     assert term2.token == term.token
    ...     assert term2.value == val
    ...

The terms have titles, which are message ids that show the plugin title or id
and whether the plugin is a utility or just contained in the auth utility.
We'll give one of the plugins a dublin core title just to show the
functionality. We need to regenerate the vocabulary, since it calculates all
of its data at once. Then we'll check the titles.  We'll have to translate
them to see what we expect.

    >>> zope.interface.directlyProvides(contained_plugins[1], ISpecial)
    >>> vocab = vocabulary.credentialsPlugins(auth)
    >>> pprint.pprint([i18n.translate(term.title) for term in vocab])
    ['Plugin 0 (a utility)',
     'Special Title (in contents)',
     'Plugin 2 (in contents)',
     'Plugin 3 (in contents)',
     'Plugin 4 (in contents)',
     'Plugin X (not found; deselecting will remove)']


=======
CHANGES
=======

2.0 (2023-02-09)
----------------

- Drop support for Python 2.6, 2.7, 3.4, 3.5, 3.6.

- Add Python 3.7, 3.8, 3.9, 3.10, 3.11 support.

- Remove unused imports.


1.0.1 (2018-05-16)
------------------

- bugfix: remove None from queryNextUtiliy because getNextUtility signature got
  changed and None was used as name argument.

- cleanup, removed unused imports

- Get rid of deprecation warning. Switch hooks import from zope.site to
  zope.component

- skip test_exception_in_subscriber_leaves_item_in_place for IAuthenticator
  container. This zope.container test method left over an event subscriber with
  zope.container version 4.2.1 which raises an error in other methods. Needs
  review and probably a tear down in zope.container test method.


1.0.0 (2017-04-17)
------------------

- Ping Python support to 2.7, 3.5, 3.6 and PyPy.


1.0.0a5 (2013-03-31)
--------------------

- More Py3 compatibility bugs in untested code.


1.0.0a4 (2013-02-28)
--------------------

- Removed support for Zope generations. It was not used and application-wide
  generations make more sense anyways. This removed the
  ``zope.app.generations`` and ``zope.generations`` dependencies.

- Made ``z3c.configurator`` support optional.

- Cleaned up source code a little bit.


1.0.0a3 (2013-02-28)
--------------------

- Changed manifest to actually include the page templates.


1.0.0a2 (2013-02-28)
--------------------

- Added version Trove classifiers.


1.0.0a1 (2013-02-28)
--------------------

- Dropped support for Python 2.4 and 2.5, added Python 3.3 support.

- Removed dependencies on ``zope.app`` packages.


0.8.1 (2011-01-21)
------------------

- Bugfix: Did not handle unicode IUser.login values.

- Fixed DeprecationWarnings.


0.8.0 (2010-01-25)
------------------

- Security Fix: move camefrom url to a session variable instead of exposing
  the url in the login form. Because the camefrom url is built at server side
  based on local information and will always only use internal traversal names.
  Exposing this camefrom query in the login url gives others only a point to
  attack because it could be simply set by a unfriendly domain with a custom
  url. This is much better since such a unfriendly 3rd party domain url doesn't
  get redirected by default based on the changes in zope.publisher's redirect
  method. (zope.publisher 3.9.3 does only redirect to urls located in the same
  domain by default)

  Remove all camefrom widgets and queries in our custom forms if you use any.
  You can just set and get the camefrom session variable in your custom forms
  if you need to.

0.7.2 (2010-01-26)
------------------

- Bugfix: Failed miserably on challenge on pages having non-ASCII names.

0.7.1 (2009-08-19)
------------------

- Bugfix: the method specialGroups applied groups everytime the method get
  called even if the group was already applied. This is a problem if the
  global shared unauthenticated principal instance is used because it will
  apply similar groups till the server get restarted and a new principal
  instance is used.

- Feature: added getUserByLogin to IUserContainer

- Added a test for user migration (that they will keep their ID)


0.7.0 (2009-05-11)
------------------

- Update dependencies:

   * Use ``zope.container`` instead of ``zope.app.container``.
   * Use ``zope.site`` instead of ``zope.app.component``.
   * Use ``zope.authentication`` and ``zope.principalregistry`` instead
     of ``zope.app.security``.
   * Use ``zope.password`` instead of maintaining a copy of password
     managers.

- Drop dependency on z3c.i18n and recreate a message factory instead.

0.6.2 (2010-01-26)
------------------

- Bugfix: Failed miserably on challenge on pages having non-ASCII names.
  (Backport from 0.7.2)

0.6.1 (2009-08-19)
------------------

- Backport bugfix 0.7.1 to 0.6.1. See branches/roger-0.6.0

  the method specialGroups applied groups everytime the method get
  called even if the group was already applied. This is a problem if the
  global shared unauthenticated principal instance is used because it will
  apply similar groups till the server get restarted and a new principal
  instance is used.


0.6.0 (2009-01-04)
------------------

- Feature: added support for local IUnauthenticatedPrincipal. This is usefull
  if you need to apply local roles to IUnauthenticatedPrincipal. This was not
  possible before and is not possible in zope.app.authentication

- Feature: implemented initial grant view based on ISource widget. Note, this
  source widget terms implementation which is very complex to understand will
  get moved to z3c.from if we fixed the ITerm dependency. Which means ITerm
  needs to get moved out of zope.app.form first.

- Feature: added support for next utility lookup in authenticate call. By
  default the principals from the global principalregistry get involved now.
  You can disable this feature by setting includeNextUtilityForAuthenticate to
  False.

- Feature: added PrincipalRegistryAuthenticatorPlugin which allows to
  authenticate principals defined in global principal registry.

- Feature: implemented z3c.form prefix support in SessionCredentialsPlugin. Now
  there is an option called prefixes which can be used for define a list of
  used z3c.form prefixes. This makes it simpler for supporting different forms
  and adjust the credential extraction.

- Renamed IGroupPrincipal to IFoundGroup which makes it more understandable
  why this adapter implementation is needed. The IFoundGroup adapter is now
  also used for zope.security.interfaces.IGroup principals. This makes it
  possible to use them in the new principalregistry credential. Provide
  deprecation message for the old IGroupPrincipal implementation.

- Removed dependency for zapi. But it's not really gone since other packages
  use zapi too.

- Removed unused InvalidPrincipalIds and InvalidGroupId exceptions

- Removed unused IMemberAwareGroup support. This interface is not used in zope
  at all.

- Added documentation for Pypi home page.


0.5.1 (2008-04-16)
------------------

- Cleanup imports and adjust dependencies


0.5.0 (2008-04-16)
------------------

- Initial Release

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/zopefoundation/z3c.authenticator",
    "name": "z3c.authenticator",
    "maintainer": "",
    "docs_url": null,
    "requires_python": ">=3.7",
    "maintainer_email": "",
    "keywords": "zope3 z3c authentication auth group",
    "author": "Roger Ineichen and the Zope Community",
    "author_email": "zope-dev@zope.dev",
    "download_url": "https://files.pythonhosted.org/packages/e6/8b/26f535ec0104baf32b25d8b0091cc6fbc6dff068cb82b2d5f07c573b1084/z3c.authenticator-2.0.tar.gz",
    "platform": null,
    "description": "This package provides an IAuthentication implementation for Zope3. Note that\nthis implementation is independent of zope.app.authentication and it doesn't\ndepend on that package. This means it doesn't even use the credential or\nauthentication plugins offered from zope.app.authentication package.\n\n\n.. contents::\n\n=======================\nIAuthentication Utility\n=======================\n\nThe Authenticator package provides a framework for authenticating principals\nand associating information with them. It uses plugins and subscribers to get\nits work done.\n\nFor a simple authentication utility to be used, it should be registered as a\nutility providing the `zope.authentication.interfaces.IAuthentication` interface.\n\nOur target is to support a handy IAuthentication utility which offers a simple\nAPI for custom IUser implementations and does not depend on the default\nzope.app.authentication implementation.\n\n\nSecurity\n--------\n\nThe Authenticator supports unique id tokens for principals. This means\nprincipal that get deleted and again added with the same id, login etc. do\nnot have the same id again. We support this by generate a user id token\ngenerated by the host id, timestamp, a random string and the login attribute.\n\n\nWhat's different from PluggableAuthentication\n---------------------------------------------\n\nWe use a different pattern for IAuthenticatorPlugins in this implementation\nthan used in PluggableAuthentication from zope.app.authentication,\nbecause the pluggable authentication is not very handy when it comes to\nimplementing custom principal information. The IPrincipalInfo hook supporting\nnot propagate the password of a IInternalPrincipal is droped in this\nimplementation.\n\nIn our implementation we offer a IFoundPrincipal and IAuthenticatedPrincipal\nwhich are implemented as adapters for a IUser. These adapters do not offer\ntheir context which is the real IUser.\n\nThe Authenticator doesn't use a prefix. The usage of a prefix is only\nimplemented in the IGroupContainer.\n\nWe do not use a prefix in the IUserContainer because of the used unique user\nid tokens. This will make sure that the same principal id doesn't get used at\na later time (common criteria). There is a ``add`` method which creates\nthis id for you based on the login. The __setitem__ should not get used\ndirectly for adding IUser instances anymore. We heavily restricted the\nusage of this method. See the inline doc tests in __setitem__ for more info.\n\n\nAuthentication\n==============\n\nThe primary job of Authenticator is to authenticate principals. It uses\ntwo types of plug-ins in its work:\n\n  - Credentials Plugins\n\n  - Authenticator Plugins\n\nCredentials plugins are responsible for extracting user credentials from a\nrequest. A credentials plugin may in some cases issue a 'challenge' to obtain\ncredentials. For example, a 'session' credentials plugin reads credentials\nfrom a session (the \"extraction\"). If it cannot find credentials, it will\nredirect the user to a login form in order to provide them (the \"challenge\").\n\nAuthenticator plugins are responsible for authenticating the credentials\nextracted by a credentials plugin. They are also typically able to create\nprincipal objects for credentials they successfully authenticate.\n\nGiven a request object, the Authenticator returns a principal object, if it\ncan. The Authenticator utility does this by first iterating through its\ncredentials plugins to obtain a set of credentials. If it gets credentials, it\niterates through its authenticator plugins to authenticate them.\n\nIf an authenticator succeeds in authenticating a set of credentials, the\nAuthenticator uses the authenticator to create a principal\ncorresponding to the credentials. The authenticator notifies subscribers if\nan authenticated principal is created. Subscribers are responsible for adding\ndata, especially groups, to the principal. Typically, if a subscriber adds\ndata, it should also add corresponding interface declarations.\n\n\nFAQ\n---\n\nHere some useful hints:\n\nHow should I set permission for principals?\n\n  You can apply roles to groups\n  and apply permissions to roles. Or you can directly apply local permisssions\n  to groups or to principals. After setting up these mappings you can grant roles to\n  groups. I always recommend a principal - group and permission - role mapping,\n  this gives you the most possible abstraction which is useful if it comes\n  to managing permissions and principals without directly invoking principals and\n  permissions themselves. But of course you can grant permissions to groups or the\n  worst thing, directly to principals. Granting permissions to principals is only\n  useful if it comes to selective local permission settings for selected\n  principals, e.g. an ownership-like permission setup.\n\nHow can I set permissions for all principals?\n\n  You can register one\n  group as IEveryone utility. This IGroup utility get applied to all principals.\n\nCan I apply local groups to unauthenticated principals?\n\n  Yes this will work.\n  Since the last refactoring I refactored the IGroup implementation which makes\n  it compatible with the principalregistry API. This means you can now register\n  one local group as an unnamed IUnauthenticatedGroup. You can also register one\n  local group as an unnamed IAuthenticatedGroup utility which will get applied\n  to every authenticated principal or an unnamed utility for\n  IUnauthenticatedGroup.\n\nCan I apply a local group to every principal?\n\n  Yes, this is possible if you\n  register a local unnamed utility providing IEveryoneGroup.\n\n\nPrincipal\n---------\n\nFirst we create a principal:\n\n  >>> from z3c.authenticator import interfaces\n  >>> from z3c.authenticator.user import User\n  >>> login = 'bob'\n  >>> password = 'secret'\n  >>> title = 'Bob'\n  >>> p = User(login, password, title)\n\nSuch a principal provides the following attributes be default\n\n  >>> p.login\n  'bob'\n\n  >>> p.password.decode('utf-8')\n  'secret'\n\n  >>> p.title\n  'Bob'\n\nand IUser:\n\n  >>> interfaces.IUser.providedBy(p)\n  True\n\n\nAuthenticator Plugin\n--------------------\n\nFirst set up a UserContainer which will store the principals:\n\n  >>> from z3c.authenticator.user import UserContainer\n  >>> authPlugin = UserContainer()\n\nNow we have a UserContainer that provides an IUserContainer:\n\n  >>> interfaces.IUserContainer.providedBy(authPlugin)\n  True\n\nNow we will add the created principal to the principal container using the\ncontainer's ``add`` method:\n\n  >>> uid, user = authPlugin.add(p)\n\nThe method returns the user id and the user object. The id gets generated\nfrom the host IP address, the time, a random string and the user login attr.\nThis token should be unique and guaranteed that it will never get generated twice.\nThis allows us to add, delete and add the same user again without having such a\nuser inheriting existing permissions. We can test this token by comparing it\nonly with the __name__ of the object in this test since the token will be\ndifferent every test run.\n\n  >>> user.__name__ == uid\n  True\n\nThe returned user is still our previous added IUser\n\n  >>> user is p\n  True\n\n  >>> len(user.__name__)\n  32\n\n  >>> user.login\n  'bob'\n\n  >>> user.password.decode('utf-8')\n  'secret'\n\n  >>> user.title\n  'Bob'\n\nLet's register the UserContainer as a named IAuthenticatorPlugin utility:\n\n  >>> import zope.component\n  >>> zope.component.provideUtility(authPlugin,\n  ...     provides=interfaces.IAuthenticatorPlugin,\n  ...     name='My Authenticator Plugin')\n\n\nCredentials Plugin\n------------------\n\nAfter seting up the user and user container, we'll create a simple credentials\nplugin:\n\n  >>> import zope.interface\n  >>> import zope.component\n\n  >>> @zope.interface.implementer(interfaces.ICredentialsPlugin)\n  ... class MyCredentialsPlugin(object):\n  ...\n  ...\n  ...     def extractCredentials(self, request):\n  ...         return {'login': request.get('login', ''),\n  ...                 'password': request.get('password', '')}\n  ...\n  ...     def challenge(self, request):\n  ...         pass # challenge is a no-op for this plugin\n  ...\n  ...     def logout(self, request):\n  ...         pass # logout is a no-op for this plugin\n\nAs a plugin, MyCredentialsPlugin needs to be registered as a named utility or\nit could be stored in the Authenticator attribute credentialsPlugins.\nUse the first and register the plugina utility:\n\n  >>> myCredentialsPlugin = MyCredentialsPlugin()\n  >>> zope.component.provideUtility(myCredentialsPlugin,\n  ...     name='My Credentials Plugin')\n\n\nAuthenticatedPrincipal and FoundPrincipal\n-----------------------------------------\n\nWhile authenticator plugins provide users, they are not responsible for\ncreating principals. This function is performed by the Authenticator:\n\n  >>> from z3c.authenticator.principal import AuthenticatedPrincipal\n  >>> from z3c.authenticator.principal import FoundPrincipal\n  >>> zope.component.provideAdapter(AuthenticatedPrincipal,\n  ...     provides=interfaces.IAuthenticatedPrincipal)\n\n  >>> zope.component.provideAdapter(FoundPrincipal,\n  ...     provides=interfaces.IFoundPrincipal)\n\n\nConfiguring the Authenticator\n-----------------------------\n\nFinally, we'll create the Authenticator itself:\n\n  >>> from z3c.authenticator import authentication\n  >>> auth = authentication.Authenticator()\n\nand configure it with the two plugins:\n\n  >>> auth.credentialsPlugins = ('My Credentials Plugin', )\n  >>> auth.authenticatorPlugins = ('My Authenticator Plugin', )\n\n\nAuthenticate\n------------\n\nWe can now use the Authenticator to authenticate a sample request:\n\n  >>> from zope.publisher.browser import TestRequest\n  >>> print(auth.authenticate(TestRequest()))\n  None\n\nIn this case, we cannot authenticate an empty request. In the same way, we\nwill not be able to authenticate a request with the wrong credentials:\n\n  >>> request = TestRequest(form={'login': 'let me in!', 'password': 'secret'})\n  >>> print(auth.authenticate(request))\n  None\n\nHowever, if we provide the proper credentials:\n\n  >>> request = TestRequest(form={'login': 'bob', 'password': 'secret'})\n  >>> bob = auth.authenticate(request)\n  >>> bob\n  <AuthenticatedPrincipal...>\n\n  >>> interfaces.IAuthenticatedPrincipal.providedBy(bob)\n  True\n\nwe get an authenticated principal.\n\n\nChanging login names\n--------------------\n\nChanging the login (i.e. username) of a principal is always a critical task because such a\nlogin together with a password is the key to our implemenation. Let's try to\nchange the login and check if everything is correct. We can do this by getting the\nprincipal from the UserContainer and changing the login on the IUser\nimplementation:\n\n  >>> internal = authPlugin[bob.id]\n  >>> internal.login = 'bob2'\n\nNow we should be able to login with the new login:\n\n  >>> request = TestRequest(form={'login': 'bob2', 'password': 'secret'})\n  >>> bob2 = auth.authenticate(request)\n  >>> bob2\n  <AuthenticatedPrincipal ...>\n\n  >>> bob2.title\n  'Bob'\n\nBut not with the old one:\n\n  >>> request = TestRequest(form={'login': 'bob', 'password': 'secret'})\n  >>> auth.authenticate(request) == None\n  True\n\nThe user bob has still the same id as bob2 since the user id token doesn't\nget changed by changing the login:\n\n  >>> bob.id == bob2.id\n  True\n\n\nEvents\n------\n\nAuthenticating a principal will create events.\n\n  >>> from zope.component.eventtesting import getEvents\n  >>> from zope.component.eventtesting import clearEvents\n\nWe can verify that the appropriate event was published:\n\n  >>> clearEvents()\n  >>> request = TestRequest(form={'login': 'bob2', 'password': 'secret'})\n  >>> bobAgain = auth.authenticate(request)\n\nAnd the principal attribute in the event provides the authenticated principal:\n\n  >>> [event] = getEvents(interfaces.IAuthenticatedPrincipalCreated)\n  >>> event.principal is bobAgain\n  True\n\n  >>> event.principal\n  <AuthenticatedPrincipal ...>\n\n  >>> event.request is request\n  True\n\nThe principal has the id, title, and description.\n\n  >>> event.principal.title\n  'Bob'\n\n  >>> event.principal.id == uid\n  True\n\n  >>> event.principal.description\n  ''\n\nWe provide subscribers to these events that can be used for doing custom\nprocessing. Note, the principal attibute provides an IAuthenticatedPrincipal:\n\n  >>> def addInfo(event):\n  ...     id = event.principal.id\n  ...     event.principal.description = 'Description for: %s' % id\n\n  >>> zope.component.provideHandler(addInfo,\n  ...     [interfaces.IAuthenticatedPrincipalCreated])\n\nNow, if we authenticate a principal, its description is set:\n\n  >>> principal = auth.authenticate(request)\n  >>> principal.description\n  'Description for: ...'\n\n\nCustomization\n-------------\n\nLet's show you how the existing pattern can get used in a real use case. In\nthe next sample we'd like to provide an additional email attribute for principals.\nFirst we have to define the interfaces declaring the email attribute:\n\n  >>> class IMyEmail(zope.interface.Interface):\n  ...     email = zope.schema.TextLine(\n  ...         title='EMail',\n  ...         description='The EMail')\n\n  >>> class IMyUser(IMyEmail, interfaces.IUser):\n  ...     \"\"\"Custom IUser interface.\"\"\"\n\n  >>> class IMyFoundPrincipal(IMyEmail, interfaces.IFoundPrincipal):\n  ...     \"\"\"Custom IIMyFoundrincipal interface.\"\"\"\n\n  >>> class IMyAuthenticatedPrincipal(IMyEmail,\n  ...     interfaces.IAuthenticatedPrincipal):\n  ...     \"\"\"Custom IAuthenticatedPrincipal interface.\"\"\"\n\nAfter the schema, we define a custom principal implementation implementing\nthis interface:\n\n  >>> @zope.interface.implementer(IMyUser)\n  ... class MyUser(User):\n  ...     def __init__(self, login, password, title, description, email):\n  ...         super(MyUser, self).__init__(login, password, title,\n  ...                                           description)\n  ...         self.email = email\n\nNow we have to define the AuthenticatedPrincipal for MyUser:\n\n  >>> @zope.interface.implementer(IMyAuthenticatedPrincipal)\n  ... class MyAuthenticatedPrincipal(AuthenticatedPrincipal):\n  ...     def __init__(self, principal):\n  ...         super(MyAuthenticatedPrincipal, self).__init__(principal)\n  ...         self.email = principal.email\n\nAnd we have to define the FoundPrincipal for MyUser:\n\n  >>> @zope.interface.implementer(IMyFoundPrincipal)\n  ... class MyFoundPrincipal(FoundPrincipal):\n  ...     def __init__(self, principal):\n  ...         super(MyFoundPrincipal, self).__init__(principal)\n  ...         self.email = principal.email\n\nNote that you can provide different attributes for the found and authenticated\nprincipals if needed. That's up to you what you like to do with these attributes\nlater.\n\nNow we need to register our custom authenticated and found principal\nadapters:\n\n  >>> zope.component.provideAdapter(MyAuthenticatedPrincipal,\n  ...     provides=interfaces.IAuthenticatedPrincipal)\n\n  >>> zope.component.provideAdapter(MyFoundPrincipal,\n  ...     provides=interfaces.IFoundPrincipal)\n\nNow we can use them without any other event subscribers or other registration\nin our principal container. Let's add a principal to this container:\n\n  >>> p = MyUser('max', 'password', 'Max', '', 'max@foobar.com')\n  >>> token, max = authPlugin.add(p)\n  >>> len(token)\n  32\n\n  >>> max.__name__ == token\n  True\n\n  >>> max.password.decode('utf-8')\n  'password'\n\n  >>> max.title\n  'Max'\n\n  >>> max.email\n  'max@foobar.com'\n\nLet's try to authenticate...\n\n  >>> request = TestRequest(form={'login': 'max', 'password': 'password'})\n  >>> authenticated = auth.authenticate(request)\n\nand check your authenticated principal:\n\n  >>> interfaces.IAuthenticatedPrincipal.providedBy(authenticated)\n  True\n\n  >>> authenticated\n  <MyAuthenticatedPrincipal ...>\n\n  >>> authenticated.id == token\n  True\n\n  >>> authenticated.email\n  'max@foobar.com'\n\nCheck getUserByLogin:\n\n  >>> max = authPlugin.getUserByLogin('max')\n  >>> max.__class__.__name__\n  'MyUser'\n\n  >>> authPlugin.getUserByLogin('max').login\n  'max'\n\n  >>> authPlugin.getUserByLogin('max').__name__ == token\n  True\n\n\nA handy feature for migration is that you can set your own ``token``.\nUsually in z.a.authentication the ``token`` == login and we want to keep it\nthat way, unless you want to iterate through all permissions and whatever.\nNote, the __name__ and the id in the container must be the *SAME* object.\n\n  >>> login = 'migrateduser'\n  >>> p = User(login, 'password', 'John')\n\nPreset the ``token``\n\n  >>> p.__name__ = login\n\nWatch out, we use __setitem__ instead of add(), because add() would kill off\nthe preset ``token`` in __name__.\n\n  >>> authPlugin[login] = p\n\nHere we are, the user is set with the non-generated token.\n\n  >>> 'migrateduser' in authPlugin.keys()\n  True\n\n  >>> authPlugin['migrateduser']\n  <z3c.authenticator.user.User object at ...>\n\n  >>> authPlugin.getUserByLogin('migrateduser')\n  <z3c.authenticator.user.User object at ...>\n\n\nEdge cases\n----------\n\nWe can have Users with text logins, as we allow this with TextLine in IUser.\n\n  >>> p = User('bob'+chr(233), 'password', 'title')\n\nAdding it should not fail:\n\n  >>> uid, user = authPlugin.add(p)\n\n\n\n=====\nGroup\n=====\n\nGroup container provide support for groups information stored in the ZODB. \nThey are persistent, and must be contained within the IAuthentication that \nuse them.\n\n\nGroup\n-----\n\nLike other users, groups are created when they are needed.\n\n  >>> from z3c.authenticator import interfaces\n  >>> from z3c.authenticator.group import Group\n  >>> group1 = Group('groups')\n  >>> group1\n  <Group None>\n\n  >>> interfaces.IGroup.providedBy(group1)\n  True\n\n  >>> group1.title\n  'groups'\n\n  >>> group1.description\n  ''\n\n  >>> group1.principals\n  ()\n\n\nGroupContainer\n--------------\n\nGroup containers contain IGroup objects. A IAuthentication will adapt\nIFoundGroup to this IGroup objects.\n\n  >>> from z3c.authenticator.group import GroupContainer\n  >>> groups = GroupContainer('groups.')\n\n  >>> interfaces.IGroupContainer.providedBy(groups)\n  True\n\nWe can add your previous created group to the group container using the \naddGroup method which returns the group id and group:\n\n  >>> gid, g1 = groups.addGroup('g1', group1)\n  >>> gid\n  'groups.g1'\n\n  >>> interfaces.IGroup.providedBy(g1)\n  True\n\n  >>> g1.__name__\n  'groups.g1'\n\nNote that when group is added, a GroupAdded event is generated:\n\n  >>> from zope.component.eventtesting import getEvents\n  >>> getEvents(interfaces.IGroupAdded)\n  [<GroupAdded 'groups.g1'>]\n\nGroups are defined with respect to an authentication service. Groups must be \naccessible via an authentication service and can contain principals accessible\nvia an authentication service. To illustrate the group interaction with the \nauthentication service, we will setup a Authenticator utility:\n\n  >>> from z3c.authenticator.authentication import Authenticator\n  >>> authenticator = Authenticator()\n\nGive them a location and register them as a IAuthentication utility :\n\n  >>> import zope.component\n  >>> from zope.authentication.interfaces import IAuthentication\n  >>> rootFolder['authenticator'] = authenticator\n  >>> zope.component.provideUtility(authenticator, IAuthentication)\n\nWe will create and register a new principals utility:\n\n  >>> zope.component.provideUtility(authenticator, IAuthentication)\n\nWe also need to register the group athentication plugin:\n\n  >>> zope.component.provideUtility(groups, \n  ...     provides=interfaces.IAuthenticatorPlugin, \n  ...     name='My Group Plugin')\n\nAfter setup the group and group container, we will create a simple credentials \nplugin and add them to the authentication utility:\n\n  >>> import zope.interface\n  >>> from z3c.authenticator import interfaces\n\n  >>> @zope.interface.implementer(interfaces.ICredentialsPlugin)\n  ... class MyCredentialsPlugin(object):\n  ...\n  ...     def extractCredentials(self, request):\n  ...         return {'login':request.get('login', ''), \n  ...                 'password':request.get('password', '')}\n  ...\n  ...     def challenge(self, request):\n  ...         pass # challenge is a no-op for this plugin\n  ...\n  ...     def logout(self, request):\n  ...         pass # logout is a no-op for this plugin\n\nand configure and add the credential plugin to the Authenticator:\n\n  >>> myCredentialsPlugin = MyCredentialsPlugin()\n  >>> authenticator['credentials'] = myCredentialsPlugin\n  >>> authenticator.credentialsPlugins = ('credentials', )\n\nWe also need a principal and a IAuthenticationPlugin:\n\n  >>> from z3c.authenticator.user import User\n  >>> p1 = User('p1', 'password', 'Principal 1')\n  >>> p2 = User('p2', 'password', 'Principal 2')\n  >>> p3 = User('p3', 'password', 'Principal 3')\n  >>> p4 = User('p4', 'password', 'Principal 4')\n\n  >>> from z3c.authenticator.user import UserContainer\n  >>> users = UserContainer()\n  >>> token1, p1 = users.add(p1)\n  >>> token2, p2 = users.add(p2)\n  >>> token3, p3 = users.add(p3)\n  >>> token4, p4 = users.add(p4)\n\nAdd the GroupContainer and UserContainer to the Authenticator and \nset the correct plugin names\n\n  >>> authenticator['users'] = users\n  >>> authenticator['groups'] = groups\n  >>> authenticator.authenticatorPlugins = ('users', 'groups')\n\n\nAdding users to groups\n----------------------\n\nNow we can set the users on the group but first we need to register the\nIFoundPrincipal adapter for groups. The FoundGroup adapter provides this\ninterface:\n\n  >>> from z3c.authenticator.principal import FoundGroup\n  >>> zope.component.provideAdapter(FoundGroup, \n  ...     provides=interfaces.IFoundPrincipal)\n\nAnd we also need to provide the IFoundPrincipal and IAuthenticatedPrincipal\nadapter for IPrincipal objects:\n\n  >>> from z3c.authenticator.principal import AuthenticatedPrincipal\n  >>> from z3c.authenticator.principal import FoundPrincipal\n  >>> zope.component.provideAdapter(AuthenticatedPrincipal, \n  ...     provides=interfaces.IAuthenticatedPrincipal)\n\n  >>> zope.component.provideAdapter(FoundPrincipal, \n  ...     provides=interfaces.IFoundPrincipal)\n\nAnd we need the ``setGroupsForPrincipal`` subscriber:\n\n  >>> from z3c.authenticator.group import setGroupsForPrincipal\n  >>> zope.component.provideHandler(setGroupsForPrincipal, \n  ...     [interfaces.IPrincipalCreated])\n\n  >>> g1.principals = [p1.__name__, p2.__name__]\n  >>> g1.principals\n  (..., ...)\n\n  >>> g1.principals[0] == p1.__name__\n  True\n\n  >>> g1.principals[1] == p2.__name__\n  True\n\nAdding users fires an event.\n\n  >>> getEvents(interfaces.IPrincipalsAddedToGroup)[-1]\n  <PrincipalsAddedToGroup [..., ...] 'groups.g1'>\n\nWe can now look up groups for the users:\n\n  >>> groups.getGroupsForPrincipal(p1.__name__)\n  ('groups.g1',)\n\nNote that the group id is a concatenation of the group-folder prefix\nand the name of the group object within the folder.\n\nIf we delete a group:\n\n  >>> del groups['groups.g1']\n\nthen the groups folder loses the group information for that group's users:\n\n  >>> groups.getGroupsForPrincipal('p1')\n  ()\n\nbut the principal information on the group is unchanged:\n\n  >>> g1.principals\n  (..., ...)\n\n  >>> g1.principals[0] == p1.__name__\n  True\n\n  >>> g1.principals[1] == p2.__name__\n  True\n\nIt also fires an event showing that the users are removed from the groups.\n\n  >>> getEvents(interfaces.IPrincipalsRemovedFromGroup)[-1]\n  <PrincipalsRemovedFromGroup [..., ...] 'groups.g1'>\n\nAdding the group again within a different name will make the groups \navailable for the principal. Let's use a different group name:\n\n  >>> groups['groups.G1'] = g1\n\n  >>> groups.getGroupsForPrincipal(p1.__name__)\n  ('groups.G1',)\n\nHere we see that the new name is reflected in the group information.\n\nAn event is fired, as usual.\n\n  >>> getEvents(interfaces.IPrincipalsAddedToGroup)[-1]\n  <PrincipalsAddedToGroup [..., ...] 'groups.G1'>\n\nIn terms of member events (members added and removed from groups), we have\nnow seen that events are fired when a group object is added and when it is \nremoved from a group container; and we have seen that events are fired\nwhen a principal is added to an already-registered groups.  Events are also\nfired when a principal is removed from an already-registered groups.  Let's\nquickly see some more examples.\n\n  >>> g1.principals = (p1.__name__, p3.__name__, p4.__name__)\n  >>> g1.principals\n  (..., ..., ...)\n\n  >>> g1.principals[0] == p1.__name__\n  True\n\n  >>> g1.principals[1] == p3.__name__\n  True\n\n  >>> g1.principals[2] == p4.__name__\n  True\n\n  >>> getEvents(interfaces.IPrincipalsAddedToGroup)[-1]\n  <PrincipalsAddedToGroup [..., ...] 'groups.G1'>\n\n  >>> getEvents(interfaces.IPrincipalsRemovedFromGroup)[-1]\n  <PrincipalsRemovedFromGroup [...] 'groups.G1'>\n\n  >>> g1.principals = (p1.__name__, p2.__name__)\n  >>> g1.principals\n  (..., ...)\n\n  >>> g1.principals[0] == p1.__name__\n  True\n\n  >>> g1.principals[1] == p2.__name__\n  True\n\n  >>> getEvents(interfaces.IPrincipalsAddedToGroup)[-1]\n  <PrincipalsAddedToGroup [...] 'groups.G1'>\n\n  >>> getEvents(interfaces.IPrincipalsRemovedFromGroup)[-1]\n  <PrincipalsRemovedFromGroup [..., ...] 'groups.G1'>\n\n  >>> groups.getGroupsForPrincipal(p2.__name__)\n  ('groups.G1',)\n\n\nGroups in groups\n----------------\n\nGroups can contain groups:\n\n  >>> g2 = Group('Group Two')\n  >>> groups['groups.G2'] = g2\n  >>> g2.principals\n  ()\n  \n  >>> g2.principals = ['groups.G1']\n\n  >>> g2.principals\n  ('groups.G1',)\n\n  >>> groups.getGroupsForPrincipal('groups.G2')\n  ()\n\n  >>> g1.principals\n  (..., ...)\n\n  >>> g1.principals[0] == p1.__name__\n  True\n\n  >>> g1.principals[1] == p2.__name__\n  True\n\n  >>> groups.getGroupsForPrincipal('groups.G1')\n  ('groups.G2',)\n\n  >>> old = getEvents(interfaces.IPrincipalsAddedToGroup)[-1]\n  >>> old\n  <PrincipalsAddedToGroup ['groups.G1'] 'groups.G2'>\n\nGroups cannot contain cycles:\n\n  >>> g1.principals\n  (..., ...)\n\n  >>> g1.principals[0] == p1.__name__\n  True\n\n  >>> g1.principals[1] == p2.__name__\n  True\n\n  >>> g2.principals\n  ('groups.G1',)\n\n  >>> g1.principals = (p1.__name__, p2.__name__, 'groups.G2')\n  Traceback (most recent call last):\n  ...\n  z3c.authenticator.group.GroupCycle: (...)\n\n  >>> g1.principals\n  (..., ...)\n\n\n  >>> g1.principals[0] == p1.__name__\n  True\n\n  >>> g1.principals[1] == p2.__name__\n  True\n\nTrying to do so does not fire an event.\n\n  >>> getEvents(interfaces.IPrincipalsAddedToGroup)[-1] is old\n  True\n\nThey need not be hierarchical:\n\n  >>> ga = Group(\"Group A\")\n  >>> groups['groups.GA'] = ga\n\n  >>> gb = Group(\"Group B\")\n  >>> groups['groups.GB'] = gb\n  >>> gb.principals = ['groups.GA']\n\n  >>> gc = Group(\"Group C\")\n  >>> groups['groups.GC'] = gc\n  >>> gc.principals = ['groups.GA']\n\n  >>> gd = Group(\"Group D\")\n  >>> groups['groups.GD'] = gd\n  >>> gd.principals = ['groups.GA', 'groups.GB']\n\n  >>> ga.principals = [p1.__name__]\n\nGroup containers provide a very simple search interface.  They perform\nsimple string searches on group titles and descriptions.\n\n  >>> list(groups.search({'search': 'gro'}))\n  ['groups.G1', 'groups.G2',\n   'groups.GA', 'groups.GB', 'groups.GC', 'groups.GD']\n\n  >>> list(groups.search({'search': 'two'}))\n  ['groups.G2']\n\nThey also support batching:\n\n  >>> list(groups.search({'search': 'gro'}, 2, 3))\n  ['groups.GA', 'groups.GB', 'groups.GC']\n\n\nIf you don't supply a search key, no results will be returned:\n\n  >>> list(groups.search({}))\n  []\n\n\nIdentifying groups\n------------------\n\nThe function, `setGroupsForPrincipal`, is a subscriber to\nprincipal-creation events.  It adds any group-folder-defined groups to\nusers in those groups:\n\n  >>> auth1 = authenticator.getPrincipal(p1.__name__)\n\n  >>> auth1.groups\n  ['groups.G1', 'groups.GA']\n\nOf course, this applies to groups too:\n\n  >>> g1 = authenticator.getPrincipal('groups.G1')\n  >>> g1.id\n  'groups.G1'\n\n  >>> g1.groups\n  ['groups.G2']\n\nA FoundGroup provides IFoundGroup which is inherited from \nIFoundPrincipal and IGroup:\n\n  >>> interfaces.IFoundGroup.providedBy(g1)\n  True\n\n  >>> interfaces.IFoundPrincipal.providedBy(g1)\n  True\n\n  >>> import zope.security.interfaces\n  >>> zope.security.interfaces.IGroup.providedBy(g1)\n  True\n\n\nspecialGroups\n-------------\n\nTwo special groups, IAuthenticatedGroup, and IEveryoneGroup may apply to users\ncreated by the IAuthentication utility.  There is a subscriber called \n``specialGroups``. This subscriber can set this special groups on any \nprincipal if IAuthenticatedGroup, or IEveryoneGroup utilities are \nprovided. The subscriber knows also how to apply local groups to principals.\nNote, principals means IAuthenticatedPrincipal, IFoundPrincipal or IFoundGroup.\n\nIf we notify the subscriber with the principal, nothing will happen\nbecause the groups haven't been defined:\n\n  >>> from z3c.authenticator.principal import FoundPrincipal\n  >>> from z3c.authenticator.event import FoundPrincipalCreated\n  >>> from z3c.authenticator.group import specialGroups\n  >>> x = User('x', 'password', 'X')\n  >>> found = FoundPrincipal(x)\n  >>> event = FoundPrincipalCreated(authenticator, found)\n  >>> specialGroups(event)\n  >>> found.groups\n  []\n\nNow, if we define the Everybody group:\n\n  >>> import zope.authentication.interfaces\n  >>> @zope.interface.implementer(\n  ...        zope.authentication.interfaces.IEveryoneGroup)\n  ... class EverybodyGroup(Group):\n  ...     pass\n\n  >>> all = EverybodyGroup('groups.all')\n  >>> groups['groups.all'] = all\n  >>> zope.component.provideUtility(all,\n  ...     zope.authentication.interfaces.IEveryoneGroup)\n\nThen the group will be added to the principal:\n\n  >>> specialGroups(event)\n  >>> found.groups\n  ['groups.all']\n\nSimilarly for the authenticated group:\n\n  >>> @zope.interface.implementer(\n  ...         zope.authentication.interfaces.IAuthenticatedGroup)\n  ... class AuthenticatedGroup(Group):\n  ...     pass\n\n  >>> authenticated = AuthenticatedGroup('groups.authenticated')\n  >>> groups['groups.authenticated'] = authenticated\n  >>> zope.component.provideUtility(authenticated,\n  ...     zope.authentication.interfaces.IAuthenticatedGroup)\n\nThen the group will be added to the principal:\n\n  >>> found.groups = []\n  >>> specialGroups(event)\n  >>> found.groups.sort()\n  >>> found.groups\n  ['groups.all', 'groups.authenticated']\n\nIt is important that we do not apply a group twice since the\nUnauthenticatedPrincipal is a single instance in the securitypolicy. This issue\nis fixed in version 0.6.1 and 0.7.1\n\n  >>> specialGroups(event)\n  >>> found.groups\n  ['groups.all', 'groups.authenticated']\n\n\nallGroups\n---------\n\nThe `allGroups` attribute is a readonly iterable of the full closure of the\ngroups in the `groups` attribute. Let's define a new principal first:\n\n  >>> p = User('p', 'password', 'Principal')\n  >>> token, p = users.add(p)\n\nAnd the groups:\n\n  >>> ga = Group(\"Administrators\")\n  >>> gr = Group(\"Reviewers\")\n  >>> gid, ga = groups.addGroup('Administrators', ga)\n  >>> gid, gr = groups.addGroup('Reviewers', gr)\n\nIf the principal is a direct member of the 'Administrators' group, \n\n  >>> ga.principals = [p.__name__]\n\nthen getGroupsForPrincipal would be ['Administrators']\n\n  >>> groups.getGroupsForPrincipal(p.__name__)\n  ('groups.Administrators',)\n  \nand if the 'Administrators' group is a member of the 'Reviewers' group, \n\n  >>> gr.principals = [ga.id]\n\nthen groups would be ['Administrators'] too.\n\n  >>> groups.getGroupsForPrincipal(p.__name__)\n  ('groups.Administrators',)\n\nnow let's use the setGroupsForPrincipal subscriber which knows how to apply\nthe groups to the found principal:\n\n  >>> pFound = FoundPrincipal(p)\n  >>> event = FoundPrincipalCreated(authenticator, pFound)\n  >>> setGroupsForPrincipal(event)\n\nAs you can see and pFound.groups is ['Administrators'].\n\n  >>> sorted(pFound.groups)\n  ['groups.Administrators']\n\nAnd pFound.allGroups is ['Administrators', 'Reviewers'].\n\n  >>> sorted(pFound.allGroups)\n  ['groups.Administrators', 'groups.Reviewers']\n\n\n==========\nVocabulary\n==========\n\nThe vocabulary module provides vocabularies for the authenticator plugins and\nthe credentials plugins.\n\nThe options should include the unique names of all of the plugins that provide\nthe appropriate interface (IAuthentiatorPlugin or ICredentialsPlugin, \nrespectively) for the current context which is expected to be a IAuthenticator \nutility, hereafter referred to as a Authenticator.\n\nThese names may be for objects contained within the Authenticator \n(\"contained plugins\"), or may be utilities registered for the specified \ninterface, found in the context of the Authenticator \n(\"utility plugins\"). Contained plugins mask utility plugins of the same name. \nThey also may be names currently selected in the Authenticator that do \nnot actually have a corresponding plugin at this time.\n\nHere is a short example of how the vocabulary should work.  Let's say we're\nworking with authentication plugins.  We'll create some faux\nauthentication plugins, and register some of them as utilities and put\nothers in a faux Authenticator.\n\n    >>> from z3c.authenticator import interfaces\n    >>> import zope.interface\n    >>> import zope.component\n    >>> @zope.interface.implementer(interfaces.IAuthenticatorPlugin)\n    ... class DemoPlugin(object):\n    ...     def __init__(self, name):\n    ...         self.name = name\n    ...\n    >>> utility_plugins = dict(\n    ...     (i, DemoPlugin('Plugin %d' % i)) for i in range(4))\n    >>> contained_plugins = dict(\n    ...     (i, DemoPlugin('Plugin %d' % i)) for i in range(1, 5))\n    >>> sorted(utility_plugins.keys())\n    [0, 1, 2, 3]\n    >>> for p in utility_plugins.values():\n    ...     zope.component.provideUtility(p, name=p.name)\n    ...\n    >>> sorted(contained_plugins.keys()) # 1 will mask utility plugin 1\n    [1, 2, 3, 4]\n    >>> @zope.interface.implementer(interfaces.IAuthenticator)\n    ... class DemoAuth(dict):\n    ...     def __init__(self, *args, **kwargs):\n    ...         super(DemoAuth, self).__init__(*args, **kwargs)\n    ...         self.authenticatorPlugins = ('Plugin 3', 'Plugin X')\n    ...         self.credentialsPlugins = ('Plugin 4', 'Plugin X')\n    ...\n    >>> auth = DemoAuth((p.name, p) for p in contained_plugins.values())\n    \n    >>> @zope.component.adapter(zope.interface.Interface)\n    ... @zope.interface.implementer(zope.component.IComponentLookup)\n    ... def getSiteManager(context):\n    ...     return zope.component.getGlobalSiteManager()\n    ...\n    >>> zope.component.provideAdapter(getSiteManager)\n\n\nauthenticatorPlugins\n--------------------\n\nWe are now ready to create a vocabulary that we can use.  The context is\nour faux authentication utility, `auth`.\n\n    >>> from z3c.authenticator import vocabulary\n    >>> vocab = vocabulary.authenticatorPlugins(auth)\n\nIterating over the vocabulary results in all of the terms, in a relatively\narbitrary order of their names.  (This vocabulary should typically use a\nwidget that sorts values on the basis of localized collation order of the\nterm titles.)\n\n    >>> [term.value for term in vocab] # doctest: +NORMALIZE_WHITESPACE\n    ['Plugin 0', 'Plugin 1', 'Plugin 2', 'Plugin 3', 'Plugin 4',\n     'Plugin X']\n\nSimilarly, we can use `in` to test for the presence of values in the\nvocabulary.\n\n    >>> ['Plugin %s' % i in vocab for i in range(-1, 6)]\n    [False, True, True, True, True, True, False]\n    >>> 'Plugin X' in vocab\n    True\n\nThe length reports the expected value.\n\n    >>> len(vocab)\n    6\n\nOne can get a term for a given value using `getTerm()`; its token, in\nturn, should also return the same effective term from `getTermByToken`.\n\n    >>> values = ['Plugin 0', 'Plugin 1', 'Plugin 2', 'Plugin 3', 'Plugin 4',\n    ...           'Plugin X']\n    >>> for val in values:\n    ...     term = vocab.getTerm(val)\n    ...     assert term.value == val\n    ...     term2 = vocab.getTermByToken(term.token)\n    ...     assert term2.token == term.token\n    ...     assert term2.value == val\n    ...\n\nThe terms have titles, which are message ids that show the plugin title or id\nand whether the plugin is a utility or just contained in the auth utility.\nWe'll give one of the plugins a dublin core title just to show the\nfunctionality.\n\n    >>> import zope.dublincore.interfaces\n    >>> class ISpecial(zope.interface.Interface):\n    ...     pass\n    ...\n    >>> zope.interface.directlyProvides(contained_plugins[1], ISpecial)\n    >>> @zope.interface.implementer(\n    ...         zope.dublincore.interfaces.IDCDescriptiveProperties)\n    ... class DemoDCAdapter(object):\n    ...     zope.component.adapts(ISpecial)\n    ...     def __init__(self, context):\n    ...         pass\n    ...     title = 'Special Title'\n    ...\n    >>> zope.component.provideAdapter(DemoDCAdapter)\n\nWe need to regenerate the vocabulary, since it calculates all of its data at\nonce.\n\n    >>> vocab = vocabulary.authenticatorPlugins(auth)\n\nNow we'll check the titles.  We'll have to translate them to see what we\nexpect.\n\n    >>> from zope import i18n\n    >>> import pprint\n    >>> pprint.pprint([i18n.translate(term.title) for term in vocab])\n    ['Plugin 0 (a utility)',\n     'Special Title (in contents)',\n     'Plugin 2 (in contents)',\n     'Plugin 3 (in contents)',\n     'Plugin 4 (in contents)',\n     'Plugin X (not found; deselecting will remove)']\n\n\ncredentialsPlugins\n------------------\n\nFor completeness, we'll do the same review of the credentialsPlugins.\n\n    >>> @zope.interface.implementer(interfaces.ICredentialsPlugin)\n    ... class DemoPlugin(object):\n    ...     def __init__(self, name):\n    ...         self.name = name\n    ...\n    >>> utility_plugins = dict(\n    ...     (i, DemoPlugin('Plugin %d' % i)) for i in range(4))\n    >>> contained_plugins = dict(\n    ...     (i, DemoPlugin('Plugin %d' % i)) for i in range(1, 5))\n    >>> for p in utility_plugins.values():\n    ...     zope.component.provideUtility(p, name=p.name)\n    ...\n    >>> auth = DemoAuth((p.name, p) for p in contained_plugins.values())\n    >>> vocab = vocabulary.credentialsPlugins(auth)\n\nIterating over the vocabulary results in all of the terms, in a relatively\narbitrary order of their names.  (This vocabulary should typically use a\nwidget that sorts values on the basis of localized collation order of the term\ntitles.) Similarly, we can use `in` to test for the presence of values in the\nvocabulary. The length reports the expected value.\n\n    >>> [term.value for term in vocab] # doctest: +NORMALIZE_WHITESPACE\n    ['Plugin 0', 'Plugin 1', 'Plugin 2', 'Plugin 3', 'Plugin 4',\n     'Plugin X']\n    >>> ['Plugin %s' % i in vocab for i in range(-1, 6)]\n    [False, True, True, True, True, True, False]\n    >>> 'Plugin X' in vocab\n    True\n    >>> len(vocab)\n    6\n\nOne can get a term for a given value using `getTerm()`; its token, in\nturn, should also return the same effective term from `getTermByToken`.\n\n    >>> values = ['Plugin 0', 'Plugin 1', 'Plugin 2', 'Plugin 3', 'Plugin 4',\n    ...           'Plugin X']\n    >>> for val in values:\n    ...     term = vocab.getTerm(val)\n    ...     assert term.value == val\n    ...     term2 = vocab.getTermByToken(term.token)\n    ...     assert term2.token == term.token\n    ...     assert term2.value == val\n    ...\n\nThe terms have titles, which are message ids that show the plugin title or id\nand whether the plugin is a utility or just contained in the auth utility.\nWe'll give one of the plugins a dublin core title just to show the\nfunctionality. We need to regenerate the vocabulary, since it calculates all\nof its data at once. Then we'll check the titles.  We'll have to translate\nthem to see what we expect.\n\n    >>> zope.interface.directlyProvides(contained_plugins[1], ISpecial)\n    >>> vocab = vocabulary.credentialsPlugins(auth)\n    >>> pprint.pprint([i18n.translate(term.title) for term in vocab])\n    ['Plugin 0 (a utility)',\n     'Special Title (in contents)',\n     'Plugin 2 (in contents)',\n     'Plugin 3 (in contents)',\n     'Plugin 4 (in contents)',\n     'Plugin X (not found; deselecting will remove)']\n\n\n=======\nCHANGES\n=======\n\n2.0 (2023-02-09)\n----------------\n\n- Drop support for Python 2.6, 2.7, 3.4, 3.5, 3.6.\n\n- Add Python 3.7, 3.8, 3.9, 3.10, 3.11 support.\n\n- Remove unused imports.\n\n\n1.0.1 (2018-05-16)\n------------------\n\n- bugfix: remove None from queryNextUtiliy because getNextUtility signature got\n  changed and None was used as name argument.\n\n- cleanup, removed unused imports\n\n- Get rid of deprecation warning. Switch hooks import from zope.site to\n  zope.component\n\n- skip test_exception_in_subscriber_leaves_item_in_place for IAuthenticator\n  container. This zope.container test method left over an event subscriber with\n  zope.container version 4.2.1 which raises an error in other methods. Needs\n  review and probably a tear down in zope.container test method.\n\n\n1.0.0 (2017-04-17)\n------------------\n\n- Ping Python support to 2.7, 3.5, 3.6 and PyPy.\n\n\n1.0.0a5 (2013-03-31)\n--------------------\n\n- More Py3 compatibility bugs in untested code.\n\n\n1.0.0a4 (2013-02-28)\n--------------------\n\n- Removed support for Zope generations. It was not used and application-wide\n  generations make more sense anyways. This removed the\n  ``zope.app.generations`` and ``zope.generations`` dependencies.\n\n- Made ``z3c.configurator`` support optional.\n\n- Cleaned up source code a little bit.\n\n\n1.0.0a3 (2013-02-28)\n--------------------\n\n- Changed manifest to actually include the page templates.\n\n\n1.0.0a2 (2013-02-28)\n--------------------\n\n- Added version Trove classifiers.\n\n\n1.0.0a1 (2013-02-28)\n--------------------\n\n- Dropped support for Python 2.4 and 2.5, added Python 3.3 support.\n\n- Removed dependencies on ``zope.app`` packages.\n\n\n0.8.1 (2011-01-21)\n------------------\n\n- Bugfix: Did not handle unicode IUser.login values.\n\n- Fixed DeprecationWarnings.\n\n\n0.8.0 (2010-01-25)\n------------------\n\n- Security Fix: move camefrom url to a session variable instead of exposing\n  the url in the login form. Because the camefrom url is built at server side\n  based on local information and will always only use internal traversal names.\n  Exposing this camefrom query in the login url gives others only a point to\n  attack because it could be simply set by a unfriendly domain with a custom\n  url. This is much better since such a unfriendly 3rd party domain url doesn't\n  get redirected by default based on the changes in zope.publisher's redirect\n  method. (zope.publisher 3.9.3 does only redirect to urls located in the same\n  domain by default)\n\n  Remove all camefrom widgets and queries in our custom forms if you use any.\n  You can just set and get the camefrom session variable in your custom forms\n  if you need to.\n\n0.7.2 (2010-01-26)\n------------------\n\n- Bugfix: Failed miserably on challenge on pages having non-ASCII names.\n\n0.7.1 (2009-08-19)\n------------------\n\n- Bugfix: the method specialGroups applied groups everytime the method get\n  called even if the group was already applied. This is a problem if the\n  global shared unauthenticated principal instance is used because it will\n  apply similar groups till the server get restarted and a new principal\n  instance is used.\n\n- Feature: added getUserByLogin to IUserContainer\n\n- Added a test for user migration (that they will keep their ID)\n\n\n0.7.0 (2009-05-11)\n------------------\n\n- Update dependencies:\n\n   * Use ``zope.container`` instead of ``zope.app.container``.\n   * Use ``zope.site`` instead of ``zope.app.component``.\n   * Use ``zope.authentication`` and ``zope.principalregistry`` instead\n     of ``zope.app.security``.\n   * Use ``zope.password`` instead of maintaining a copy of password\n     managers.\n\n- Drop dependency on z3c.i18n and recreate a message factory instead.\n\n0.6.2 (2010-01-26)\n------------------\n\n- Bugfix: Failed miserably on challenge on pages having non-ASCII names.\n  (Backport from 0.7.2)\n\n0.6.1 (2009-08-19)\n------------------\n\n- Backport bugfix 0.7.1 to 0.6.1. See branches/roger-0.6.0\n\n  the method specialGroups applied groups everytime the method get\n  called even if the group was already applied. This is a problem if the\n  global shared unauthenticated principal instance is used because it will\n  apply similar groups till the server get restarted and a new principal\n  instance is used.\n\n\n0.6.0 (2009-01-04)\n------------------\n\n- Feature: added support for local IUnauthenticatedPrincipal. This is usefull\n  if you need to apply local roles to IUnauthenticatedPrincipal. This was not\n  possible before and is not possible in zope.app.authentication\n\n- Feature: implemented initial grant view based on ISource widget. Note, this\n  source widget terms implementation which is very complex to understand will\n  get moved to z3c.from if we fixed the ITerm dependency. Which means ITerm\n  needs to get moved out of zope.app.form first.\n\n- Feature: added support for next utility lookup in authenticate call. By\n  default the principals from the global principalregistry get involved now.\n  You can disable this feature by setting includeNextUtilityForAuthenticate to\n  False.\n\n- Feature: added PrincipalRegistryAuthenticatorPlugin which allows to\n  authenticate principals defined in global principal registry.\n\n- Feature: implemented z3c.form prefix support in SessionCredentialsPlugin. Now\n  there is an option called prefixes which can be used for define a list of\n  used z3c.form prefixes. This makes it simpler for supporting different forms\n  and adjust the credential extraction.\n\n- Renamed IGroupPrincipal to IFoundGroup which makes it more understandable\n  why this adapter implementation is needed. The IFoundGroup adapter is now\n  also used for zope.security.interfaces.IGroup principals. This makes it\n  possible to use them in the new principalregistry credential. Provide\n  deprecation message for the old IGroupPrincipal implementation.\n\n- Removed dependency for zapi. But it's not really gone since other packages\n  use zapi too.\n\n- Removed unused InvalidPrincipalIds and InvalidGroupId exceptions\n\n- Removed unused IMemberAwareGroup support. This interface is not used in zope\n  at all.\n\n- Added documentation for Pypi home page.\n\n\n0.5.1 (2008-04-16)\n------------------\n\n- Cleanup imports and adjust dependencies\n\n\n0.5.0 (2008-04-16)\n------------------\n\n- Initial Release\n",
    "bugtrack_url": null,
    "license": "ZPL 2.1",
    "summary": "IAuthentication implementation for for Zope3",
    "version": "2.0",
    "split_keywords": [
        "zope3",
        "z3c",
        "authentication",
        "auth",
        "group"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "a90c1f620a07631a66b2b181d2fa1d863509c219c726458b384f83e58bfab898",
                "md5": "ef1a731190de8a75670facda989868cc",
                "sha256": "beeb3bf2768c0f9262962b000f2e285aa57037dedc53302357b68196299c6c54"
            },
            "downloads": -1,
            "filename": "z3c.authenticator-2.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "ef1a731190de8a75670facda989868cc",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.7",
            "size": 68691,
            "upload_time": "2023-02-09T10:23:34",
            "upload_time_iso_8601": "2023-02-09T10:23:34.685479Z",
            "url": "https://files.pythonhosted.org/packages/a9/0c/1f620a07631a66b2b181d2fa1d863509c219c726458b384f83e58bfab898/z3c.authenticator-2.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "e68b26f535ec0104baf32b25d8b0091cc6fbc6dff068cb82b2d5f07c573b1084",
                "md5": "1a3b6ba4b000e2340970082f1ab29c8c",
                "sha256": "aac7ddc834d03dbdc3aefad146735df2f3f6131dbf03030b3be80e6cf1f814f2"
            },
            "downloads": -1,
            "filename": "z3c.authenticator-2.0.tar.gz",
            "has_sig": false,
            "md5_digest": "1a3b6ba4b000e2340970082f1ab29c8c",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.7",
            "size": 66418,
            "upload_time": "2023-02-09T10:23:37",
            "upload_time_iso_8601": "2023-02-09T10:23:37.151892Z",
            "url": "https://files.pythonhosted.org/packages/e6/8b/26f535ec0104baf32b25d8b0091cc6fbc6dff068cb82b2d5f07c573b1084/z3c.authenticator-2.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2023-02-09 10:23:37",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "github_user": "zopefoundation",
    "github_project": "z3c.authenticator",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "tox": true,
    "lcname": "z3c.authenticator"
}
        
Elapsed time: 0.04974s