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 / clients / base.py @ 564

History | View | Annotate | Download (19.9 KB)

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

6
This module is an implementation of various logic needed
7
for consuming OAuth 2.0 RFC6749.
8
"""
9
from __future__ import absolute_import, unicode_literals
10

    
11
import time
12

    
13
from oauthlib.common import generate_token
14
from oauthlib.oauth2.rfc6749 import tokens
15
from oauthlib.oauth2.rfc6749.parameters import parse_token_response
16
from oauthlib.oauth2.rfc6749.parameters import prepare_token_request
17
from oauthlib.oauth2.rfc6749.parameters import prepare_token_revocation_request
18
from oauthlib.oauth2.rfc6749.errors import TokenExpiredError
19
from oauthlib.oauth2.rfc6749.errors import InsecureTransportError
20
from oauthlib.oauth2.rfc6749.utils import is_secure_transport
21

    
22

    
23
AUTH_HEADER = 'auth_header'
24
URI_QUERY = 'query'
25
BODY = 'body'
26

    
27
FORM_ENC_HEADERS = {
28
    'Content-Type': 'application/x-www-form-urlencoded'
29
}
30

    
31
class Client(object):
32

    
33
    """Base OAuth2 client responsible for access token management.
34

35
    This class also acts as a generic interface providing methods common to all
36
    client types such as ``prepare_authorization_request`` and
37
    ``prepare_token_revocation_request``. The ``prepare_x_request`` methods are
38
    the recommended way of interacting with clients (as opposed to the abstract
39
    prepare uri/body/etc methods). They are recommended over the older set
40
    because they are easier to use (more consistent) and add a few additional
41
    security checks, such as HTTPS and state checking.
42

43
    Some of these methods require further implementation only provided by the
44
    specific purpose clients such as
45
    :py:class:`oauthlib.oauth2.MobileApplicationClient` and thus you should always
46
    seek to use the client class matching the OAuth workflow you need. For
47
    Python, this is usually :py:class:`oauthlib.oauth2.WebApplicationClient`.
48

49
    """
50

    
51
    def __init__(self, client_id,
52
                 default_token_placement=AUTH_HEADER,
53
                 token_type='Bearer',
54
                 access_token=None,
55
                 refresh_token=None,
56
                 mac_key=None,
57
                 mac_algorithm=None,
58
                 token=None,
59
                 scope=None,
60
                 state=None,
61
                 redirect_url=None,
62
                 state_generator=generate_token,
63
                 **kwargs):
64
        """Initialize a client with commonly used attributes.
65

66
        :param client_id: Client identifier given by the OAuth provider upon
67
        registration.
68

69
        :param default_token_placement: Tokens can be supplied in the Authorization
70
        header (default), the URL query component (``query``) or the request
71
        body (``body``).
72

73
        :param token_type: OAuth 2 token type. Defaults to Bearer. Change this
74
        if you specify the ``access_token`` parameter and know it is of a
75
        different token type, such as a MAC, JWT or SAML token. Can
76
        also be supplied as ``token_type`` inside the ``token`` dict parameter.
77

78
        :param access_token: An access token (string) used to authenticate
79
        requests to protected resources. Can also be supplied inside the
80
        ``token`` dict parameter.
81

82
        :param refresh_token: A refresh token (string) used to refresh expired
83
        tokens. Can also be supplide inside the ``token`` dict parameter.
84

85
        :param mac_key: Encryption key used with MAC tokens.
86

87
        :param mac_algorithm:  Hashing algorithm for MAC tokens.
88

89
        :param token: A dict of token attributes such as ``access_token``,
90
        ``token_type`` and ``expires_at``.
91

92
        :param scope: A list of default scopes to request authorization for.
93

94
        :param state: A CSRF protection string used during authorization.
95

96
        :param redirect_url: The redirection endpoint on the client side to which
97
        the user returns after authorization.
98

99
        :param state_generator: A no argument state generation callable. Defaults
100
        to :py:meth:`oauthlib.common.generate_token`.
101
        """
102

    
103
        self.client_id = client_id
104
        self.default_token_placement = default_token_placement
105
        self.token_type = token_type
106
        self.access_token = access_token
107
        self.refresh_token = refresh_token
108
        self.mac_key = mac_key
109
        self.mac_algorithm = mac_algorithm
110
        self.token = token or {}
111
        self.scope = scope
112
        self.state_generator = state_generator
113
        self.state = state
114
        self.redirect_url = redirect_url
115
        self._expires_at = None
116
        self._populate_attributes(self.token)
117

    
118
    @property
119
    def token_types(self):
120
        """Supported token types and their respective methods
121

