summaryrefslogtreecommitdiff
path: root/oauthlib/oauth2/draft25/grant_types.py
blob: 9773b8696a239b8072704c990b1b13ae5b8e20e2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
"""
oauthlib.oauth2.draft_25.grant_types
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
"""
from oauthlib.common import generate_token, add_params_to_uri
from oauthlib.uri_validate import is_absolute_uri
import json
import errors


class RequestValidator(object):

    @property
    def response_types(self):
        return (u'code', u'token')

    def validate_request(self, request, response_types=None):
        request.state = getattr(request, u'state', None)
        response_types = response_types or self.response_types or []

        if not request.client_id:
            raise errors.InvalidRequestError(state=request.state,
                    description=u'Missing client_id parameter.')

        if not request.response_type:
            raise errors.InvalidRequestError(state=request.state,
                    description=u'Missing response_type parameter.')

        if not self.validate_client(request.client_id):
            raise errors.UnauthorizedClientError(state=request.state)

        if not request.response_type in response_types:
            raise errors.UnsupportedResponseTypeError(state=request.state)

        self.validate_request_scopes(request)

        if getattr(request, u'redirect_uri', None):
            if not is_absolute_uri(request.redirect_uri):
                raise errors.InvalidRequestError(state=request.state,
                        description=u'Non absolute redirect URI. See RFC3986')

            if not self.validate_redirect_uri(request.client_id, request.redirect_uri):
                raise errors.AccessDeniedError(state=request.state)
        else:
            request.redirect_uri = self.get_default_redirect_uri(request.client_id)
            if not request.redirect_uri:
                raise errors.AccessDeniedError(state=request.state)

        return True

    def validate_request_scopes(self, request):
        request.state = getattr(request, u'state', None)
        if request.scopes:
            if not self.validate_scopes(request.client_id, request.scopes):
                raise errors.InvalidScopeError(state=request.state)
        else:
            request.scopes = self.get_default_scopes(request.client_id)

    def validate_client(self, client, *args, **kwargs):
        raise NotImplementedError('Subclasses must implement this method.')

    def validate_scopes(self, client, scopes):
        raise NotImplementedError('Subclasses must implement this method.')

    def validate_user(self, username, password, client=None):
        raise NotImplementedError('Subclasses must implement this method.')

    def validate_redirect_uri(self, client, redirect_uri):
        raise NotImplementedError('Subclasses must implement this method.')

    def get_default_redirect_uri(self, client):
        raise NotImplementedError('Subclasses must implement this method.')

    def get_default_scopes(self, client):
        raise NotImplementedError('Subclasses must implement this method.')


class GrantTypeBase(object):

    def create_authorization_response(self, request):
        raise NotImplementedError('Subclasses must implement this method.')

    def create_token_response(self, request, token_handler):
        raise NotImplementedError('Subclasses must implement this method.')


class AuthorizationCodeGrant(GrantTypeBase):

    @property
    def scopes(self):
        return ('default',)

    @property
    def error_uri(self):
        return u'/oauth_error'

    def __init__(self, request_validator=None):
        self.request_validator = request_validator or RequestValidator()

    def create_authorization_code(self, request):
        """Generates an authorization grant represented as a dictionary."""
        grant = {u'code': generate_token()}
        if hasattr(request, 'state') and request.state:
            grant[u'state'] = request.state
        return grant

    def save_authorization_code(self, client_id, grant):
        """Saves authorization codes for later use by the token endpoint."""
        raise NotImplementedError('Subclasses must implement this method.')

    def create_authorization_response(self, request, token_handler):
        try:
            self.request_validator.validate_request(request)

        except errors.OAuth2Error as e:
            request.redirect_uri = getattr(request, u'redirect_uri',
                    self.error_uri)
            return add_params_to_uri(request.redirect_uri, e.twotuples)

        grant = self.create_authorization_code(request)
        self.save_authorization_code(request.client_id, grant)
        return add_params_to_uri(request.redirect_uri, grant.items())

    def create_token_response(self, request, token_handler):
        """Validate the authorization code.

        The client MUST NOT use the authorization code more than once. If an
        authorization code is used more than once, the authorization server
        MUST deny the request and SHOULD revoke (when possible) all tokens
        previously issued based on that authorization code. The authorization
        code is bound to the client identifier and redirection URI.
        """
        try:
            self.validate_token_request(request)
        except errors.OAuth2Error as e:
            return e.json
        return json.dumps(token_handler(request, refresh_token=True))

    def validate_token_request(self, request):

        if getattr(request, u'grant_type', '') != u'authorization_code':
            raise errors.UnsupportedGrantTypeError()

        if not getattr(request, u'code', None):
            raise errors.InvalidRequestError(
                    description=u'Missing code parameter.')

        # TODO: document diff client & client_id, former is authenticated
        # outside spec, i.e. http basic
        if (not hasattr(request, 'client') or
            not self.request_validator.validate_client(request.client, request.grant_type)):
            raise errors.UnauthorizedClientError()

        if not self.request_validator.validate_code(request.client, request.code):
            raise errors.InvalidGrantError()

    # TODO: validate scopes


