summaryrefslogtreecommitdiff
path: root/oauthlib/oauth2/draft25/tokens.py
blob: cc8c237ed5500a666b5a3a262f101b3260526137 (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
from __future__ import absolute_import
"""
oauthlib.oauth2.draft25.tokens
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

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

- Bearer http://tools.ietf.org/html/draft-ietf-oauth-saml2-bearer-08
- MAC http://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-00

"""
from binascii import b2a_base64
import hashlib
import hmac
from urlparse import urlparse

from oauthlib.common import add_params_to_uri, add_params_to_qs, generate_token
from . import utils


def prepare_mac_header(token, uri, key, http_method, nonce=None, headers=None,
        body=None, ext=u'', hash_algorithm=u'hmac-sha-1'):
    """Add an `MAC Access Authentication`_ signature to headers.

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

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

    Example MAC Authorization header, linebreaks added for clarity

    Authorization: MAC id="h480djs93hd8",
                       nonce="1336363200:dj83hs9s",
                       mac="bhCQXTVyfj5cmA9uKkPFx1zeOXM="

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

    :param uri: Request URI.
    :param headers: Request headers as a dictionary.
    :param http_method: HTTP Request method.
    :param key: MAC given provided by token endpoint.
    :param algorithm: HMAC algorithm provided by token endpoint.
    :return: headers dictionary with the authorization field added.
    """
    http_method = http_method.upper()
    host, port = utils.host_from_uri(uri)

    if hash_algorithm.lower() == u'hmac-sha-1':
        h = hashlib.sha1
    else:
        h = hashlib.sha256

    nonce = nonce or u'{0}:{1}'.format(utils.generate_nonce(), utils.generate_timestamp())
    sch, net, path, par, query, fra = urlparse(uri)

    if query:
        request_uri = path + u'?' + query
    else:
        request_uri = path

    # Hash the body/payload
    if body is not None:
        bodyhash = b2a_base64(h(body).digest())[:-1].decode('utf-8')
    else:
        bodyhash = u''

    # Create the normalized base string
    base = []
    base.append(nonce)
    base.append(http_method.upper())
    base.append(request_uri)
    base.append(host)
    base.append(port)
    base.append(bodyhash)
    base.append(ext)
    base_string = '\n'.join(base) + u'\n'

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

    header = []
    header.append(u'MAC id="%s"' % token)
    header.append(u'nonce="%s"' % nonce)
    if bodyhash:
        header.append(u'bodyhash="%s"' % bodyhash)
    if ext:
        header.append(u'ext="%s"' % ext)
    header.append(u'mac="%s"' % sign)

    headers = headers or {}
    headers[u'Authorization'] = u', '.join(header)
    return headers


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

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

    .. _`Bearer Token`: http://tools.ietf.org/html/draft-ietf-oauth-v2-bearer-18
    """
    return add_params_to_uri(uri, [((u'access_token', token))])


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

    Authorization: Bearer h480djs93hd8

    .. _`Bearer Token`: http://tools.ietf.org/html/draft-ietf-oauth-v2-bearer-18
    """
    headers = headers or {}
    headers[u'Authorization'] = u'Bearer %s' % token
    return headers


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

    access_token=h480djs93hd8

    .. _`Bearer Token`: http://tools.ietf.org/html/draft-ietf-oauth-v2-bearer-18
    """
    return add_params_to_qs(body, [((u'access_token', token))])


class TokenBase(object):

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

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

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


class BearerToken(TokenBase):

    @property
    def expires_in(self):
        return 3600

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

    def __call__(self, request, refresh_token=False):
        token = {
            u'access_token': generate_token(),
            u'expires_in': self.expires_in,
            u'scope': ' '.join(request.scopes),
            u'token_type': u'Bearer',
        }
        if getattr(request, u'state', None):
            token[u'state'] = request.state

        if refresh_token:
            token[u'refresh_token'] = generate_token()

        self.save_token(request, token)
        return token

    def validate_request(self, request):
        pass

    def estimate_type(self, request):
        pass