Statistics
| Revision:

gvsig-scripting / org.gvsig.scripting / trunk / org.gvsig.scripting / org.gvsig.scripting.app / org.gvsig.scripting.app.mainplugin / src / main / resources-plugin / scripting / lib / oauthlib / oauth2 / rfc6749 / parameters.py @ 564

History | View | Annotate | Download (15.5 KB)

1
# -*- coding: utf-8 -*-
2
"""
3
oauthlib.oauth2.rfc6749.parameters
4
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5

6
This module contains methods related to `Section 4`_ of the OAuth 2 RFC.
7

8
.. _`Section 4`: http://tools.ietf.org/html/rfc6749#section-4
9
"""
10
from __future__ import absolute_import, unicode_literals
11

    
12
import json
13
import os
14
import time
15
try:
16
    import urlparse
17
except ImportError:
18
    import urllib.parse as urlparse
19
from oauthlib.common import add_params_to_uri, add_params_to_qs, unicode_type
20
from oauthlib.signals import scope_changed
21
from .errors import raise_from_error, MissingTokenError, MissingTokenTypeError
22
from .errors import MismatchingStateError, MissingCodeError
23
from .errors import InsecureTransportError
24
from .tokens import OAuth2Token
25
from .utils import list_to_scope, scope_to_list, is_secure_transport
26

    
27

    
28
def prepare_grant_uri(uri, client_id, response_type, redirect_uri=None,
29
                      scope=None, state=None, **kwargs):
30
    """Prepare the authorization grant request URI.
31

32
    The client constructs the request URI by adding the following
33
    parameters to the query component of the authorization endpoint URI
34
    using the ``application/x-www-form-urlencoded`` format as defined by
35
    [`W3C.REC-html401-19991224`_]:
36

37
    :param response_type: To indicate which OAuth 2 grant/flow is required,
38
                          "code" and "token".
39
    :param client_id: The client identifier as described in `Section 2.2`_.
40
    :param redirect_uri: The client provided URI to redirect back to after
41
                         authorization as described in `Section 3.1.2`_.
42
    :param scope: The scope of the access request as described by
43
                  `Section 3.3`_.
44

45
    :param state: An opaque value used by the client to maintain
46
                  state between the request and callback.  The authorization
47
                  server includes this value when redirecting the user-agent
48
                  back to the client.  The parameter SHOULD be used for
49
                  preventing cross-site request forgery as described in
50
                  `Section 10.12`_.
51
    :param kwargs: Extra arguments to embed in the grant/authorization URL.
52

53
    An example of an authorization code grant authorization URL:
54

55
    .. code-block:: http
56

57
        GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz
58
            &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
59
        Host: server.example.com
60

61
    .. _`W3C.REC-html401-19991224`: http://tools.ietf.org/html/rfc6749#ref-W3C.REC-html401-19991224
62
    .. _`Section 2.2`: http://tools.ietf.org/html/rfc6749#section-2.2
63
    .. _`Section 3.1.2`: http://tools.ietf.org/html/rfc6749#section-3.1.2
64
    .. _`Section 3.3`: http://tools.ietf.org/html/rfc6749#section-3.3
65
    .. _`section 10.12`: http://tools.ietf.org/html/rfc6749#section-10.12
66
    """
67
    if not is_secure_transport(uri):
68
        raise InsecureTransportError()
69

    
70
    params = [(('response_type', response_type)),
71
              (('client_id', client_id))]
72

    
73
    if redirect_uri:
74
        params.append(('redirect_uri', redirect_uri))
75
    if scope:
76
        params.append(('scope', list_to_scope(scope)))
77
    if state:
78
        params.append(('state', state))
79

    
80
    for k in kwargs:
81
        if kwargs[k]:
82
            params.append((unicode_type(k), kwargs[k]))
83

    
84
    return add_params_to_uri(uri, params)