class ImplicitGrant(GrantTypeBase):
    """`Implicit Grant`_

    The implicit grant type is used to obtain access tokens (it does not
    support the issuance of refresh tokens) and is optimized for public
    clients known to operate a particular redirection URI.  These clients
    are typically implemented in a browser using a scripting language
    such as JavaScript.

    Unlike the authorization code grant type, in which the client makes
    separate requests for authorization and for an access token, the
    client receives the access token as the result of the authorization
    request.

    The implicit grant type does not include client authentication, and
    relies on the presence of the resource owner and the registration of
    the redirection URI.  Because the access token is encoded into the
    redirection URI, it may be exposed to the resource owner and other
    applications residing on the same device.

    See `Sections 10.3`_ and `10.16`_ for important security considerations
    when using the implicit grant.

    The client constructs the request URI by adding the following
    parameters to the query component of the authorization endpoint URI
    using the "application/x-www-form-urlencoded" format, per `Appendix B`_:

    response_type
            REQUIRED.  Value MUST be set to "token".

    client_id
            REQUIRED.  The client identifier as described in `Section 2.2`_.

    redirect_uri
            OPTIONAL.  As described in `Section 3.1.2`_.

    scope
            OPTIONAL.  The scope of the access request as described by
            `Section 3.3`_.

    state
            RECOMMENDED.  An opaque value used by the client to maintain
            state between the request and callback.  The authorization
            server includes this value when redirecting the user-agent back
            to the client.  The parameter SHOULD be used for preventing
            cross-site request forgery as described in `Section 10.12`_.

    The authorization server validates the request to ensure that all
    required parameters are present and valid.  The authorization server
    MUST verify that the redirection URI to which it will redirect the
    access token matches a redirection URI registered by the client as
    described in `Section 3.1.2`_.

    .. _`Implicit Grant`: http://tools.ietf.org/html/rfc6749#section-4.2
    .. _`10.16`: http://tools.ietf.org/html/rfc6749#section-10.16
    .. _`Section 2.2`: http://tools.ietf.org/html/rfc6749#section-2.2
    .. _`Section 3.1.2`: http://tools.ietf.org/html/rfc6749#section-3.1.2
    .. _`Section 3.3`: http://tools.ietf.org/html/rfc6749#section-3.3
    .. _`Section 10.3`: http://tools.ietf.org/html/rfc6749#section-10.3
    .. _`Appendix B`: http://tools.ietf.org/html/rfc6749#appendix-B
    """

    def __init__(self, request_validator=None):
        self.request_validator = request_validator or RequestValidator()

    def create_token_response(self, request, token_handler):
        """Return token or error embedded in the URI fragment.

        If the resource owner grants the access request, the authorization
        server issues an access token and delivers it to the client by adding
        the following parameters to the fragment component of the redirection
        URI using the "application/x-www-form-urlencoded" format, per
        `Appendix B`_:

        access_token
                REQUIRED.  The access token issued by the authorization server.

        token_type
                REQUIRED.  The type of the token issued as described in
                `Section 7.1`_.  Value is case insensitive.

        expires_in
                RECOMMENDED.  The lifetime in seconds of the access token.  For
                example, the value "3600" denotes that the access token will
                expire in one hour from the time the response was generated.
                If omitted, the authorization server SHOULD provide the
                expiration time via other means or document the default value.

        scope
                OPTIONAL, if identical to the scope requested by the client;
                otherwise, REQUIRED.  The scope of the access token as
                described by `Section 3.3`_.

        state
                REQUIRED if the "state" parameter was present in the client
                authorization request.  The exact value received from the
                client.

        The authorization server MUST NOT issue a refresh token.

        .. _`Appendix B`: http://tools.ietf.org/html/rfc6749#appendix-B
        .. _`Section 3.3`: http://tools.ietf.org/html/rfc6749#section-3.3
        .. _`Section 7.2`: http://tools.ietf.org/html/rfc6749#section-7.2
        """
        try:
            self.request_validator.validate_request(request)
        except errors.OAuth2Error as e:
            return add_params_to_uri(request.redirect_uri, e.twotuples,
                    fragment=True)
        token = token_handler(request, refresh_token=False)
        return add_params_to_uri(request.redirect_uri, token.items(),
                fragment=True)


