# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2007, Agendaless Consulting and Contributors.
# Copyright (c) 2008, Florent Aide .
# Copyright (c) 2008-2009, Gustavo Narea .
# All Rights Reserved.
#
# This software is subject to the provisions of the BSD-like license at
# http://www.repoze.org/LICENSE.txt. A copy of the license should accompany
# this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL
# EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO,
# THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND
# FITNESS FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""
Utilities to setup authorization by configuring repoze.who's middleware to
use repoze.what.
"""
import os
from zope.interface import implements
from repoze.who.plugins.testutil import make_middleware
from repoze.who.classifiers import default_challenge_decider, \
default_request_classifier
from repoze.who.interfaces import IAuthenticator, IMetadataProvider
__all__ = ['AuthorizationMetadata', 'setup_auth']
class AuthorizationMetadata(object):
"""
repoze.who metadata provider to load groups and permissions data for
the current user.
There's no need to include this class in the end-user documentation,
as there's no reason why they may ever need it... It's only by
:func:`setup_auth`.
"""
implements(IMetadataProvider)
def __init__(self, group_adapters=None, permission_adapters=None):
"""
Fetch the groups and permissions of the authenticated user.
:param group_adapters: Set of adapters that retrieve the known groups
of the application, each identified by a keyword.
:type group_adapters: dict
:param permission_adapters: Set of adapters that retrieve the
permissions for the groups, each identified by a keyword.
:type permission_adapters: dict
"""
self.group_adapters = group_adapters
self.permission_adapters = permission_adapters
def _find_groups(self, identity):
"""
Return the groups to which the authenticated user belongs, as well as
the permissions granted to such groups.
"""
groups = set()
permissions = set()
if self.group_adapters is not None:
# repoze.what-2.X group adapters expect to find the
# 'repoze.what.userid' key in the credentials
credentials = identity.copy()
credentials['repoze.what.userid'] = identity['repoze.who.userid']
# It's using groups/permissions-based authorization
for grp_fetcher in self.group_adapters.values():
groups |= set(grp_fetcher.find_sections(credentials))
for group in groups:
for perm_fetcher in self.permission_adapters.values():
permissions |= set(perm_fetcher.find_sections(group))
return tuple(groups), tuple(permissions)
# IMetadataProvider
def add_metadata(self, environ, identity):
"""
Load the groups and permissions of the authenticated user.
It will load such data into the :mod:`repoze.who` ``identity`` and
the :mod:`repoze.what` ``credentials`` dictionaries.
:param environ: The WSGI environment.
:param identity: The :mod:`repoze.who`'s ``identity`` dictionary.
"""
logger = environ.get('repoze.who.logger')
# Finding the groups and permissions:
groups, permissions = self._find_groups(identity)
identity['groups'] = groups
identity['permissions'] = permissions
# Adding the groups and permissions to the repoze.what credentials for
# forward compatibility:
if 'repoze.what.credentials' not in environ:
environ['repoze.what.credentials'] = {}
environ['repoze.what.credentials']['groups'] = groups
environ['repoze.what.credentials']['permissions'] = permissions
# Adding the userid:
userid = identity['repoze.who.userid']
environ['repoze.what.credentials']['repoze.what.userid'] = userid
# Adding the adapters:
environ['repoze.what.adapters'] = {
'groups': self.group_adapters,
'permissions': self.permission_adapters
}
# Logging
logger and logger.info('User belongs to the following groups: %s' %
str(groups))
logger and logger.info('User has the following permissions: %s' %
str(permissions))
def setup_auth(app, group_adapters=None, permission_adapters=None, **who_args):
"""
Setup :mod:`repoze.who` with :mod:`repoze.what` support.
:param app: The WSGI application object.
:param group_adapters: The group source adapters to be used.
:type group_adapters: dict
:param permission_adapters: The permission source adapters to be used.
:type permission_adapters: dict
:param who_args: Authentication-related keyword arguments to be passed to
:mod:`repoze.who`.
:return: The WSGI application with authentication and authorization
middleware.
.. tip::
If you are looking for an easier way to get started, you may want to
use :mod:`the quickstart plugin ` and
its :func:`setup_sql_auth()
` function.
You must define the ``group_adapters`` and ``permission_adapters``
keyword arguments if you want to use the groups/permissions-based
authorization pattern.
Additional keyword arguments will be passed to
:func:`repoze.who.plugins.testutil.make_middleware` -- and
among those keyword arguments, you *must* define at least the identifier(s),
authenticator(s) and challenger(s) to be used. For example::
from repoze.who.plugins.basicauth import BasicAuthPlugin
from repoze.who.plugins.htpasswd import HTPasswdPlugin, crypt_check
from repoze.what.middleware import setup_auth
from repoze.what.plugins.xml import XMLGroupsAdapter
from repoze.what.plugins.ini import INIPermissionAdapter
# Defining the group adapters; you may add as much as you need:
groups = {'all_groups': XMLGroupsAdapter('/path/to/groups.xml')}
# Defining the permission adapters; you may add as much as you need:
permissions = {'all_perms': INIPermissionAdapter('/path/to/perms.ini')}
# repoze.who identifiers; you may add as much as you need:
basicauth = BasicAuthPlugin('Private web site')
identifiers = [('basicauth', basicauth)]
# repoze.who authenticators; you may add as much as you need:
htpasswd_auth = HTPasswdPlugin('/path/to/users.htpasswd', crypt_check)
authenticators = [('htpasswd', htpasswd_auth)]
# repoze.who challengers; you may add as much as you need:
challengers = [('basicauth', basicauth)]
app_with_auth = setup_auth(
app,
groups,
permissions,
identifiers=identifiers,
authenticators=authenticators,
challengers=challengers)
.. attention::
Keep in mind that :mod:`repoze.who` must be configured `through`
:mod:`repoze.what` for authorization to work.
.. note::
If you want to skip authentication while testing your application,
you should pass the ``skip_authentication`` keyword argument with a
value that evaluates to ``True``.
.. versionchanged:: 1.0.5
:class:`repoze.who.middleware.PluggableAuthenticationMiddleware`
replaced with :func:`repoze.who.plugins.testutil.make_middleware`
internally.
"""
authorization = AuthorizationMetadata(group_adapters,
permission_adapters)
if 'mdproviders' not in who_args:
who_args['mdproviders'] = []
who_args['mdproviders'].append(('authorization_md', authorization))
if 'classifier' not in who_args:
who_args['classifier'] = default_request_classifier
if 'challenge_decider' not in who_args:
who_args['challenge_decider'] = default_challenge_decider
if os.environ.get('AUTH_LOG'):
import sys
who_args['log_stream'] = sys.stdout
skip_authn = who_args.pop('skip_authentication', False)
middleware = make_middleware(skip_authn, app, **who_args)
return middleware