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

History | View | Annotate | Download (9.09 KB)

1
"""
2
oauthlib.oauth2.rfc6749.tokens
3
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4

5
This module contains methods for adding two types of access tokens to requests.
6

7
- Bearer http://tools.ietf.org/html/rfc6750
8
- MAC http://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01
9
"""
10
from __future__ import absolute_import, unicode_literals
11

    
12
from binascii import b2a_base64
13
import hashlib
14
import hmac
15
try:
16
    from urlparse import urlparse
17
except ImportError:
18
    from urllib.parse import urlparse
19

    
20
from oauthlib.common import add_params_to_uri, add_params_to_qs, unicode_type
21
from oauthlib import common
22

    
23
from . import utils
24

    
25

    
26
class OAuth2Token(dict):
27

    
28
    def __init__(self, params, old_scope=None):
29
        super(OAuth2Token, self).__init__(params)
30
        self._new_scope = None
31
        if 'scope' in params:
32
            self._new_scope = set(utils.scope_to_list(params['scope']))
33
        if old_scope is not None:
34
            self._old_scope = set(utils.scope_to_list(old_scope))
35
            if self._new_scope is None:
36
                # the rfc says that if the scope hasn't changed, it's optional
37
                # in params so set the new scope to the old scope
38
                self._new_scope = self._old_scope
39
        else:
40
            self._old_scope = self._new_scope
41

    
42
    @property
43
    def scope_changed(self):
44
        return self._new_scope != self._old_scope
45

    
46
    @property
47
    def old_scope(self):
48
        return utils.list_to_scope(self._old_scope)
49

    
50
    @property
51
    def old_scopes(self):
52
        return list(self._old_scope)
53

    
54
    @property
55
    def scope(self):
56
        return utils.list_to_scope(self._new_scope)
57

    
58
    @property
59
    def scopes(self):
60
        return list(self._new_scope)
61

    
62
    @property
63
    def missing_scopes(self):
64
        return list(self._old_scope - self._new_scope)
65

    
66
    @property
67
    def additional_scopes(self):
68
        return list(self._new_scope - self._old_scope)
69

    
70

    
71
def prepare_mac_header(token, uri, key, http_method,
72
                       nonce=None,
73
                       headers=None,
74
                       body=None,
75
                       ext='',
76
                       hash_algorithm='hmac-sha-1',
77
                       issue_time=None,
78
                       draft=0):
79
    """Add an `MAC Access Authentication`_ signature to headers.
80

81
    Unlike OAuth 1, this HMAC signature does not require inclusion of the
82
    request payload/body, neither does it use a combination of client_secret
83
    and token_secret but rather a mac_key provided together with the access
84
    token.
85

86
    Currently two algorithms are supported, "hmac-sha-1" and "hmac-sha-256",
87
    `extension algorithms`_ are not supported.
88

89
    Example MAC Authorization header, linebreaks added for clarity
90

91
    Authorization: MAC id="h480djs93hd8",
92
                       nonce="1336363200:dj83hs9s",
93
                       mac="bhCQXTVyfj5cmA9uKkPFx1zeOXM="
94

95
    .. _`MAC Access Authentication`: http://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01
96
    .. _`extension algorithms`: http://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01#section-7.1
97

98
    :param uri: Request URI.
99
    :param headers: Request headers as a dictionary.
100
    :param http_method: HTTP Request method.
101
    :param key: MAC given provided by token endpoint.
102
    :param hash_algorithm: HMAC algorithm provided by token endpoint.
103
    :param issue_time: Time when the MAC credentials were issued (datetime).
104
    :param draft: MAC authentication specification version.
105
    :return: headers dictionary with the authorization field added.
106
    """
107
    http_method = http_method.upper()
108
    host, port = utils.host_from_uri(uri)
109

    
110
    if hash_algorithm.lower() == 'hmac-sha-1':
111
        h = hashlib.sha1
112
    elif hash_algorithm.lower() == 'hmac-sha-256':
113
        h = hashlib.sha256
114
    else:
115
        raise ValueError('unknown hash algorithm')
116

    
117
    if draft == 0:
118
        nonce = nonce or '{0}:{1}'.format(utils.generate_age(issue_time),
119
                                          common.generate_nonce())
120
    else:
121
        ts = common.generate_timestamp()
122
        nonce = common.generate_nonce()
123

    
124
    sch, net, path, par, query, fra = urlparse(uri)
125

    
126
    if query:
127
        request_uri = path + '?' + query
128
    else:
129
        request_uri = path
130

    
131
    # Hash the body/payload
132
    if body is not None and draft == 0:
133
        body = body.encode('utf-8')
134
        bodyhash = b2a_base64(h(body).digest())[:-1].decode('utf-8')
135
    else:
136
        bodyhash = ''
137

    
138
    # Create the normalized base string
139
    base = []
140
    if draft == 0:
141
        base.append(nonce)
142
    else:
143
        base.append(ts)
144
        base.append(nonce)
145
    base.append(http_method.upper())
146
    base.append(request_uri)
147
    base.append(host)
