~leonardr/lazr.restful/version-specific-request-interface

« back to all changes in this revision

Viewing changes to src/lazr/restful/wsgi.py

  • Committer: Leonard Richardson
  • Date: 2009-10-07 17:57:59 UTC
  • mfrom: (79.1.2 forward-port)
  • Revision ID: leonard.richardson@canonical.com-20091007175759-elso1usuu5y3xe8b
[r=allenap,deryck] Split the authentication middleware into a new library, lazr.authentication. Fix a bug that choked on some incoming UTF-8 strings.

Show diffs side-by-side

added added

removed removed

Lines of Context:
2
2
 
3
3
__metaclass__ = type
4
4
__all__ = [
5
 
    'BasicAuthMiddleware',
6
5
    'WSGIApplication',
7
6
    ]
8
7
 
9
8
from pkg_resources import resource_string
10
 
import re
11
 
import urlparse
12
9
from wsgiref.simple_server import make_server as wsgi_make_server
13
10
 
14
11
from zope.component import getUtility
16
13
from zope.interface import implements
17
14
from zope.publisher.publish import publish
18
15
 
19
 
from oauth.oauth import (
20
 
    OAuthError, OAuthRequest, OAuthServer, OAuthSignatureMethod_PLAINTEXT)
21
 
 
22
16
from lazr.restful.interfaces import (
23
17
    IWebServiceConfiguration, IServiceRootResource)
24
18
from lazr.restful.simple import Publication, Request
66
60
        """Create a WSGI server object for a particular web service."""
67
61
        cls.configure_server(host, port, config_package, config_file)
68
62
        return wsgi_make_server(host, int(port), cls)
69
 
 
70
 
 
71
 
class AuthenticationMiddleware(object):
72
 
    """A base class for middleware that authenticates HTTP requests.
73
 
 
74
 
    This class implements a generic HTTP authentication workflow:
75
 
    check whether the requested resource is protected, get credentials
76
 
    from the WSGI environment, validate them (using a callback
77
 
    function) and either allow or deny acces.
78
 
    """
79
 
 
80
 
    def __init__(self, application, authenticate_with,
81
 
                 realm="Restricted area", protect_path_pattern='.*'):
82
 
        """Constructor.
83
 
 
84
 
        :param application: A WSGI application.
85
 
 
86
 
        :param authenticate_with: A callback function that takes some
87
 
            number of credential arguemnts (the number and type
88
 
            depends on the implementation of
89
 
            getCredentialsFromEnvironment()) and returns an object
90
 
            representing the authenticated user. If the credentials
91
 
            are invalid or don't identify any existing user, the
92
 
            function should return None.
93
 
 
94
 
        :param realm: The string to give out as the authentication realm.
95
 
        :param protect_path_pattern: A regular expression string. URL
96
 
            paths matching this string will be protected with the
97
 
            authentication method. URL paths not matching this string
98
 
            can be accessed without authenticating.
99
 
        """
100
 
        self.application = application
101
 
        self.authenticate_with = authenticate_with
102
 
        self.realm = realm
103
 
        self.protect_path_pattern = re.compile(protect_path_pattern)
104
 
 
105
 
    def _unauthorized(self, start_response):
106
 
        """Short-circuit the request with a 401 error code."""
107
 
        start_response("401 Unauthorized",
108
 
                       [('WWW-Authenticate',
109
 
                         'Basic realm="%s"' % self.realm)])
110
 
        return ['401 Unauthorized']
111
 
 
112
 
    def __call__(self, environ, start_response):
113
 
        """Protect certain resources by checking auth credentials."""
114
 
        path_info = environ.get('PATH_INFO', '/')
115
 
        if not self.protect_path_pattern.match(path_info):
116
 
            environ['authenticated_user'] = None
117
 
            return self.application(environ, start_response)
118
 
 
119
 
        try:
120
 
            credentials = self.getCredentialsFromEnvironment(environ)
121
 
        except ValueError:
122
 
            credentials = None
123
 
        if credentials is None:
124
 
            return self._unauthorized(start_response)
125
 
 
126
 
        authenticated_user = self.authenticate_with(*credentials)
127
 
        if authenticated_user is None:
128
 
            return self._unauthorized(start_response)
129
 
 
130
 
        environ['authenticated_user'] = authenticated_user
131
 
 
132
 
        return self.application(environ, start_response)
133
 
 
134
 
    def getCredentialsFromEnvironment(self, environment):
135
 
        """Retrieve a set of credentials from the environment.
136
 
 
137
 
        This superclass implementation ignores the environment
138
 
        entirely, and so never authenticates anybody.
139
 
 
140
 
        :param environment: The WSGI environment.
141
 
        :return: A list of objects to be passed into the authenticate_with
142
 
                 callback function, or None if the credentials could not
143
 
                 be determined.
144
 
        """
145
 
        return None
146
 
 
147
 
 
148
 
class BasicAuthMiddleware(AuthenticationMiddleware):
149
 
    """WSGI middleware that implements HTTP Basic Auth."""
150
 
 
151
 
    def getCredentialsFromEnvironment(self, environ):
152
 
        authorization = environ.get('HTTP_AUTHORIZATION')
153
 
        if authorization is None:
154
 
            return None
155
 
 
156
 
        method, auth = authorization.split(' ', 1)
157
 
        if method.lower() != 'basic':
158
 
            return None
159
 
 
160
 
        auth = auth.strip().decode('base64')
161
 
        username, password = auth.split(':', 1)
162
 
        return username, password
163
 
 
164
 
 
165
 
class OAuthMiddleware(AuthenticationMiddleware):
166
 
    """WSGI middleware that implements (part of) OAuth.
167
 
 
168
 
    This middleware only protects resources by making sure requests
169
 
    are signed with a valid consumer and access token. It does not
170
 
    help clients get request tokens or exchange request tokens for
171
 
    access tokens.
172
 
    """
173
 
 
174
 
    def __init__(self, application, authenticate_with, data_store=None,
175
 
                 realm="Restricted area", protect_path_pattern='.*'):
176
 
        """See `AuthenticationMiddleware.`
177
 
 
178
 
        :param data_store: An OAuthDataStore.
179
 
        """
180
 
        super(OAuthMiddleware, self).__init__(
181
 
            application, authenticate_with, realm, protect_path_pattern)
182
 
        self.data_store = data_store
183
 
 
184
 
    def getCredentialsFromEnvironment(self, environ):
185
 
        http_method = environ['REQUEST_METHOD']
186
 
 
187
 
        # Recreate the URL.
188
 
        url_scheme = environ['wsgi.url_scheme']
189
 
        hostname = environ['HTTP_HOST']
190
 
        path = environ['PATH_INFO']
191
 
        query_string = environ['QUERY_STRING']
192
 
        original_url = urlparse.urlunparse(
193
 
            (url_scheme, hostname, path, '', query_string, ''))
194
 
        headers = {'Authorization' : environ.get('HTTP_AUTHORIZATION', '')}
195
 
        request = OAuthRequest().from_request(
196
 
            http_method, original_url, headers=headers,
197
 
            query_string=query_string)
198
 
        if request is None:
199
 
            return None
200
 
        server = OAuthServer(self.data_store)
201
 
        server.add_signature_method(OAuthSignatureMethod_PLAINTEXT())
202
 
        try:
203
 
            consumer, token, parameters = server.verify_request(request)
204
 
        except OAuthError, e:
205
 
            return None
206
 
        return consumer, token, parameters