85

    
86

    
87
def prepare_token_request(grant_type, body='', **kwargs):
88
    """Prepare the access token request.
89

90
    The client makes a request to the token endpoint by adding the
91
    following parameters using the ``application/x-www-form-urlencoded``
92
    format in the HTTP request entity-body:
93

94
    :param grant_type: To indicate grant type being used, i.e. "password",
95
            "authorization_code" or "client_credentials".
96
    :param body: Existing request body to embed parameters in.
97
    :param code: If using authorization code grant, pass the previously
98
                 obtained authorization code as the ``code`` argument.
99
    :param redirect_uri: If the "redirect_uri" parameter was included in the
100
                         authorization request as described in
101
                         `Section 4.1.1`_, and their values MUST be identical.
102
    :param kwargs: Extra arguments to embed in the request body.
103

104
    An example of an authorization code token request body:
105

106
    .. code-block:: http
107

108
        grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA
109
        &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
110

111
    .. _`Section 4.1.1`: http://tools.ietf.org/html/rfc6749#section-4.1.1
112
    """
113
    params = [('grant_type', grant_type)]
114

    
115
    if 'scope' in kwargs:
116
        kwargs['scope'] = list_to_scope(kwargs['scope'])
117

    
118
    for k in kwargs:
119
        if kwargs[k]:
120
            params.append((unicode_type(k), kwargs[k]))
121

    
122
    return add_params_to_qs(body, params)
123

    
124

    
125
def prepare_token_revocation_request(url, token, token_type_hint="access_token",
126
        callback=None, body='', **kwargs):
127
    """Prepare a token revocation request.
128

129
    The client constructs the request by including the following parameters
130
    using the "application/x-www-form-urlencoded" format in the HTTP request
131
    entity-body:
132

133
    token   REQUIRED.  The token that the client wants to get revoked.
134

135
    token_type_hint  OPTIONAL.  A hint about the type of the token submitted
136
    for revocation.  Clients MAY pass this parameter in order to help the
137
    authorization server to optimize the token lookup.  If the server is unable
138
    to locate the token using the given hint, it MUST extend its search across
139
    all of its supported token types.  An authorization server MAY ignore this
140
    parameter, particularly if it is able to detect the token type
141
    automatically.  This specification defines two such values:
142

143
        * access_token: An access token as defined in [RFC6749],
144
             `Section 1.4`_
145

146
        * refresh_token: A refresh token as defined in [RFC6749],
147
             `Section 1.5`_
148

149
        Specific implementations, profiles, and extensions of this
150
        specification MAY define other values for this parameter using the
151
        registry defined in `Section 4.1.2`_.
152

153
    .. _`Section 1.4`: http://tools.ietf.org/html/rfc6749#section-1.4
154
    .. _`Section 1.5`: http://tools.ietf.org/html/rfc6749#section-1.5
155
    .. _`Section 4.1.2`: http://tools.ietf.org/html/rfc7009#section-4.1.2
156

157
    """
158
    if not is_secure_transport(url):
159
        raise InsecureTransportError()
160

    
161
    params = [('token', token)]
162

    
163
    if token_type_hint:
164
        params.append(('token_type_hint', token_type_hint))
165

    
166
    for k in kwargs:
167
        if kwargs[k]:
168
            params.append((unicode_type(k), kwargs[k]))
169

    
170
    headers = {'Content-Type': 'application/x-www-form-urlencoded'}
171

    
172
    if callback:
173
        params.append(('callback', callback))
174
        return add_params_to_uri(url, params), headers, body
175
    else:
176
        return url, headers, add_params_to_qs(body, params)