122
        Additional tokens can be supported by extending this dictionary.
123

124
        The Bearer token spec is stable and safe to use.
125

126
        The MAC token spec is not yet stable and support for MAC tokens
127
        is experimental and currently matching version 00 of the spec.
128
        """
129
        return {
130
            'Bearer': self._add_bearer_token,
131
            'MAC': self._add_mac_token
132
        }
133

    
134
    def prepare_request_uri(self, *args, **kwargs):
135
        """Abstract method used to create request URIs."""
136
        raise NotImplementedError("Must be implemented by inheriting classes.")
137

    
138
    def prepare_request_body(self, *args, **kwargs):
139
        """Abstract method used to create request bodies."""
140
        raise NotImplementedError("Must be implemented by inheriting classes.")
141

    
142
    def parse_request_uri_response(self, *args, **kwargs):
143
        """Abstract method used to parse redirection responses."""
144

    
145
    def add_token(self, uri, http_method='GET', body=None, headers=None,
146
                  token_placement=None, **kwargs):
147
        """Add token to the request uri, body or authorization header.
148

149
        The access token type provides the client with the information
150
        required to successfully utilize the access token to make a protected
151
        resource request (along with type-specific attributes).  The client
152
        MUST NOT use an access token if it does not understand the token
153
        type.
154

155
        For example, the "bearer" token type defined in
156
        [`I-D.ietf-oauth-v2-bearer`_] is utilized by simply including the access
157
        token string in the request:
158

159
        .. code-block:: http
160

161
            GET /resource/1 HTTP/1.1
162
            Host: example.com
163
            Authorization: Bearer mF_9.B5f-4.1JqM
164

165
        while the "mac" token type defined in [`I-D.ietf-oauth-v2-http-mac`_] is
166
        utilized by issuing a MAC key together with the access token which is
167
        used to sign certain components of the HTTP requests:
168

169
        .. code-block:: http
170

171
            GET /resource/1 HTTP/1.1
172
            Host: example.com
173
            Authorization: MAC id="h480djs93hd8",
174
                                nonce="274312:dj83hs9s",
175
                                mac="kDZvddkndxvhGRXZhvuDjEWhGeE="
176

177
        .. _`I-D.ietf-oauth-v2-bearer`: http://tools.ietf.org/html/rfc6749#section-12.2
178
        .. _`I-D.ietf-oauth-v2-http-mac`: http://tools.ietf.org/html/rfc6749#section-12.2
179
        """
180
        if not is_secure_transport(uri):
181
            raise InsecureTransportError()
182

    
183
        token_placement = token_placement or self.default_token_placement
184

    
185
        case_insensitive_token_types = dict(
186
            (k.lower(), v) for k, v in self.token_types.items())
187
        if not self.token_type.lower() in case_insensitive_token_types:
188
            raise ValueError("Unsupported token type: %s" % self.token_type)
189

    
190
        if not self.access_token:
191
            raise ValueError("Missing access token.")
192

    
193
        if self._expires_at and self._expires_at < time.time():
194
            raise TokenExpiredError()
195

    
196
        return case_insensitive_token_types[self.token_type.lower()](uri, http_method, body,
197
                                                                     headers, token_placement, **kwargs)
198

    
199
    def prepare_authorization_request(self, authorization_url, state=None,
200
            redirect_url=None, scope=None, **kwargs):
201
        """Prepare the authorization request.
202

203
        This is the first step in many OAuth flows in which the user is
204
        redirected to a certain authorization URL. This method adds
205
        required parameters to the authorization URL.
206

207
        :param authorization_url: Provider authorization endpoint URL.
208

209
        :param state: CSRF protection string. Will be automatically created if
210
        not provided. The generated state is available via the ``state``
211
        attribute. Clients should verify that the state is unchanged and
212
        present in the authorization response. This verification is done
213
        automatically if using the ``authorization_response`` parameter
214
        with ``prepare_token_request``.
215

216
        :param redirect_url: Redirect URL to which the user will be returned
217
        after authorization. Must be provided unless previously setup with
218
        the provider. If provided then it must also be provided in the
219
        token request.
220

221
        :param kwargs: Additional parameters to included in the request.
222

223
        :returns: The prepared request tuple with (url, headers, body).
224
        """
225
        if not is_secure_transport(authorization_url):
226
            raise InsecureTransportError()
227

    
228
        self.state = state or self.state_generator()
229
        self.redirect_url = redirect_url or self.redirect_url
230
        self.scope = scope or self.scope
231
        auth_url = self.prepare_request_uri(
232
                authorization_url, redirect_uri=self.redirect_url,
233
                scope=self.scope, state=self.state, **kwargs)
234
        return auth_url, FORM_ENC_HEADERS, ''
235

    
236
    def prepare_token_request(self, token_url, authorization_response=None,
237
            redirect_url=None, state=None, body='', **kwargs):
238
        """Prepare a token creation request.
