Tres Seaver
2009-05-09 0dd8088ae9eadc86d94aabcbf69ab1b079ed34c3
Added 'passthrough_challenge_decider', which avoids re-challenging 401
responses which have been "pre-challenged" by the application.

4 files modified
66 ■■■■■ changed files
CHANGES.txt 3 ●●●●● patch | view | raw | blame | history
docs/narr.rst 13 ●●●● patch | view | raw | blame | history
repoze/who/classifiers.py 21 ●●●●● patch | view | raw | blame | history
repoze/who/tests/test_classifiers.py 29 ●●●●● patch | view | raw | blame | history
CHANGES.txt
@@ -5,6 +5,9 @@
After 1.0.13
============
- Added 'passthrough_challenge_decider', which avoids re-challenging 401
  responses which have been "pre-challenged" by the application.
- One-hundred percent unit test coverage.
1.0.13 (2009/4/24)
docs/narr.rst
@@ -27,9 +27,10 @@
themselves as willing to participate in identification and
authorization for a request based on this classification.  The request
classification system is pluggable.  :mod:`repoze.who` provides a
default classifier that you may use.  You may extend the
classification system by making :mod:`repoze.who` aware of a different
request classifier implementation.
default classifier that you may use.
You may extend the classification system by making :mod:`repoze.who` aware
of a different request classifier implementation.
Challenge Deciders
------------------
@@ -38,6 +39,12 @@
response returned from a downstream application requires a challenge
plugin to fire.  When using the default challenge decider, only the
status is used (if it starts with ``401``, a challenge is required).
:mod:`repoze.who` also provides an alternate challenge decider,
``repoze.who.classifiers.passthrough_challenge_decider``, which avoids
challenging ``401`` responses which have been "pre-challenged" by the
application.
You may supply a different challenge decider as necessary.
Plugins
repoze/who/classifiers.py
@@ -1,6 +1,7 @@
from paste.httpheaders import USER_AGENT
from paste.httpheaders import REQUEST_METHOD
from paste.httpheaders import CONTENT_TYPE
from paste.httpheaders import USER_AGENT
from paste.httpheaders import WWW_AUTHENTICATE
import zope.interface
from repoze.who.interfaces import IRequestClassifier
@@ -51,3 +52,21 @@
def default_challenge_decider(environ, status, headers):
    return status.startswith('401 ')
zope.interface.directlyProvides(default_challenge_decider, IChallengeDecider)
def passthrough_challenge_decider(environ, status, headers):
    """ Don't challenge for pre-challenged responses.
    o Assume responsese with 'WWW-Authenticate' or an HTML content type
      are pre-challenged.
    """
    if not status.startswith('401 '):
        return False
    h_dict = dict(headers)
    if 'WWW-Authenticate' in h_dict:
        return False
    ct = h_dict.get('Content-Type')
    if ct is not None:
        return not ct.startswith('text/html')
    return True
zope.interface.directlyProvides(passthrough_challenge_decider,
                                IChallengeDecider)
repoze/who/tests/test_classifiers.py
@@ -60,3 +60,32 @@
    def test_doesnt_challenges_on_non_401(self):
        decider = self._getFUT()
        self.failIf(decider({}, '200 Ok', []))
class TestPassthroughChallengeDecider(unittest.TestCase):
    def _getFUT(self):
        from repoze.who.classifiers import passthrough_challenge_decider
        return passthrough_challenge_decider
    def _makeEnviron(self, kw=None):
        environ = {}
        environ['wsgi.version'] = (1,0)
        if kw is not None:
            environ.update(kw)
        return environ
    def test_challenges_on_bare_401(self):
        decider = self._getFUT()
        self.failUnless(decider({}, '401 Unauthorized', []))
    def test_doesnt_challenges_on_non_401(self):
        decider = self._getFUT()
        self.failIf(decider({}, '200 Ok', []))
    def test_doesnt_challenges_on_401_with_WWW_Authenticate(self):
        decider = self._getFUT()
        self.failIf(decider({}, '401 Ok', [('WWW-Authenticate', 'xxx')]))
    def test_doesnt_challenges_on_401_with_text_html(self):
        decider = self._getFUT()
        self.failIf(decider({}, '401 Ok', [('Content-Type', 'text/html')]))