Commits

Lynn Rees  committed 1193f57

[svn]

  • Participants
  • Parent commits 5d2839d
  • Branches wsgiauth

Comments (0)

Files changed (7)

File trunk/wsgiauth/__init__.py

+# Copyright (c) 2006 L. C. Rees.  All rights reserved.
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the "Software"),
+# to deal in the Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute, sublicense,
+# and/or sell copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+__all__ = ['base', 'basic', 'cookie', 'digest', 'ip', 'openid', 'url', 'util']

File trunk/wsgiauth/basic.py

 HTTP 1.0:
 
 http://www.w3.org/Protocols/HTTP/1.0/draft-ietf-http-spec.html#BasicAA
-
-Do not use basic authentication unless you are using SSL or need to work
-with very out-dated clients, use HTTP digest authentication instead.'''
+'''
 
 from wsgiauth.base import Scheme, HTTPAuth
 

File trunk/wsgiauth/digest.py

-#! /usr/bin/env python
 # (c) 2005 Clark C. Evans
-#
-# This module is part of the Python Paste Project and is released under
-# the MIT License: http://www.opensource.org/licenses/mit-license.php
-# This code was written with funding by http://prometheusresearch.com
-#
 # Copyright (c) 2006 L. C. Rees.  All rights reserved.
 #
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the "Software"),
+# to deal in the Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute, sublicense,
+# and/or sell copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following conditions:
 #
-# 1.  Redistributions of source code must retain the above copyright notice,
-# this list of conditions and the following disclaimer.
-# 2.  Redistributions in binary form must reproduce the above copyright
-# notice, this list of conditions and the following disclaimer in the
-# documentation and/or other materials provided with the distribution.
-# 3.  Neither the name of the Portable Site Information Project nor the names
-# of its contributors may be used to endorse or promote products derived from
-# this software without specific prior written permission.
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
 #
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-# POSSIBILITY OF SUCH DAMAGE.
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
 
 '''HTTP Digest Authentication
 
 HTTP 1.1 specification:
 
 http://www.w3.org/Protocols/HTTP/1.1/spec.html#DigestAA
-
-Basically, you just put this module before your WSGI application, and it
-takes care of requesting and handling authentication requests.
-
-This code has not been audited by a security expert, please use with
-caution (or better yet, report security holes). At this time, this
-implementation does not provide for further challenges, nor does it
-support Authentication-Info header.  It also uses md5, and an option
-to use sha would be a good thing if it was supported by browser clients.
 '''
 
 import md5

File trunk/wsgiauth/ip.py

 # (c) 2005 Ian Bicking and contributors; written for Paste
-# (http://pythonpaste.org) # Licensed under the MIT license:
-# http://www.opensource.org/licenses/mit-license.php
-#
 # Copyright (c) 2006 L. C. Rees.  All rights reserved.
 #
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the "Software"),
+# to deal in the Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute, sublicense,
+# and/or sell copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following conditions:
 #
-# 1.  Redistributions of source code must retain the above copyright notice,
-# this list of conditions and the following disclaimer.
-# 2.  Redistributions in binary form must reproduce the above copyright
-# notice, this list of conditions and the following disclaimer in the
-# documentation and/or other materials provided with the distribution.
-# 3.  Neither the name of the Portable Site Information Project nor the names
-# of its contributors may be used to endorse or promote products derived from
-# this software without specific prior written permission.
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
 #
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-# POSSIBILITY OF SUCH DAMAGE.
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
 
 '''Authenticate an IP address.'''
 
-from util import Forbidden
+from wsgiauth.util import Forbidden
 
 __all__ = ['IP', 'ip']
 
 
 class IP(object):
 
-    '''On each request, `REMOTE_ADDR` is authenticated and access is allowed
-    based on that.
+    '''On each request, `REMOTE_ADDR` is authenticated and access allowed based
+    on IP address.
     '''
 
     def __init__(self, app, authfunc, **kw):

File trunk/wsgiauth/openid.py

 # (c) 2005 Ben Bangert
 # (c) 2005 Clark C. Evans
-# This module is part of the Python Paste Project and is released under
-# the MIT License: http://www.opensource.org/licenses/mit-license.php
-# This code was written with funding by http://prometheusresearch.com
-#
-# Copyright (c) 2005 Allan Saddi <allan@saddi.com>
 # Copyright (c) 2006 L. C. Rees.  All rights reserved.
 #
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the "Software"),
+# to deal in the Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute, sublicense,
+# and/or sell copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following conditions:
 #
-# 1.  Redistributions of source code must retain the above copyright notice,
-# this list of conditions and the following disclaimer.
-# 2.  Redistributions in binary form must reproduce the above copyright
-# notice, this list of conditions and the following disclaimer in the
-# documentation and/or other materials provided with the distribution.
-# 3.  Neither the name of the Portable Site Information Project nor the names
-# of its contributors may be used to endorse or promote products derived from
-# this software without specific prior written permission.
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
 #
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-# POSSIBILITY OF SUCH DAMAGE.
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
 
 '''OpenID Authentication (Consumer)
 
 
     http://openid.net/
 