177

    
178

    
179
def parse_authorization_code_response(uri, state=None):
180
    """Parse authorization grant response URI into a dict.
181

182
    If the resource owner grants the access request, the authorization
183
    server issues an authorization code and delivers it to the client by
184
    adding the following parameters to the query component of the
185
    redirection URI using the ``application/x-www-form-urlencoded`` format:
186

187
    **code**
188
            REQUIRED.  The authorization code generated by the
189
            authorization server.  The authorization code MUST expire
190
            shortly after it is issued to mitigate the risk of leaks.  A
191
            maximum authorization code lifetime of 10 minutes is
192
            RECOMMENDED.  The client MUST NOT use the authorization code
193
            more than once.  If an authorization code is used more than
194
            once, the authorization server MUST deny the request and SHOULD
195
            revoke (when possible) all tokens previously issued based on
196
            that authorization code.  The authorization code is bound to
197
            the client identifier and redirection URI.
198

199
    **state**
200
            REQUIRED if the "state" parameter was present in the client
201
            authorization request.  The exact value received from the
202
            client.
203

204
    :param uri: The full redirect URL back to the client.
205
    :param state: The state parameter from the authorization request.
206

207
    For example, the authorization server redirects the user-agent by
208
    sending the following HTTP response:
209

210
    .. code-block:: http
211

212
        HTTP/1.1 302 Found
213
        Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA
214
                &state=xyz
215

216
    """
217
    if not is_secure_transport(uri):
218
        raise InsecureTransportError()
219

    
220
    query = urlparse.urlparse(uri).query
221
    params = dict(urlparse.parse_qsl(query))
222

    
223
    if not 'code' in params:
224
        raise MissingCodeError("Missing code parameter in response.")
225

    
226
    if state and params.get('state', None) != state:
227
        raise MismatchingStateError()
228

    
229
    return params
230

    
231

    
232
def parse_implicit_response(uri, state=None, scope=None):
233
    """Parse the implicit token response URI into a dict.
234

235
    If the resource owner grants the access request, the authorization
236
    server issues an access token and delivers it to the client by adding
237
    the following parameters to the fragment component of the redirection
238
    URI using the ``application/x-www-form-urlencoded`` format:
239

240
    **access_token**
241
            REQUIRED.  The access token issued by the authorization server.
242

243
    **token_type**
244
            REQUIRED.  The type of the token issued as described in
245
            Section 7.1.  Value is case insensitive.
246

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

254
    **scope**
255
            OPTIONAL, if identical to the scope requested by the client,
256
            otherwise REQUIRED.  The scope of the access token as described
257
            by Section 3.3.
258

259
    **state**
260
            REQUIRED if the "state" parameter was present in the client
261
            authorization request.  The exact value received from the
262
            client.
263

264
    Similar to the authorization code response, but with a full token provided
265
    in the URL fragment:
266

267
    .. code-block:: http
268

269
        HTTP/1.1 302 Found
270
        Location: http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA
271
                &state=xyz&token_type=example&expires_in=3600
272
    """
273
    if not is_secure_transport(uri):
274
        raise InsecureTransportError()
275

    
276
    fragment = urlparse.urlparse(uri).fragment
277
    params = dict(urlparse.parse_qsl(fragment, keep_blank_values=True))
278

    
279
    if 'scope' in params:
280
        params['scope'] = scope_to_list(params['scope'])
281

    
282
    if 'expires_in' in params:
283
        params['expires_at'] = time.time() + int(params['expires_in'])
284

    
285
    if state and params.get('state', None) != state:
286
        raise ValueError("Mismatching or missing state in params.")
287

    
288
    params = OAuth2Token(params, old_scope=scope)
289
    validate_token_parameters(params)
290
    return params