239

240
        Note that these requests usually require client authentication, either
241
        by including client_id or a set of provider specific authentication
242
        credentials.
243

244
        :param token_url: Provider token creation endpoint URL.
245

246
        :param authorization_response: The full redirection URL string, i.e.
247
        the location to which the user was redirected after successfull
248
        authorization. Used to mine credentials needed to obtain a token
249
        in this step, such as authorization code.
250

251
        :param redirect_url: The redirect_url supplied with the authorization
252
        request (if there was one).
253

254
        :param body: Request body (URL encoded string).
255

256
        :param kwargs: Additional parameters to included in the request.
257

258
        :returns: The prepared request tuple with (url, headers, body).
259
        """
260
        if not is_secure_transport(token_url):
261
            raise InsecureTransportError()
262

    
263
        state = state or self.state
264
        if authorization_response:
265
            self.parse_request_uri_response(
266
                    authorization_response, state=state)
267
        self.redirect_url = redirect_url or self.redirect_url
268
        body = self.prepare_request_body(body=body,
269
                redirect_uri=self.redirect_url, **kwargs)
270

    
271
        return token_url, FORM_ENC_HEADERS, body
272

    
273
    def prepare_refresh_token_request(self, token_url, refresh_token=None,
274
            body='', scope=None, **kwargs):
275
        """Prepare an access token refresh request.
276

277
        Expired access tokens can be replaced by new access tokens without
278
        going through the OAuth dance if the client obtained a refresh token.
279
        This refresh token and authentication credentials can be used to
280
        obtain a new access token, and possibly a new refresh token.
281

282
        :param token_url: Provider token refresh endpoint URL.
283

284
        :param refresh_token: Refresh token string.
285

286
        :param body: Request body (URL encoded string).
287

288
        :param scope: List of scopes to request. Must be equal to
289
        or a subset of the scopes granted when obtaining the refresh
290
        token.
291

292
        :param kwargs: Additional parameters to included in the request.
293

294
        :returns: The prepared request tuple with (url, headers, body).
295
        """
296
        if not is_secure_transport(token_url):
297
            raise InsecureTransportError()
298

    
299
        self.scope = scope or self.scope
300
        body = self.prepare_refresh_body(body=body,
301
                refresh_token=refresh_token, scope=self.scope, **kwargs)
302
        return token_url, FORM_ENC_HEADERS, body
303

    
304
    def prepare_token_revocation_request(self, revocation_url, token,
305
            token_type_hint="access_token", body='', callback=None, **kwargs):
306
        """Prepare a token revocation request.
307

308
        :param revocation_url: Provider token revocation endpoint URL.
309

310
        :param token: The access or refresh token to be revoked (string).
311

312
        :param token_type_hint: ``"access_token"`` (default) or
313
        ``"refresh_token"``. This is optional and if you wish to not pass it you
314
        must provide ``token_type_hint=None``.
315

316
        :param callback: A jsonp callback such as ``package.callback`` to be invoked
317
        upon receiving the response. Not that it should not include a () suffix.
318

319
        :param kwargs: Additional parameters to included in the request.
320

321
        :returns: The prepared request tuple with (url, headers, body).
322

323
        Note that JSONP request may use GET requests as the parameters will
324
        be added to the request URL query as opposed to the request body.
325

326
        An example of a revocation request
327

328
        .. code-block: http
329

330
            POST /revoke HTTP/1.1
331
            Host: server.example.com
332
            Content-Type: application/x-www-form-urlencoded
333
            Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
334

335
            token=45ghiukldjahdnhzdauz&token_type_hint=refresh_token
336

337
        An example of a jsonp revocation request
338

339
        .. code-block: http
340

341
            GET /revoke?token=agabcdefddddafdd&callback=package.myCallback HTTP/1.1
342
            Host: server.example.com
343
            Content-Type: application/x-www-form-urlencoded
344
            Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
345

346
        and an error response
347

348
        .. code-block: http
349

350
        package.myCallback({"error":"unsupported_token_type"});
351

352
        Note that these requests usually require client credentials, client_id in
353
        the case for public clients and provider specific authentication
354
        credentials for confidential clients.
355
        """
356
        if not is_secure_transport(revocation_url):
357
            raise InsecureTransportError()
358

    
359
        return prepare_token_revocation_request(revocation_url, token,
360
                token_type_hint=token_type_hint, body=body, callback=callback,
361
                **kwargs)
362

    
363
    def parse_request_body_response(self, body, scope=None, **kwargs):
364
        """Parse the JSON response body.
