| | |
| | | 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) --' |
| | |
| | | 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): |
| | |
| | | mdproviders, |
| | | request_classifier, |
| | | challenge_decider, |
| | | logger) |
| | | remote_user_key, |
| | | logger |
| | | ) |
| | | |
| | | |
| | | def __call__(self, environ, start_response): |
| | |
| | | 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 |
| | |
| | | |
| | | 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): |
| | | """\ |
| | |
| | | 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 |
| | |
| | | # 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): |
| | |
| | | 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] |
| | |
| | | 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) |
| | |
| | | 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 |
| | |
| | | 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__ |
| | | |
| | | |