summaryrefslogtreecommitdiff
path: root/oauthlib/oauth1/rfc5849/parameters.py
blob: b0e6459c41547982228f9cfc8834e616a4072eb8 (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
# -*- coding: utf-8 -*-
from __future__ import absolute_import

"""
oauthlib.parameters
~~~~~~~~~~~~~~~~~~~

This module contains methods related to `section 3.5`_ of the OAuth 1.0a spec.

.. _`section 3.5`: http://tools.ietf.org/html/rfc5849#section-3.5
"""

from urlparse import urlparse, urlunparse
from . import constants, utils
from oauthlib.common import extract_params, urlencode

def get_signature_type(request):
    """**Determine the signature type of a request**
    Per `section 3.5`_ of the spec.

    .. _`section 3.5`: http://tools.ietf.org/html/rfc5849#section-3.5
    """
    signature_types = []

    # When making an OAuth-authenticated request, protocol parameters as
    # well as any other parameter using the "oauth_" prefix SHALL be
    # included in the request using one and only one of the following
    # locations, listed in order of decreasing preference:

    # 1.  The HTTP "Authorization" header field as described in
    #     `Section 3.5.1`_.
    #
    # .. _`Section 3.5.1`: http://tools.ietf.org/html/rfc5849#section-3.5.1
    if request.headers:
        headers_lower = dict(
            (k.lower(), v) for k, v in request.headers.items())
        authorization_header = headers_lower.get(u'authorization')
        if authorization_header is not None:
            header_oauth_params = utils.filter_oauth_params(
                utils.parse_authorization_header(authorization_header))
            if header_oauth_params:
                signature_types.append(constants.SIGNATURE_TYPE_AUTH_HEADER)

    # 2.  The HTTP request entity-body as described in `Section 3.5.2`_.
    #
    # .. _`Section 3.5.2`: http://tools.ietf.org/html/rfc5849#section-3.5.2
    if utils.filter_oauth_params(extract_params(request.body) or []):
        signature_types.append(constants.SIGNATURE_TYPE_BODY)

    # 3.  The HTTP request URI query as described in `Section 3.5.3`_.
    #
    # .. _`Section 3.5.3`: http://tools.ietf.org/html/rfc5849#section-3.5.3
    if utils.filter_oauth_params(request.uri_query_params):
        signature_types.append(constants.SIGNATURE_TYPE_QUERY)

    if len(signature_types) > 1:
        raise ValueError('oauth_ params must come from only 1 signature type but were found in %s' % ', '.join(
            signature_types))
    try:
        return signature_types[0]
    except IndexError:
        raise ValueError('oauth_ params are missing. Could not determine signature type.')


def prepare_headers(request, realm=None):
    """**Prepare the Authorization header.**
    Per `section 3.5.1`_ of the spec.

    Protocol parameters can be transmitted using the HTTP "Authorization"
    header field as defined by `RFC2617`_ with the auth-scheme name set to
    "OAuth" (case insensitive).

    For example::

        Authorization: OAuth realm="Example",
            oauth_consumer_key="0685bd9184jfhq22",
            oauth_token="ad180jjd733klru7",
            oauth_signature_method="HMAC-SHA1",
            oauth_signature="wOJIO9A2W5mFwDgiDvZbTSMK%2FPY%3D",
            oauth_timestamp="137131200",
            oauth_nonce="4572616e48616d6d65724c61686176",
            oauth_version="1.0"


    .. _`section 3.5.1`: http://tools.ietf.org/html/rfc5849#section-3.5.1
    .. _`RFC2617`: http://tools.ietf.org/html/rfc2617
    """
    if not request.oauth_params:
        raise ValueError('request.oauth_params must be present.')
    new_request = request.clone()

    # Protocol parameters SHALL be included in the "Authorization" header
    # field as follows:
    authorization_header_parameters_parts = []
    for oauth_parameter_name, value in new_request.oauth_params:
        # 1.  Parameter names and values are encoded per Parameter Encoding
        #     (`Section 3.6`_)
        #
        # .. _`Section 3.6`: http://tools.ietf.org/html/rfc5849#section-3.6
        escaped_name = utils.escape(oauth_parameter_name)
        escaped_value = utils.escape(value)

        # 2.  Each parameter's name is immediately followed by an "=" character
        #     (ASCII code 61), a """ character (ASCII code 34), the parameter
        #     value (MAY be empty), and another """ character (ASCII code 34).
        part = u'{0}="{1}"'.format(escaped_name, escaped_value)

        authorization_header_parameters_parts.append(part)

    # 3.  Parameters are separated by a "," character (ASCII code 44) and
    #     OPTIONAL linear whitespace per `RFC2617`_.
    #
    # .. _`RFC2617`: http://tools.ietf.org/html/rfc2617
    authorization_header_parameters = ', '.join(
        authorization_header_parameters_parts)

    # 4.  The OPTIONAL "realm" parameter MAY be added and interpreted per
    #     `RFC2617 section 1.2`_.
    #
    # .. _`RFC2617 section 1.2`: http://tools.ietf.org/html/rfc2617#section-1.2
    if realm:
        # NOTE: realm should *not* be escaped
        authorization_header_parameters = (u'realm="%s", ' % realm +
            authorization_header_parameters)

    # the auth-scheme name set to "OAuth" (case insensitive).
    authorization_header = u'OAuth %s' % authorization_header_parameters

    # contribute the Authorization header to the given headers
    new_request.headers[u'Authorization'] = authorization_header
    return new_request


def _append_params(oauth_params, params):
    """Append OAuth params to an existing set of parameters.

    Both params and oauth_params is must be lists of 2-tuples.

    Per `section 3.5.2`_ and `3.5.3`_ of the spec.

    .. _`section 3.5.2`: http://tools.ietf.org/html/rfc5849#section-3.5.2
    .. _`3.5.3`: http://tools.ietf.org/html/rfc5849#section-3.5.3

    """
    merged = list(params)
    merged.extend(oauth_params)
    # The request URI / entity-body MAY include other request-specific
    # parameters, in which case, the protocol parameters SHOULD be appended
    # following the request-specific parameters, properly separated by an "&"
    # character (ASCII code 38)
    merged.sort(key=lambda i: i[0].startswith('oauth_'))
    return merged


def prepare_form_encoded_body(request):
    """Prepare the Form-Encoded Body.

    Per `section 3.5.2`_ of the spec.

    .. _`section 3.5.2`: http://tools.ietf.org/html/rfc5849#section-3.5.2

    """
    if not request.oauth_params:
        raise ValueError('request.oauth_params must be present.')

    # append OAuth params to the existing body
    new_request = request.clone()
    body = _append_params(new_request.oauth_params, new_request.body)
    new_request.body = body
    new_request.headers['Content-Type'] = u'application/x-www-form-urlencoded'
    return new_request


def prepare_request_uri_query(request):
    """Prepare the Request URI Query.

    Per `section 3.5.3`_ of the spec.

    .. _`section 3.5.3`: http://tools.ietf.org/html/rfc5849#section-3.5.3

    """
    new_request = request.clone()

    # append OAuth params to the existing set of query components
    sch, net, path, par, query, fra = urlparse(new_request.uri)
    query = urlencode(_append_params(new_request.oauth_params,
        extract_params(query) or []))
    new_request.uri = urlunparse((sch, net, path, par, query, fra))
    return new_request


PREPARE_BY_SIGNATURE_TYPE = {
    constants.SIGNATURE_TYPE_AUTH_HEADER: prepare_headers,
    constants.SIGNATURE_TYPE_QUERY: prepare_form_encoded_body,
    constants.SIGNATURE_TYPE_BODY: prepare_request_uri_query,
}


def prepare_request(request, signature_type):
    try:
        return PREPARE_BY_SIGNATURE_TYPE[signature_type](request)
    except KeyError:
        raise ValueError('Unknown signature type specified.')