/usr/lib/python3/dist-packages/oauthlib/oauth1/rfc5849/__init__.py is in python3-oauthlib 2.0.1-1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
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 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 | # -*- coding: utf-8 -*-
"""
oauthlib.oauth1.rfc5849
~~~~~~~~~~~~~~
This module is an implementation of various logic needed
for signing and checking OAuth 1.0 RFC 5849 requests.
"""
from __future__ import absolute_import, unicode_literals
import base64
import hashlib
import logging
log = logging.getLogger(__name__)
import sys
try:
import urlparse
except ImportError:
import urllib.parse as urlparse
if sys.version_info[0] == 3:
bytes_type = bytes
else:
bytes_type = str
from oauthlib.common import Request, urlencode, generate_nonce
from oauthlib.common import generate_timestamp, to_unicode
from . import parameters, signature
SIGNATURE_HMAC = "HMAC-SHA1"
SIGNATURE_RSA = "RSA-SHA1"
SIGNATURE_PLAINTEXT = "PLAINTEXT"
SIGNATURE_METHODS = (SIGNATURE_HMAC, SIGNATURE_RSA, SIGNATURE_PLAINTEXT)
SIGNATURE_TYPE_AUTH_HEADER = 'AUTH_HEADER'
SIGNATURE_TYPE_QUERY = 'QUERY'
SIGNATURE_TYPE_BODY = 'BODY'
CONTENT_TYPE_FORM_URLENCODED = 'application/x-www-form-urlencoded'
class Client(object):
"""A client used to sign OAuth 1.0 RFC 5849 requests."""
SIGNATURE_METHODS = {
SIGNATURE_HMAC: signature.sign_hmac_sha1_with_client,
SIGNATURE_RSA: signature.sign_rsa_sha1_with_client,
SIGNATURE_PLAINTEXT: signature.sign_plaintext_with_client
}
@classmethod
def register_signature_method(cls, method_name, method_callback):
cls.SIGNATURE_METHODS[method_name] = method_callback
def __init__(self, client_key,
client_secret=None,
resource_owner_key=None,
resource_owner_secret=None,
callback_uri=None,
signature_method=SIGNATURE_HMAC,
signature_type=SIGNATURE_TYPE_AUTH_HEADER,
rsa_key=None, verifier=None, realm=None,
encoding='utf-8', decoding=None,
nonce=None, timestamp=None):
"""Create an OAuth 1 client.
:param client_key: Client key (consumer key), mandatory.
:param resource_owner_key: Resource owner key (oauth token).
:param resource_owner_secret: Resource owner secret (oauth token secret).
:param callback_uri: Callback used when obtaining request token.
:param signature_method: SIGNATURE_HMAC, SIGNATURE_RSA or SIGNATURE_PLAINTEXT.
:param signature_type: SIGNATURE_TYPE_AUTH_HEADER (default),
SIGNATURE_TYPE_QUERY or SIGNATURE_TYPE_BODY
depending on where you want to embed the oauth
credentials.
:param rsa_key: RSA key used with SIGNATURE_RSA.
:param verifier: Verifier used when obtaining an access token.
:param realm: Realm (scope) to which access is being requested.
:param encoding: If you provide non-unicode input you may use this
to have oauthlib automatically convert.
:param decoding: If you wish that the returned uri, headers and body
from sign be encoded back from unicode, then set
decoding to your preferred encoding, i.e. utf-8.
:param nonce: Use this nonce instead of generating one. (Mainly for testing)
:param timestamp: Use this timestamp instead of using current. (Mainly for testing)
"""
# Convert to unicode using encoding if given, else assume unicode
encode = lambda x: to_unicode(x, encoding) if encoding else x
self.client_key = encode(client_key)
self.client_secret = encode(client_secret)
self.resource_owner_key = encode(resource_owner_key)
self.resource_owner_secret = encode(resource_owner_secret)
self.signature_method = encode(signature_method)
self.signature_type = encode(signature_type)
self.callback_uri = encode(callback_uri)
self.rsa_key = encode(rsa_key)
self.verifier = encode(verifier)
self.realm = encode(realm)
self.encoding = encode(encoding)
self.decoding = encode(decoding)
self.nonce = encode(nonce)
self.timestamp = encode(timestamp)
def __repr__(self):
attrs = vars(self).copy()
attrs['client_secret'] = '****' if attrs['client_secret'] else None
attrs[
'resource_owner_secret'] = '****' if attrs['resource_owner_secret'] else None
attribute_str = ', '.join('%s=%s' % (k, v) for k, v in attrs.items())
return '<%s %s>' % (self.__class__.__name__, attribute_str)
def get_oauth_signature(self, request):
"""Get an OAuth signature to be used in signing a request
To satisfy `section 3.4.1.2`_ item 2, if the request argument's
headers dict attribute contains a Host item, its value will
replace any netloc part of the request argument's uri attribute
value.
.. _`section 3.4.1.2`: http://tools.ietf.org/html/rfc5849#section-3.4.1.2
"""
if self.signature_method == SIGNATURE_PLAINTEXT:
# fast-path
return signature.sign_plaintext(self.client_secret,
self.resource_owner_secret)
uri, headers, body = self._render(request)
collected_params = signature.collect_parameters(
uri_query=urlparse.urlparse(uri).query,
body=body,
headers=headers)
log.debug("Collected params: {0}".format(collected_params))
normalized_params = signature.normalize_parameters(collected_params)
normalized_uri = signature.normalize_base_string_uri(uri,
headers.get('Host', None))
log.debug("Normalized params: {0}".format(normalized_params))
log.debug("Normalized URI: {0}".format(normalized_uri))
base_string = signature.construct_base_string(request.http_method,
normalized_uri, normalized_params)
log.debug("Base signing string: {0}".format(base_string))
if self.signature_method not in self.SIGNATURE_METHODS:
raise ValueError('Invalid signature method.')
sig = self.SIGNATURE_METHODS[self.signature_method](base_string, self)
log.debug("Signature: {0}".format(sig))
return sig
def get_oauth_params(self, request):
"""Get the basic OAuth parameters to be used in generating a signature.
"""
nonce = (generate_nonce()
if self.nonce is None else self.nonce)
timestamp = (generate_timestamp()
if self.timestamp is None else self.timestamp)
params = [
('oauth_nonce', nonce),
('oauth_timestamp', timestamp),
('oauth_version', '1.0'),
('oauth_signature_method', self.signature_method),
('oauth_consumer_key', self.client_key),
]
if self.resource_owner_key:
params.append(('oauth_token', self.resource_owner_key))
if self.callback_uri:
params.append(('oauth_callback', self.callback_uri))
if self.verifier:
params.append(('oauth_verifier', self.verifier))
# providing body hash for requests other than x-www-form-urlencoded
# as described in http://oauth.googlecode.com/svn/spec/ext/body_hash/1.0/oauth-bodyhash.html
# 4.1.1. When to include the body hash
# * [...] MUST NOT include an oauth_body_hash parameter on requests with form-encoded request bodies
# * [...] SHOULD include the oauth_body_hash parameter on all other requests.
content_type = request.headers.get('Content-Type', None)
content_type_eligible = content_type and content_type.find('application/x-www-form-urlencoded') < 0
if request.body is not None and content_type_eligible:
params.append(('oauth_body_hash', base64.b64encode(hashlib.sha1(request.body.encode('utf-8')).digest()).decode('utf-8')))
return params
def _render(self, request, formencode=False, realm=None):
"""Render a signed request according to signature type
Returns a 3-tuple containing the request URI, headers, and body.
If the formencode argument is True and the body contains parameters, it
is escaped and returned as a valid formencoded string.
"""
# TODO what if there are body params on a header-type auth?
# TODO what if there are query params on a body-type auth?
uri, headers, body = request.uri, request.headers, request.body
# TODO: right now these prepare_* methods are very narrow in scope--they
# only affect their little thing. In some cases (for example, with
# header auth) it might be advantageous to allow these methods to touch
# other parts of the request, like the headers—so the prepare_headers
# method could also set the Content-Type header to x-www-form-urlencoded
# like the spec requires. This would be a fundamental change though, and
# I'm not sure how I feel about it.
if self.signature_type == SIGNATURE_TYPE_AUTH_HEADER:
headers = parameters.prepare_headers(
request.oauth_params, request.headers, realm=realm)
elif self.signature_type == SIGNATURE_TYPE_BODY and request.decoded_body is not None:
body = parameters.prepare_form_encoded_body(
request.oauth_params, request.decoded_body)
if formencode:
body = urlencode(body)
headers['Content-Type'] = 'application/x-www-form-urlencoded'
elif self.signature_type == SIGNATURE_TYPE_QUERY:
uri = parameters.prepare_request_uri_query(
request.oauth_params, request.uri)
else:
raise ValueError('Unknown signature type specified.')
return uri, headers, body
def sign(self, uri, http_method='GET', body=None, headers=None, realm=None):
"""Sign a request
Signs an HTTP request with the specified parts.
Returns a 3-tuple of the signed request's URI, headers, and body.
Note that http_method is not returned as it is unaffected by the OAuth
signing process. Also worth noting is that duplicate parameters
will be included in the signature, regardless of where they are
specified (query, body).
The body argument may be a dict, a list of 2-tuples, or a formencoded
string. The Content-Type header must be 'application/x-www-form-urlencoded'
if it is present.
If the body argument is not one of the above, it will be returned
verbatim as it is unaffected by the OAuth signing process. Attempting to
sign a request with non-formencoded data using the OAuth body signature
type is invalid and will raise an exception.
If the body does contain parameters, it will be returned as a properly-
formatted formencoded string.
Body may not be included if the http_method is either GET or HEAD as
this changes the semantic meaning of the request.
All string data MUST be unicode or be encoded with the same encoding
scheme supplied to the Client constructor, default utf-8. This includes
strings inside body dicts, for example.
"""
# normalize request data
request = Request(uri, http_method, body, headers,
encoding=self.encoding)
# sanity check
content_type = request.headers.get('Content-Type', None)
multipart = content_type and content_type.startswith('multipart/')
should_have_params = content_type == CONTENT_TYPE_FORM_URLENCODED
has_params = request.decoded_body is not None
# 3.4.1.3.1. Parameter Sources
# [Parameters are collected from the HTTP request entity-body, but only
# if [...]:
# * The entity-body is single-part.
if multipart and has_params:
raise ValueError(
"Headers indicate a multipart body but body contains parameters.")
# * The entity-body follows the encoding requirements of the
# "application/x-www-form-urlencoded" content-type as defined by
# [W3C.REC-html40-19980424].
elif should_have_params and not has_params:
raise ValueError(
"Headers indicate a formencoded body but body was not decodable.")
# * The HTTP request entity-header includes the "Content-Type"
# header field set to "application/x-www-form-urlencoded".
elif not should_have_params and has_params:
raise ValueError(
"Body contains parameters but Content-Type header was {0} "
"instead of {1}".format(content_type or "not set",
CONTENT_TYPE_FORM_URLENCODED))
# 3.5.2. Form-Encoded Body
# Protocol parameters can be transmitted in the HTTP request entity-
# body, but only if the following REQUIRED conditions are met:
# o The entity-body is single-part.
# o The entity-body follows the encoding requirements of the
# "application/x-www-form-urlencoded" content-type as defined by
# [W3C.REC-html40-19980424].
# o The HTTP request entity-header includes the "Content-Type" header
# field set to "application/x-www-form-urlencoded".
elif self.signature_type == SIGNATURE_TYPE_BODY and not (
should_have_params and has_params and not multipart):
raise ValueError(
'Body signatures may only be used with form-urlencoded content')
# We amend http://tools.ietf.org/html/rfc5849#section-3.4.1.3.1
# with the clause that parameters from body should only be included
# in non GET or HEAD requests. Extracting the request body parameters
# and including them in the signature base string would give semantic
# meaning to the body, which it should not have according to the
# HTTP 1.1 spec.
elif http_method.upper() in ('GET', 'HEAD') and has_params:
raise ValueError('GET/HEAD requests should not include body.')
# generate the basic OAuth parameters
request.oauth_params = self.get_oauth_params(request)
# generate the signature
request.oauth_params.append(
('oauth_signature', self.get_oauth_signature(request)))
# render the signed request and return it
uri, headers, body = self._render(request, formencode=True,
realm=(realm or self.realm))
if self.decoding:
log.debug('Encoding URI, headers and body to %s.', self.decoding)
uri = uri.encode(self.decoding)
body = body.encode(self.decoding) if body else body
new_headers = {}
for k, v in headers.items():
new_headers[k.encode(self.decoding)] = v.encode(self.decoding)
headers = new_headers
return uri, headers, body
|