Tres Seaver
2016-05-31 455778d138ea623d224c9206e5001fd2a1fd7e1c
repoze/who/middleware.py
@@ -1,12 +1,9 @@
import logging
from StringIO import StringIO
import sys
from repoze.who.api import APIFactory
from repoze.who.interfaces import IIdentifier
from repoze.who.interfaces import IAuthenticator
from repoze.who.interfaces import IChallenger
from repoze.who.interfaces import IMetadataProvider
from repoze.who._compat import StringIO
_STARTED = '-- repoze.who request started (%s) --'
_ENDED = '-- repoze.who request ended (%s) --'
@@ -18,12 +15,23 @@
                 authenticators,
                 challengers,
                 mdproviders,
                 request_classifier,
                 challenge_decider,
                 request_classifier = None,
                 challenge_decider = None,
                 log_stream = None,
                 log_level = logging.INFO,
                 remote_user_key = 'REMOTE_USER',
                 classifier = None
                 ):
        if challenge_decider is None:
            raise ValueError('challenge_decider is required')
        if request_classifier is not None and classifier is not None:
            raise ValueError(
                    'Only one of request_classifier and classifier is allowed')
        if request_classifier is None:
            if classifier is None:
                raise ValueError(
                        'Either request_classifier or classifier is required')
            request_classifier = classifier
        self.app = app
        logger = self.logger = None
        if isinstance(log_stream, logging.Logger):
@@ -44,7 +52,9 @@
                                      mdproviders,
                                      request_classifier,
                                      challenge_decider,
                                      logger)
                                      remote_user_key,
                                      logger
                                     )
    def __call__(self, environ, start_response):
@@ -62,44 +72,7 @@
        logger = self.logger
        path_info = environ.get('PATH_INFO', None)
        logger and logger.info(_STARTED % path_info)
        classification = api.request_classifier(environ)
        logger and logger.info('request classification: %s' % classification)
        userid = None
        identity = None
        identifier = None
        ids = api.identify(environ, classification)
        # ids will be list of tuples: [ (IIdentifier, identity) ]
        if ids:
            auth_ids = api.authenticate(environ, classification, ids)
            # auth_ids will be a list of five-tuples in the form
            #  ( (auth_rank, id_rank), authenticator, identifier, identity,
            #    userid )
            #
            # When sorted, its first element will represent the "best"
            # identity for this request.
            if auth_ids:
                auth_ids.sort()
                best = auth_ids[0]
                rank, authenticator, identifier, identity, userid = best
                identity = Identity(identity) # dont show contents at print
                # allow IMetadataProvider plugins to scribble on the identity
                api.add_metadata(environ, classification, identity)
                # add the identity to the environment; a downstream
                # application can mutate it to do an 'identity reset'
                # as necessary, e.g. identity['login'] = 'foo',
                # identity['password'] = 'bar'
                environ['repoze.who.identity'] = identity
                # set the REMOTE_USER
                environ[self.remote_user_key] = userid
        else:
            logger and logger.info('no identities found, not authenticating')
        api.authenticate()  # identity saved in environ
        # allow identifier plugins to replace the downstream
        # application (to do redirection and unauthorized themselves
@@ -122,36 +95,32 @@
        if api.challenge_decider(environ, wrapper.status, wrapper.headers):
            logger and logger.info('challenge required')
            close = getattr(app_iter, 'close', _no_op)
            challenge_app = api.challenge(
                environ,
                classification,
                wrapper.status,
                wrapper.headers,
                identifier,
                identity
                )
            challenge_app = api.challenge(wrapper.status, wrapper.headers)
            if challenge_app is not None:
                logger and logger.info('executing challenge app')
                if app_iter:
                    list(app_iter) # unwind the original app iterator
                # PEP 333 requires that we call the original iterator's
                # 'close' method, if it exists, before releasing it.
                close()
                # replace the downstream app with the challenge app
                app_iter = challenge_app(environ, start_response)
            else:
                logger and logger.info('configuration error: no challengers')
                close()
                raise RuntimeError('no challengers found')
        else:
            logger and logger.info('no challenge required')
            remember_headers = []
            if identifier:
                remember_headers = identifier.remember(environ, identity)
                if remember_headers:
                    logger and logger.info('remembering via headers from %s: %s'
                                           % (identifier, remember_headers))
            remember_headers = api.remember()
            wrapper.finish_response(remember_headers)
        logger and logger.info(_ENDED % path_info)
        return app_iter
def _no_op():
    pass
def wrap_generator(result):
    """\
@@ -160,8 +129,12 @@
    caches it to trigger any immediate side effects (in a WSGI world, this
    ensures start_response is called).
    """
    # PEP 333 requires that we call the original iterator's
    # 'close' method, if it exists, before releasing it.
    close = getattr(result, 'close', lambda: None)
    # Neat trick to pull the first iteration only. We need to do this outside
    # of the generator function to ensure it is called.
    first = marker = []
    for iter in result:
        first = iter
        break