class ResourceOwnerPasswordCredentialsGrant(GrantTypeBase):
    """`Resource Owner Password Credentials Grant`_

    The client makes a request to the token endpoint by adding the
    following parameters using the "application/x-www-form-urlencoded"
    format per Appendix B with a character encoding of UTF-8 in the HTTP
    request entity-body:

    grant_type
            REQUIRED.  Value MUST be set to "password".

    username
            REQUIRED.  The resource owner username.

    password
            REQUIRED.  The resource owner password.

    scope
            OPTIONAL.  The scope of the access request as described by
            `Section 3.3`_.

    If the client type is confidential or the client was issued client
    credentials (or assigned other authentication requirements), the
    client MUST authenticate with the authorization server as described
    in `Section 3.2.1`_.

    For example, the client makes the following HTTP request using
    transport-layer security (with extra line breaks for display purposes
    only):

        POST /token HTTP/1.1
        Host: server.example.com
        Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
        Content-Type: application/x-www-form-urlencoded

        grant_type=password&username=johndoe&password=A3ddj3w

    The authorization server MUST:

    o  require client authentication for confidential clients or for any
        client that was issued client credentials (or with other
        authentication requirements),

    o  authenticate the client if client authentication is included, and

    o  validate the resource owner password credentials using its
        existing password validation algorithm.

    Since this access token request utilizes the resource owner's
    password, the authorization server MUST protect the endpoint against
    brute force attacks (e.g., using rate-limitation or generating
    alerts).

    .. _`Resource Owner Password Credentials Grant`: http://tools.ietf.org/html/rfc6749#section-4.3
    .. _`Section 3.3`: http://tools.ietf.org/html/rfc6749#section-3.3
    .. _`Section 3.2.1`: http://tools.ietf.org/html/rfc6749#section-3.2.1
    """

    def __init__(self, request_validator=None):
        self.request_validator = request_validator or RequestValidator()

    def create_token_response(self, request, token_handler,
            require_authentication=True):
        """Return token or error in json format.

        If the access token request is valid and authorized, the
        authorization server issues an access token and optional refresh
        token as described in `Section 5.1`_.  If the request failed client
        authentication or is invalid, the authorization server returns an
        error response as described in `Section 5.2`_.

        .. _`Section 5.1`: http://tools.ietf.org/html/rfc6749#section-5.1
        .. _`Section 5.2`: http://tools.ietf.org/html/rfc6749#section-5.2
        """
        try:
            if require_authentication:
                self.request_validator.authenticate_client(request)
            self.validate_token_request(request)
        except errors.OAuth2Error as e:
            return None, {}, e.json
        return None, {}, json.dumps(token_handler(request, refresh_token=True))

    def validate_token_request(self, request):
        for param in ('grant_type', 'username', 'password'):
            if not getattr(request, param):
                raise errors.InvalidRequestError(
                        'Request is missing %s parameter.' % param)

        # This error should rarely (if ever) occur if requests are routed to
        # grant type handlers based on the grant_type parameter.
        if not request.grant_type == 'password':
            raise errors.UnsupportedGrantTypeError()

        # request.client is populated during client authentication
        client = request.client if getattr(request, 'client') else None
        if not self.request_validator.validate_user(request.username,
                request.password, client=client):
            raise errors.InvalidGrantError('Invalid credentials given.')

        self.request_validator.validate_request_scopes(request)


class ClientCredentialsGrant(GrantTypeBase):
    """`Client Credentials Grant`_

    The client can request an access token using only its client
    credentials (or other supported means of authentication) when the
    client is requesting access to the protected resources under its
    control, or those of another resource owner that have been previously
    arranged with the authorization server (the method of which is beyond
    the scope of this specification).

    The client credentials grant type MUST only be used by confidential
    clients.

        +---------+                                  +---------------+
        |         |                                  |               |
        |         |>--(A)- Client Authentication --->| Authorization |
        | Client  |                                  |     Server    |
        |         |<--(B)---- Access Token ---------<|               |
        |         |                                  |               |
        +---------+                                  +---------------+

                        Figure 6: Client Credentials Flow

    The flow illustrated in Figure 6 includes the following steps:

    (A)  The client authenticates with the authorization server and
            requests an access token from the token endpoint.

    (B)  The authorization server authenticates the client, and if valid,
            issues an access token.

    .. _`Client Credentials Grant`: http://tools.ietf.org/html/rfc6749#section-4.4
    """

    def __init__(self, request_validator=None):
        self.request_validator = request_validator or RequestValidator()

    def create_token_response(self, request, token_handler):
        """Return token or error in JSON format.

        If the access token request is valid and authorized, the
        authorization server issues an access token as described in
        `Section 5.1`_.  A refresh token SHOULD NOT be included.  If the request
        failed client authentication or is invalid, the authorization server
        returns an error response as described in `Section 5.2`_.

        .. _`Section 5.1`: http://tools.ietf.org/html/rfc6749#section-5.1
        .. _`Section 5.2`: http://tools.ietf.org/html/rfc6749#section-5.2
        """
        try:
            self.request_validator.authenticate_client(request)
            self.validate_token_request(request)
        except errors.OAuth2Error as e:
            return None, {}, e.json
        return None, {}, json.dumps(token_handler(request, refresh_token=True))

    def validate_token_request(self, request):
        if not getattr(request, 'grant_type'):
            raise errors.InvalidRequestError('Request is issing grant type.')

        if not request.grant_type == 'client_credentials':
            raise errors.UnsupportedGrantTypeError()

        self.request_validator.validate_request_scopes(request)