148
    base.append(port)
149
    if draft == 0:
150
        base.append(bodyhash)
151
    base.append(ext or '')
152
    base_string = '\n'.join(base) + '\n'
153

    
154
    # hmac struggles with unicode strings - http://bugs.python.org/issue5285
155
    if isinstance(key, unicode_type):
156
        key = key.encode('utf-8')
157
    sign = hmac.new(key, base_string.encode('utf-8'), h)
158
    sign = b2a_base64(sign.digest())[:-1].decode('utf-8')
159

    
160
    header = []
161
    header.append('MAC id="%s"' % token)
162
    if draft != 0:
163
        header.append('ts="%s"' % ts)
164
    header.append('nonce="%s"' % nonce)
165
    if bodyhash:
166
        header.append('bodyhash="%s"' % bodyhash)
167
    if ext:
168
        header.append('ext="%s"' % ext)
169
    header.append('mac="%s"' % sign)
170

    
171
    headers = headers or {}
172
    headers['Authorization'] = ', '.join(header)
173
    return headers
174

    
175

    
176
def prepare_bearer_uri(token, uri):
177
    """Add a `Bearer Token`_ to the request URI.
178
    Not recommended, use only if client can't use authorization header or body.
179

180
    http://www.example.com/path?access_token=h480djs93hd8
181

182
    .. _`Bearer Token`: http://tools.ietf.org/html/rfc6750
183
    """
184
    return add_params_to_uri(uri, [(('access_token', token))])
185

    
186

    
187
def prepare_bearer_headers(token, headers=None):
188
    """Add a `Bearer Token`_ to the request URI.
189
    Recommended method of passing bearer tokens.
190

191
    Authorization: Bearer h480djs93hd8
192

193
    .. _`Bearer Token`: http://tools.ietf.org/html/rfc6750
194
    """
195
    headers = headers or {}
196
    headers['Authorization'] = 'Bearer %s' % token
197
    return headers
198

    
199

    
200
def prepare_bearer_body(token, body=''):
201
    """Add a `Bearer Token`_ to the request body.
202

203
    access_token=h480djs93hd8
204

205
    .. _`Bearer Token`: http://tools.ietf.org/html/rfc6750
206
    """
207
    return add_params_to_qs(body, [(('access_token', token))])
208

    
209

    
210
def random_token_generator(request, refresh_token=False):
211
    return common.generate_token()
212

    
213

    
214
def signed_token_generator(private_pem, **kwargs):
215
    def signed_token_generator(request):
216
        request.claims = kwargs
217
        return common.generate_signed_token(private_pem, request)
218

    
219
    return signed_token_generator
220

    
221

    
222
class TokenBase(object):
223

    
224
    def __call__(self, request, refresh_token=False):
225
        raise NotImplementedError('Subclasses must implement this method.')
226

    
227
    def validate_request(self, request):
228
        raise NotImplementedError('Subclasses must implement this method.')
229

    
230
    def estimate_type(self, request):
231
        raise NotImplementedError('Subclasses must implement this method.')
232

    
233

    
234
class BearerToken(TokenBase):
235

    
236
    def __init__(self, request_validator=None, token_generator=None,
237
                 expires_in=None, refresh_token_generator=None):
238
        self.request_validator = request_validator
239
        self.token_generator = token_generator or random_token_generator
240
        self.refresh_token_generator = (
241
            refresh_token_generator or self.token_generator
242
        )
243
        self.expires_in = expires_in or 3600
244

    
245
    def create_token(self, request, refresh_token=False):
246
        """Create a BearerToken, by default without refresh token."""
247

    
248
        if callable(self.expires_in):
249
            expires_in = self.expires_in(request)
250
        else:
251
            expires_in = self.expires_in
252

    
253
        request.expires_in = expires_in
254

    
255
        token = {
256
            'access_token': self.token_generator(request),
257
            'expires_in': expires_in,
258
            'token_type': 'Bearer',
259
        }
260

    
261
        if request.scopes is not None:
262
            token['scope'] = ' '.join(request.scopes)
263

    
264
        if request.state is not None:
265
            token['state'] = request.state
266

    
267
        if refresh_token:
268
            if (request.refresh_token and
269
                    not self.request_validator.rotate_refresh_token(request)):
270
                token['refresh_token'] = request.refresh_token
271
            else:
272
                token['refresh_token'] = self.refresh_token_generator(request)
273

    
274
        token.update(request.extra_credentials or {})
275
        token = OAuth2Token(token)
276
        self.request_validator.save_bearer_token(token, request)
277
        return token
278

    
279
    def validate_request(self, request):
280
        token = None
281
        if 'Authorization' in request.headers:
282
            token = request.headers.get('Authorization')[7:]
283
        else:
284
            token = request.access_token
285
        return self.request_validator.validate_bearer_token(
286
            token, request.scopes, request)
287

    
288
    def estimate_type(self, request):
289
        if request.headers.get('Authorization', '').startswith('Bearer'):
290
            return 9
291
        elif request.access_token is not None:
292
            return 5
293
        else:
294
            return 0