@@ -169,25 +142,13 @@
    # Wrapper yields the first iteration, then passes result's iterations
    # directly up.
    def wrapper():
        yield first
        if first is not marker:
            yield first
        for iter in result:
            # We'll let result's StopIteration bubble up directly.
            yield iter
        close()
    return wrapper()
def match_classification(iface, plugins, classification):
    result = []
    for plugin in plugins:
        plugin_classifications = getattr(plugin, 'classifications', {})
        iface_classifications = plugin_classifications.get(iface)
        if not iface_classifications: # good for any
            result.append(plugin)
            continue
        if classification in iface_classifications:
            result.append(plugin)
    return result
class StartResponseWrapper(object):
    def __init__(self, start_response):
@@ -225,13 +186,13 @@
def make_test_middleware(app, global_conf):
    """ Functionally equivalent to
    [plugin:form]
    use = repoze.who.plugins.form.FormPlugin
    rememberer_name = cookie
    login_form_qs=__do_login
    [plugin:redirector]
    use = repoze.who.plugins.redirector.RedirectorPlugin
    login_url = /login.html
    [plugin:cookie]
    use = repoze.who.plugins.cookie:InsecureCookiePlugin
    [plugin:auth_tkt]
    use = repoze.who.plugins.auth_tkt:AuthTktCookiePlugin
    secret = SEEKRIT
    cookie_name = oatmeal
    [plugin:basicauth]
@@ -248,22 +209,20 @@
    challenge_decider = repoze.who.classifiers:default_challenge_decider
    [identifiers]
    plugins = form:browser cookie basicauth
    plugins = authtkt basicauth
    [authenticators]
    plugins = htpasswd
    plugins = authtkt htpasswd
    [challengers]
    plugins = form:browser basicauth
    plugins = redirector:browser basicauth
    """
    # be able to test without a config file
    from repoze.who.plugins.basicauth import BasicAuthPlugin
    from repoze.who.plugins.auth_tkt import AuthTktCookiePlugin
    from repoze.who.plugins.cookie import InsecureCookiePlugin
    from repoze.who.plugins.form import FormPlugin
    from repoze.who.plugins.redirector import RedirectorPlugin
    from repoze.who.plugins.htpasswd import HTPasswdPlugin
    io = StringIO()
    salt = 'aa'
    for name, password in [ ('admin', 'admin'), ('chris', 'chris') ]:
        io.write('%s:%s\n' % (name, password))
    io.seek(0)
@@ -272,13 +231,14 @@
    htpasswd = HTPasswdPlugin(io, cleartext_check)
    basicauth = BasicAuthPlugin('repoze.who')
    auth_tkt = AuthTktCookiePlugin('secret', 'auth_tkt')
    cookie = InsecureCookiePlugin('oatmeal')
    form = FormPlugin('__do_login', rememberer_name='auth_tkt')
    form.classifications = { IIdentifier:['browser'],
                             IChallenger:['browser'] } # only for browser
    identifiers = [('form', form),('auth_tkt',auth_tkt),('basicauth',basicauth)]
    redirector = RedirectorPlugin('/login.html')
    redirector.classifications = {IChallenger: ['browser']} # only for browser
    identifiers = [('auth_tkt', auth_tkt),
                   ('basicauth', basicauth),
                  ]
    authenticators = [('htpasswd', htpasswd)]
    challengers = [('form',form), ('basicauth',basicauth)]
    challengers = [('redirector', redirector),
                   ('basicauth', basicauth)]
    mdproviders = []
    from repoze.who.classifiers import default_request_classifier
    from repoze.who.classifiers import default_challenge_decider
@@ -298,38 +258,3 @@
        log_level = logging.DEBUG
        )
    return middleware
def verify(plugin, iface):
    from zope.interface.verify import verifyObject
    verifyObject(iface, plugin, tentative=True)
def make_registries(identifiers, authenticators, challengers, mdproviders):
    from zope.interface.verify import BrokenImplementation
    interface_registry = {}
    name_registry = {}
    for supplied, iface in [ (identifiers, IIdentifier),
                             (authenticators, IAuthenticator),
                             (challengers, IChallenger),
                             (mdproviders, IMetadataProvider)]:
        for name, value in supplied:
            try:
                verify(value, iface)
            except BrokenImplementation, why:
                why = str(why)
                raise ValueError(str(name) + ': ' + why)
            L = interface_registry.setdefault(iface, [])
            L.append(value)
            name_registry[name] = value
    return interface_registry, name_registry
class Identity(dict):
    """ dict subclass that prevents its members from being rendered
    during print """
    def __repr__(self):
        return '<repoze.who identity (hidden, dict-like) at %s>' % id(self)
    __str__ = __repr__