291

    
292

    
293
def parse_token_response(body, scope=None):
294
    """Parse the JSON token response body into a dict.
295

296
    The authorization server issues an access token and optional refresh
297
    token, and constructs the response by adding the following parameters
298
    to the entity body of the HTTP response with a 200 (OK) status code:
299

300
    access_token
301
            REQUIRED.  The access token issued by the authorization server.
302
    token_type
303
            REQUIRED.  The type of the token issued as described in
304
            `Section 7.1`_.  Value is case insensitive.
305
    expires_in
306
            RECOMMENDED.  The lifetime in seconds of the access token.  For
307
            example, the value "3600" denotes that the access token will
308
            expire in one hour from the time the response was generated.
309
            If omitted, the authorization server SHOULD provide the
310
            expiration time via other means or document the default value.
311
    refresh_token
312
            OPTIONAL.  The refresh token which can be used to obtain new
313
            access tokens using the same authorization grant as described
314
            in `Section 6`_.
315
    scope
316
            OPTIONAL, if identical to the scope requested by the client,
317
            otherwise REQUIRED.  The scope of the access token as described
318
            by `Section 3.3`_.
319

320
    The parameters are included in the entity body of the HTTP response
321
    using the "application/json" media type as defined by [`RFC4627`_].  The
322
    parameters are serialized into a JSON structure by adding each
323
    parameter at the highest structure level.  Parameter names and string
324
    values are included as JSON strings.  Numerical values are included
325
    as JSON numbers.  The order of parameters does not matter and can
326
    vary.
327

328
    :param body: The full json encoded response body.
329
    :param scope: The scope requested during authorization.
330

331
    For example:
332

333
    .. code-block:: http
334

335
        HTTP/1.1 200 OK
336
        Content-Type: application/json
337
        Cache-Control: no-store
338
        Pragma: no-cache
339

340
        {
341
            "access_token":"2YotnFZFEjr1zCsicMWpAA",
342
            "token_type":"example",
343
            "expires_in":3600,
344
            "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
345
            "example_parameter":"example_value"
346
        }
347

348
    .. _`Section 7.1`: http://tools.ietf.org/html/rfc6749#section-7.1
349
    .. _`Section 6`: http://tools.ietf.org/html/rfc6749#section-6
350
    .. _`Section 3.3`: http://tools.ietf.org/html/rfc6749#section-3.3
351
    .. _`RFC4627`: http://tools.ietf.org/html/rfc4627
352
    """
353
    try:
354
        params = json.loads(body)
355
    except ValueError:
356

    
357
        # Fall back to URL-encoded string, to support old implementations,
358
        # including (at time of writing) Facebook. See:
359
        #   https://github.com/idan/oauthlib/issues/267
360

    
361
        params = dict(urlparse.parse_qsl(body))
362
        for key in ('expires_in', 'expires'):
363
            if key in params:  # cast a couple things to int
364
                params[key] = int(params[key])
365

    
366
    if 'scope' in params:
367
        params['scope'] = scope_to_list(params['scope'])
368

    
369
    if 'expires' in params:
370
        params['expires_in'] = params.pop('expires')
371

    
372
    if 'expires_in' in params:
373
        params['expires_at'] = time.time() + int(params['expires_in'])
374

    
375
    params = OAuth2Token(params, old_scope=scope)
376
    validate_token_parameters(params)
377
    return params
378

    
379

    
380
def validate_token_parameters(params):
381
    """Ensures token precence, token type, expiration and scope in params."""
382
    if 'error' in params:
383
        raise_from_error(params.get('error'), params)
384

    
385
    if not 'access_token' in params:
386
        raise MissingTokenError(description="Missing access token parameter.")
387

    
388
    if not 'token_type' in params:
389
        if os.environ.get('OAUTHLIB_STRICT_TOKEN_TYPE'):
390
            raise MissingTokenTypeError()
391

    
392
    # If the issued access token scope is different from the one requested by
393
    # the client, the authorization server MUST include the "scope" response
394
    # parameter to inform the client of the actual scope granted.
395
    # http://tools.ietf.org/html/rfc6749#section-3.3
396
    if params.scope_changed:
397
        message = 'Scope has changed from "{old}" to "{new}".'.format(
398
            old=params.old_scope, new=params.scope,
399
        )
400
        scope_changed.send(message=message, old=params.old_scopes, new=params.scopes)
401
        if not os.environ.get('OAUTHLIB_RELAX_TOKEN_SCOPE', None):
402
            w = Warning(message)
403
            w.token = params
404
            w.old_scope = params.old_scopes
405
            w.new_scope = params.scopes
406
            raise w