365

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

372
        :param body: The response body from the token request.
373
        :param scope: Scopes originally requested.
374
        :return: Dictionary of token parameters.
375
        :raises: Warning if scope has changed. OAuth2Error if response is invalid.
376

377
        These response are json encoded and could easily be parsed without
378
        the assistance of OAuthLib. However, there are a few subtle issues
379
        to be aware of regarding the response which are helpfully addressed
380
        through the raising of various errors.
381

382
        A successful response should always contain
383

384
        **access_token**
385
                The access token issued by the authorization server. Often
386
                a random string.
387

388
        **token_type**
389
            The type of the token issued as described in `Section 7.1`_.
390
            Commonly ``Bearer``.
391

392
        While it is not mandated it is recommended that the provider include
393

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

401
        **scope**
402
            Providers may supply this in all responses but are required to only
403
            if it has changed since the authorization request.
404

405
        .. _`Section 5.1`: http://tools.ietf.org/html/rfc6749#section-5.1
406
        .. _`Section 5.2`: http://tools.ietf.org/html/rfc6749#section-5.2
407
        .. _`Section 7.1`: http://tools.ietf.org/html/rfc6749#section-7.1
408
        """
409
        self.token = parse_token_response(body, scope=scope)
410
        self._populate_attributes(self.token)
411
        return self.token
412

    
413
    def prepare_refresh_body(self, body='', refresh_token=None, scope=None, **kwargs):
414
        """Prepare an access token request, using a refresh token.
415

416
        If the authorization server issued a refresh token to the client, the
417
        client makes a refresh request to the token endpoint by adding the
418
        following parameters using the "application/x-www-form-urlencoded"
419
        format in the HTTP request entity-body:
420

421
        grant_type
422
                REQUIRED.  Value MUST be set to "refresh_token".
423
        refresh_token
424
                REQUIRED.  The refresh token issued to the client.
425
        scope
426
                OPTIONAL.  The scope of the access request as described by
427
                Section 3.3.  The requested scope MUST NOT include any scope
428
                not originally granted by the resource owner, and if omitted is
429
                treated as equal to the scope originally granted by the
430
                resource owner.
431
        """
432
        refresh_token = refresh_token or self.refresh_token
433
        return prepare_token_request('refresh_token', body=body, scope=scope,
434
                                     refresh_token=refresh_token, **kwargs)
435

    
436
    def _add_bearer_token(self, uri, http_method='GET', body=None,
437
                          headers=None, token_placement=None):
438
        """Add a bearer token to the request uri, body or authorization header."""
439
        if token_placement == AUTH_HEADER:
440
            headers = tokens.prepare_bearer_headers(self.access_token, headers)
441

    
442
        elif token_placement == URI_QUERY:
443
            uri = tokens.prepare_bearer_uri(self.access_token, uri)
444

    
445
        elif token_placement == BODY:
446
            body = tokens.prepare_bearer_body(self.access_token, body)
447

    
448
        else:
449
            raise ValueError("Invalid token placement.")
450
        return uri, headers, body
451

    
452
    def _add_mac_token(self, uri, http_method='GET', body=None,
453
                       headers=None, token_placement=AUTH_HEADER, ext=None, **kwargs):
454
        """Add a MAC token to the request authorization header.
455

456
        Warning: MAC token support is experimental as the spec is not yet stable.
457
        """
458
        headers = tokens.prepare_mac_header(self.access_token, uri,
459
                                            self.mac_key, http_method, headers=headers, body=body, ext=ext,
460
                                            hash_algorithm=self.mac_algorithm, **kwargs)
461
        return uri, headers, body
462

    
463
    def _populate_attributes(self, response):
464
        """Add commonly used values such as access_token to self."""
465

    
466
        if 'access_token' in response:
467
            self.access_token = response.get('access_token')
468

    
469
        if 'refresh_token' in response:
470
            self.refresh_token = response.get('refresh_token')
471

    
472
        if 'token_type' in response:
473
            self.token_type = response.get('token_type')
474

    
475
        if 'expires_in' in response:
476
            self.expires_in = response.get('expires_in')
477
            self._expires_at = time.time() + int(self.expires_in)
478

    
479
        if 'expires_at' in response:
480
            self._expires_at = int(response.get('expires_at'))
481

    
482
        if 'code' in response:
483
            self.code = response.get('code')
484

    
485
        if 'mac_key' in response:
486
            self.mac_key = response.get('mac_key')
487

    
488
        if 'mac_algorithm' in response:
489
            self.mac_algorithm = response.get('mac_algorithm')
490