[Web-SIG] Communicating authenticated user information

Clark C. Evans cce at clarkevans.com
Thu Jan 26 06:38:43 CET 2006


Uncle! Uncle!

On Wed, Jan 25, 2006 at 12:17:29PM -0500, Phillip J. Eby wrote:
| If each middleware or application does this:
| 
|     remote_user = environ.setdefault('paste.remote_user', [])
| 
| And then uses the contents of that list as the thing to check or modify, 
| then you will get the exact same result as the "pass the same environ" 
| approach, except that it's actually compatible with PEP 333, as opposed 
| to relying on implementation accidents.

Ok, assuming that we want an "extension API" for this sort of thing; I'd
rather have a bit more general solution. At the very least, it would be
nice to have a unified way to pass the REMOTE_USER up the WSGI stack so
that each WSGI middleware toolkit doesn't have to roll their own (ie,
paste.remote_user and zope.set_user).  But ideally, the solution should
handle more than just REMOTE_USER since I need to track session
identifiers and other environment changes.  Here is a proposal.

  wsgi.notify(key, value)

    This optional environment variable is a function used to notify
    previous stages of processing about a change in the ``environ``.
    Authentication middleware components, for example, would want to
    do something like:

      if environ.get('wsgi.notify'):
         environ.get('wsgi.notify')('REMOTE_USER','foo')
      environ['REMOTE_USER'] = 'foo'
 
    when setting an common environment variable which may be useful
    to previous processing stages.  Prior stages may then 
    watch for particularly important changes by replacing this
    function, making sure to call prior instances, like:
    
      class Logger:
         def __init__(self, application):
             self.application = application
             self.user_counts = {}
         def __call__(self, environ, start_response):
             prev_notify = environ.get('wsgi.notify', lambda k,v: None)
             def notify(k,v):
                 if 'REMOTE_USER' == k:
                     environ['bing.user'] = v
                 prev_notify(k,v)
             def _start_response(status, response_headers, exce_info=None):
                 user = environ.get('bing.user','anonymous')
                 self.access[user] = self.access.get(user,0) + 1 
                 return start_response(status, response_headers, exce_info)
             environ['wsgi.notify'] = notify
             return self.application(environ, _start_response)

| >The WSGI middleware components that actually create their own environ
| >are few and far between.  This is an uncommon edge case.
| 
| Composability of applications is a critical requirement for WSGI 
| middleware.  It doesn't matter how uncommon it is.  Even if there were 
| *zero* implementations of such middleware right now, that principle 
| would take precedence, meaning you'd have to have a proposal that would 
| preserve composability.  Right now, you haven't described a way to do 
| that without introducing temporal coupling (or worse) among subrequests.

If you assume a single thread of control; ie, all sub-requests are done
sequentially, then "extension APIs" share all of the pitfalls as
mandating a single ``environ``.  I've demonstrated how this is possible
in an earlier message.

However, in a *threaded* environment, the approach I proposed is
unworkable if sub-requests are executed in parallel.  In this case,
strange and nasty consequences would exist if multiple sub-applications
were accessing the same ``environ`` dict.  It is for this reason that
I'm throwing in the towel.

I hope something like the proposal above; or my other attempt to 
formalize a response-based approach are closer to your liking.

Best Wishes,

Clark


More information about the Web-SIG mailing list