-You can have multiple identities in the same way you can have multiple URLs.
-All OpenID does is provide a way to prove that you own a URL (identity).
-And it does this without passing around your password, your email address, or
-anything you don't want it to. There's no profile exchange component at all:
-your profiile is your identity URL.
-
-This module is based off the consumer.py example that comes with the Python
-OpenID library.
+This module is based on the consumer.py example that comes with the Python
+OpenID library 1.1+.
 '''
 
 import cgi
 import sys
 from Cookie import SimpleCookie
 try:
-    import openid
+    import openid as oid
 except ImportError:
     print >> sys.stderr, '''Failed to import the OpenID library.
 In order to use this example, you must either install the library
 from openid.cryptutil import randomString
 from yadis.discover import DiscoveryFailure
 from urljr.fetchers import HTTPFetchingError
-from util import geturl, getpath, Redirect, Response
-from cookie import Cookie
+from wsgiauth.util import geturl, getpath, Redirect, Response
+from wsgiauth.cookie import Cookie
 
+__all__ = ['OpenID', 'openid']
+
+def quote(s):
+    '''Quotes URLs passed as query parameters.'''
+    return '"%s"' % cgi.escape(s, 1)
+
+def openid(store, **kw):
+    '''Decorator for OpenID authorized middleware.'''
+    def decorator(application):
+        return OpenID(application, store, **kw)
+    return decorator
+
+# Fallback session tracker
+_tracker = {}
+# Fallback template
 TEMPLATE = '''<html>
   <head><title>OpenID Form</title></head>
   <body>
   </body>
 </html>'''
 
-__all__ = ['OpenID', 'openided']
-
-def quote(s):
-    return '"%s"' % cgi.escape(s, 1)
-
-def openided(store, **kw):
-    
-    def decorator(application):
-        return OpenID(application, store, **kw)
-    return decorator
-
-
-# Default session tracker
-_tracker = {}
-
-
 class OpenID(Cookie):
 
     def __init__(self, app, store, **kw):
+        # Make OpenIDAuth the authorization function
         auth = OpenIDAuth(store, **kw)
         super(OpenID, self).__init__(app, auth, **kw)
         self.authorize = auth
 
     def initial(self, environ, start_response):
         '''Initial response to a request.'''
-        def cookie_response(status, headers, exc_info=None):
+        # Add authentication cookie
+        def cookie_response(status, headers, exc_info=None):            
             headers.append(('Set-Cookie', self.generate(environ)))
             return start_response(status, headers, exc_info)
+        # Redirect to original URL
         redirect = Redirect(environ['openid.redirect'])
         return redirect(environ, cookie_response)
         
     cname = '_OIDA_'
 
     def __init__(self, store, **kw):
-        self.store = filestore.FileOpenIDStore(store)
-        # Location to load after successful process of login        
-        self.login_redirect = kw.get('login_redirect')
-        #  A function which should return a username. 
-        self.getuser = kw.get('getuser')        
-        # Session cache
+        # OpenID store
+        self.store = filestore.FileOpenIDStore(store)     
+        # Session tracker
         self.tracker = kw.get('tracker', _tracker)
-        # Template
+        # Set template
         self.template = kw.get('template', TEMPLATE)
 
     def __call__(self, environ):
-        environ['openid.baseurl'] = geturl(environ, False, False)        
+        # Base URL
+        environ['openid.baseurl'] = geturl(environ, False, False)
+        # Query string
         environ['openid.query'] = dict(cgi.parse_qsl(environ['QUERY_STRING']))
+        # Path
         path = getpath(environ)
+        # Start verification
         if path == '/verify':
             return self.verify(environ)
+        # Process response
         elif path == '/process':
             return self.process(environ)
-        else:
+        # Prompt for URL
+        else:            
             message = 'Enter an OpenID Identifier to verify.'
             return self.response(message, environ)
 
         '''Process the form submission, initating OpenID verification.'''
         # First, make sure that the user entered something
         openid_url = environ['openid.query'].get('openid_url')
+        # Ensure a URL is entered
         if not openid_url:
             message = 'Enter an OpenID Identifier to verify.'
             return self.response(message, environ)
+        # Start open id session
         oidconsumer = self.getconsumer(environ)
+        # Start verification
         try:
             request = oidconsumer.begin(openid_url)
+        # Handle HTTP errors
         except HTTPFetchingError, exc:
             message = 'Error in discovery: %s' % cgi.escape(str(exc.why))
             return self.response(message, environ, openid_url)
+        # Handle Discovery errors
         except DiscoveryFailure, exc:
             message = 'Error in discovery: %s' % cgi.escape(str(exc[0]))
             return self.response(message, environ, openid_url)
         else:
