[Zope-PAS] Struggling with 'challenge' support.

Lennart Regebro regebro at nuxeo.com
Thu Sep 23 12:55:47 EDT 2004


Lennart Regebro wrote:
> Hmmm.... I just realized, it might be possible to wrap exception instead 
> of changing it, that woudl be neater. And then to the challenge 
> *afterwards* and make the plugins write to response *last*. That could 
> actaully work, if nothing else works. Hmm....

OK, that works. Problem: The headers are already scribbled on, so you 
may have to UN-scribble them. Solution: Only call response.exception 
when it is NOT an Unauthorized exception that is being thrown. That MAY 
have side effects of which I am not aware.

The code gets pretty simple, which feels good:

PluggableAuthService now looks like this:

     def __call__(self, container, req):
         """ The __before_publishing_traverse__ hook. """
         resp = req['RESPONSE']
         resp.exception = self.exception
         return

     #
     # Response overrides
     #
     def exception(self, fatal=0, info=None,
                   absuri_match=re.compile(r'\w+://[\w\.]+').match,
                   tag_search=re.compile('[a-zA-Z]>').search,
                   abort=1
                   ):
         req = self.REQUEST
         resp = req['RESPONSE']
         try: del resp.exception
         except: pass

         if type(info) is type(()) and len(info) == 3:
             t, v, tb = info
         else:
             t, v, tb = sys.exc_info()

         if t == 'Unauthorized' or t == Unauthorized or (
             isinstance(t, types.ClassType) and issubclass(t, 
Unauthorized)):
             t = 'Unauthorized'
             self.challenge(req, resp)
             return resp

         return resp.exception(fatal, info, absuri_match, tag_search, abort)

     def challenge(self, request, response):
         # Go through all challenge plugins
         plugins = self._getOb('plugins')
         challengers = plugins.listPlugins( IChallengePlugin )
         for challenger_id, challenger in challengers:
             if challenger.challenge(request, response):
                 break

Note that we can't call all challengers at once, that will just mess up 
the headers beyond recognition, instead, a challenger returns 1 if it 
wants to do the challenge.

HTTPBasicAuthHelper:

    def challenge( self, request, response, **kw ):

         """ Challenge the user for credentials.
         """
         realm = response.realm
         if realm:
             response.setHeader('WWW-Authenticate', 'basic realm="%s"' % 
realm, 1)
         m = "<strong>You are not authorized to access this 
resource.</strong>"
         if response.debug_mode:
             if response._auth:
                 m = m + '<p>\nUsername and password are not correct.'
             else:
                 m = m + '<p>\nNo Authorization header found.'

         response.setBody(m, is_error=1)
         response.setStatus(401)
         return 1

CasAuthHelper (does aredirect):

     security.declarePrivate('challenge')
     def challenge(self, request, response, **kw):
         """ Challenge the user for credentials. """
         # Redirect if desired.
         url = self.getLoginURL()
         if url:
             came_from = request.get('came_from', None)
             if came_from is None:
                 came_from = request['URL']
             query = urllib.urlencode({'service': came_from})
             #del response.headers['WWW-Authenticate']
             response.redirect('%s?%s' % (url, query))
             return 1
         # Fall through to the standard unauthorized() call.
         return 0

This seems to work fine for me. Screamif it is stupid, or I'll check 
this in tomorrow. It's a bug today too, we can discuss it then.


//Lennart


More information about the Zope-PAS mailing list