+            # Handle URLs that don't have a discernable OpenID server
             if request is None:
                 fmt = 'No OpenID services found for %s'
                 return self.response(fmt % cgi.escape(openid_url), environ)
+            # Start redirect
             else:
                 return self.redirect(environ, request)      
 
         info = oidconsumer.complete(environ['openid.query'])
         # Handle successful responses
         if info.status == consumer.SUCCESS:            
-            # Handle i-names
+            # Fetch original requested URL
             redirecturl = self.tracker[self.getsid(environ)]['redirect']
             environ['openid.redirect'] = redirecturl
+            # Handle i-names
             if info.endpoint.canonicalID:                    
                 return info.endpoint.canonicalID
-            elif self.getuser:
-                return self.getuser(environ, info.identity_url )                
+            # Otherwise, return identity URL as user name
             else:
                 return info.identity_url
         # Handle failure to verify a URL where URL is returned.
         return appendArgs(base, query)
 
     def getconsumer(self, environ):
+        '''Get an OpenID consumer with session.'''
         return consumer.Consumer(self.getsession(environ), self.store)
 
     def response(self, message, env, url=''):
+        '''Default response.'''
+        # Set OpenID session cookie
         hdrs = [('Set-Cookie', self.setsession(env))]
+        # Build message
         cmessage = (message, quote(self.buildurl(env, 'verify')), quote(url))
         return Response(cmessage, template=self.template, headers=hdrs)
 
     def redirect(self, environ, request):
+        '''Redirect response.'''
+        # Set OpenID session cookie
         hdrs = [('Set-Cookie', self.setsession(environ))]
+        # Build redirect URL
         trust_root = environ['openid.baseurl']                
         return_to = self.buildurl(environ, 'process')
         redirect_url = request.redirectURL(trust_root, return_to)
         return Redirect(redirect_url, headers=hdrs)
 
     def getsession(self, environ):
-        """Return the existing session or a new session"""
+        '''Return the existing session or a new session'''
         # Get value of cookie header that was sent
         sid = self.getsid(environ)
         # If a session id was not set, create a new one
         return session
 
     def getsid(self, environ):
+        '''Returns a session identifier.'''
+        # Fetch cookie
         cookie_str = environ.get('HTTP_COOKIE')        
         if cookie_str:
             cookie_obj = SimpleCookie(cookie_str)
             sid_morsel = cookie_obj.get(self.cname, None)
+            # Get session id from cookie
             if sid_morsel is not None:
                 sid = sid_morsel.value
             else:
         return sid
 
     def setsession(self, environ):
+        '''Returns a session identifier.'''
         sid = self.getsession(environ)['id']
         return '%s=%s;' % (self.cname, sid)     

File trunk/wsgiauth/url.py

 # (c) 2005 Clark C. Evans
-# This module is part of the Python Paste Project and is released under
-# the MIT License: http://www.opensource.org/licenses/mit-license.php
-# This code was written with funding by http://prometheusresearch.com
-#
-# Copyright (c) 2005 Allan Saddi <allan@saddi.com>
 # Copyright (c) 2006 L. C. Rees.  All rights reserved.
 #
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the "Software"),
+# to deal in the Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute, sublicense,
+# and/or sell copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following conditions:
 #
-# 1.  Redistributions of source code must retain the above copyright notice,
-# this list of conditions and the following disclaimer.
-# 2.  Redistributions in binary form must reproduce the above copyright
-# notice, this list of conditions and the following disclaimer in the
-# documentation and/or other materials provided with the distribution.
-# 3.  Neither the name of the Portable Site Information Project nor the names
-# of its contributors may be used to endorse or promote products derived from
-# this software without specific prior written permission.
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
 #
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-# POSSIBILITY OF SUCH DAMAGE.
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
 
 '''Persistent authentication tokens in URL query components.'''
 
 import cgi
-from base import BaseAuth
-from util import Redirect, geturl
+from wsgiauth.base import BaseAuth
+from wsgiauth.util import Redirect, geturl
 
 __all__ = ['URLAuth', 'urlauth']
 

File trunk/wsgiauth/util.py

 # Copyright (c) 2006 L. C. Rees.  All rights reserved.
 #
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the "Software"),
+# to deal in the Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute, sublicense,
+# and/or sell copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following conditions:
 #
-# 1.  Redistributions of source code must retain the above copyright notice,
-# this list of conditions and the following disclaimer.
-# 2.  Redistributions in binary form must reproduce the above copyright
-# notice, this list of conditions and the following disclaimer in the
-# documentation and/or other materials provided with the distribution.
-# 3.  Neither the name of the Portable Site Information Project nor the names
-# of its contributors may be used to endorse or promote products derived from
-# this software without specific prior written permission.
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
 #
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-# POSSIBILITY OF SUCH DAMAGE.
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
 
 import cgi
 from urllib import quote
         
     def __call__(self, start_response):
         start_response(self.status, self.headers)
-        return self.response(self.message)
+        return self.response(self.message or geturl(environ))
 
 
 class NotFound(Forbidden):