class OAuth1(object): """Signs the request using OAuth 1 (RFC5849)""" 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): try: signature_type = signature_type.upper() except AttributeError: pass self.client = Client(client_key, client_secret, resource_owner_key, resource_owner_secret, callback_uri, signature_method, signature_type, rsa_key, verifier) def __call__(self, r): """Add OAuth parameters to the request. Parameters may be included from the body if the content-type is urlencoded, if no content type is set a guess is made. """ # Overwriting url is safe here as request will not modify it past # this point. is_form_encoded = (CONTENT_TYPE_FORM_URLENCODED in r.headers.get('Content-Type', '')) if is_form_encoded or extract_params(r.body): r.headers['Content-Type'] = CONTENT_TYPE_FORM_URLENCODED r.url, r.headers, r.body = self.client.sign( unicode(r.url), unicode(r.method), r.body or '', r.headers) else: # Omit body data in the signing of non form-encoded requests r.url, r.headers, _ = self.client.sign(unicode(r.url), unicode(r.method), None, r.headers) # Having the authorization header, key or value, in unicode will # result in UnicodeDecodeErrors when the request is concatenated # by httplib. This can easily be seen when attaching files. # Note that simply encoding the value is not enough since Python # saves the type of first key set. Thus we remove and re-add. # >>> d = {u'a':u'foo'} # >>> d['a'] = 'foo' # >>> d # { u'a' : 'foo' } u_header = unicode('Authorization') if u_header in r.headers: auth_header = r.headers[u_header].encode('utf-8') del r.headers[u_header] r.headers['Authorization'] = auth_header return r
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, decoding='utf-8'): try: signature_type = signature_type.upper() except AttributeError: pass self.client = Client(client_key, client_secret, resource_owner_key, resource_owner_secret, callback_uri, signature_method, signature_type, rsa_key, verifier, decoding=decoding)
def launch(CFG, url, post, status=200) : header = {'Content-Type' : 'application/x-www-form-urlencoded'} client = Client(CFG.oauth_consumer_key, client_secret=CFG.oauth_secret, signature_type='BODY') uri, headers, body = client.sign(url, 'POST', post, header) print('\nLaunching to',url,end=' ') r = requests.post(url, data=body, headers=headers, allow_redirects=False) if ( r.status_code == 302 or r.status_code == 303 ) : new_url = r.headers.get('Location', False) if new_url is False: print('No Location header found on redirect') dumpr(r) abort() error_url = post.get('launch_presentation_return_url', False) if ( new_url.startswith(error_url) ) : if ( status == 200 ) : print('Expected a successful launch') dumpr(r) abort() print('Received 302 - Success') return r print('\nFollowing redirect', new_url,end=' ') r = requests.get(new_url, cookies=r.cookies) if ( status != 200 ) : print('Expected a failed launch') dumpr(r) abort() print('Received 200 - Success') return r
class OAuth1(object): """Signs the request using OAuth 1 (RFC5849)""" 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, decoding="utf-8", ): try: signature_type = signature_type.upper() except AttributeError: pass self.client = Client( client_key, client_secret, resource_owner_key, resource_owner_secret, callback_uri, signature_method, signature_type, rsa_key, verifier, decoding=decoding, ) def __call__(self, r): """Add OAuth parameters to the request. Parameters may be included from the body if the content-type is urlencoded, if no content type is set a guess is made. """ # Overwriting url is safe here as request will not modify it past # this point. content_type = r.headers.get("Content-Type".encode("utf-8"), "") if not isinstance(content_type, unicode): content_type = content_type.decode("utf-8") is_form_encoded = CONTENT_TYPE_FORM_URLENCODED in content_type if is_form_encoded or extract_params(r.body): r.headers["Content-Type"] = CONTENT_TYPE_FORM_URLENCODED r.url, r.headers, r.body = self.client.sign(unicode(r.url), unicode(r.method), r.body or "", r.headers) else: # Omit body data in the signing of non form-encoded requests r.url, r.headers, _ = self.client.sign(unicode(r.url), unicode(r.method), None, r.headers) r.url = to_native_str(r.url) return r
def oauth1(self): auth_url = self.url_base + '/session' oauth1_client = Client( self.server_token['consumer_key'], client_secret=self.server_token['consumer_secret'], resource_owner_key=self.server_token['access_token'], resource_owner_secret=self.server_token['access_secret']) uri, headers, body = oauth1_client.sign(auth_url) request = Request(auth_url) request.add_header('Authorization', headers['Authorization']) request.add_header('Content-Type', 'application/json;charset=UTF8') request.add_header('X-Server-Protocol-Version', '2') response = urlopen(request).read() # TODO: catch HTTP errors here, do something useful resp_dict = json.loads(response) self.session_token = resp_dict['auth_session_token'] if self.new_session_callback: self.new_session_callback(self.session_token) return self.session_token
def get_access_token(consumer_key, consumer_secret, req_token_key, req_token_secret, verifier, access_token_url, validate_certs=True): method = 'GET' params = {'format': 'json', 'oauth_callback': 'oob'} # they're really serious about that oob full_url = access_token_url + "&" + urllib.urlencode(params) client = OAClient(consumer_key, client_secret=consumer_secret, resource_owner_key=req_token_key, resource_owner_secret=req_token_secret, verifier=verifier, signature_type=SIGNATURE_TYPE_QUERY) full_url, headers, body = client.sign(full_url, method) client = httplib2.Http() client.disable_ssl_certificate_validation = not validate_certs resp, content = client.request(full_url, method=method) try: resp_dict = json.loads(content) acc_token_key, acc_token_secret = resp_dict['key'], resp_dict['secret'] except: raise ValueError('access token step failed: %s\n\nheaders, etc.: %s' % (content, pformat(resp))) return acc_token_key, acc_token_secret
def sign_request(self, url, method, body, headers): """Sign a request with OAuth credentials.""" # 2012-11-19 BAW: In order to preserve API backward compatibility, # convert empty string body to None. The old python-oauth library # would treat the empty string as "no body", but python-oauthlib # requires None. if not body: content_type = headers.get('Content-Type') if content_type == 'application/x-www-form-urlencoded': body = '' else: body = None # Import oauthlib here so that you don't need it if you're not going # to use it. Plan B: move this out into a separate oauth module. from oauthlib.oauth1 import Client from oauthlib.oauth1.rfc5849 import SIGNATURE_PLAINTEXT oauth_client = Client(self.consumer_key, self.consumer_secret, self.token_key, self.token_secret, signature_method=SIGNATURE_PLAINTEXT, realm=self.oauth_realm) uri, signed_headers, body = oauth_client.sign(url, method, body, headers) headers.update(signed_headers)
def _get_authorization_header(request, client_key, client_secret): """ Get proper HTTP Authorization header for a given request Arguments: request: Request object to log Authorization header for Returns: authorization header """ sha1 = hashlib.sha1() body = request.body or '' sha1.update(body) oauth_body_hash = unicode( base64.b64encode(sha1.digest() # pylint: disable=too-many-function-args )) client = Client(client_key, client_secret) params = client.get_oauth_params(None) params.append((u'oauth_body_hash', oauth_body_hash)) mock_request = mock.Mock( uri=unicode(urllib.unquote(request.url)), headers=request.headers, body=u'', decoded_body=u'', oauth_params=params, http_method=unicode(request.method), ) sig = client.get_oauth_signature(mock_request) mock_request.oauth_params.append((u'oauth_signature', sig)) _, headers, _ = client._render( # pylint: disable=protected-access mock_request) return headers['Authorization']
def _log_correct_authorization_header(self, request): """ Helper function that logs proper HTTP Authorization header for a given request Used only in debug situations, this logs the correct Authorization header based on the request header and body according to OAuth 1 Body signing Arguments: request (xblock.django.request.DjangoWebobRequest): Request object to log Authorization header for Returns: nothing """ sha1 = hashlib.sha1() sha1.update(request.body) oauth_body_hash = unicode(base64.b64encode(sha1.digest())) log.debug("[LTI] oauth_body_hash = {}".format(oauth_body_hash)) client_key, client_secret = self.get_client_key_secret() client = Client(client_key, client_secret) mock_request = mock.Mock( uri=unicode(urllib.unquote(request.url)), headers=request.headers, body=u"", decoded_body=u"", http_method=unicode(request.method), ) params = client.get_oauth_params(mock_request) mock_request.oauth_params = params mock_request.oauth_params.append((u'oauth_body_hash', oauth_body_hash)) sig = client.get_oauth_signature(mock_request) mock_request.oauth_params.append((u'oauth_signature', sig)) _, headers, _ = client._render(mock_request) # pylint: disable=protected-access log.debug("\n\n#### COPY AND PASTE AUTHORIZATION HEADER ####\n{}\n####################################\n\n" .format(headers['Authorization']))
def _get_authorization_header(request, client_key, client_secret): """ Get proper HTTP Authorization header for a given request Arguments: request: Request object to log Authorization header for Returns: authorization header """ sha1 = hashlib.sha1() body = request.body or '' sha1.update(body) oauth_body_hash = unicode(base64.b64encode( sha1.digest() # pylint: disable=too-many-function-args )) client = Client(client_key, client_secret) params = client.get_oauth_params(request) params.append((u'oauth_body_hash', oauth_body_hash)) blank_request = Request(urllib.unquote(request.url), http_method=request.method, body='', headers=request.headers, encoding='utf_8') blank_request.oauth_params = params blank_request.decoded_body = '' signature = client.get_oauth_signature(blank_request) blank_request.oauth_params.append((u'oauth_signature', signature)) headers = client._render( # pylint: disable=protected-access blank_request )[1] return headers['Authorization']
def __init__(self, handler, **kwargs): U1Request.__init__(self, handler, kwargs) self.message.type = protocol_pb2.Message.AUTH_REQUEST self.states = { protocol_pb2.Message.AUTH_AUTHENTICATED: self.authenticated, protocol_pb2.Message.ROOT: self.root } self.end_msg_type = protocol_pb2.Message.ROOT # retrieve our OAuth credentials from file def credentials_from_file(): """Extracts the OAuth credentials from file""" with open(AuthRequest.CREDENTIALS_FILE) as f: jsoncreds = json.loads(f.read()) return jsoncreds oauth_creds = credentials_from_file() # Sign the message with oauth client = Client(oauth_creds["consumer_key"], oauth_creds["consumer_secret"], oauth_creds["token"], oauth_creds["token_secret"], signature_method=SIGNATURE_PLAINTEXT, signature_type=SIGNATURE_TYPE_QUERY) url, headers, body = client.sign('http://server') # Parse out the authentication parameters from the query string. auth_parameters = dict( (name, value) for name, value in parse_qsl(urlparse(url).query) if name.startswith('oauth_')) # add the authentication informations to the protobuf for key, value in auth_parameters.items(): newparam = self.message.auth_parameters.add( ) # create a new parameter newparam.name = key newparam.value = value
def setUp(self): self.validator = MagicMock(spec=RequestValidator) self.validator.allowed_signature_methods = ['HMAC-SHA1'] self.validator.timestamp_lifetime = 600 self.endpoint = RequestTokenEndpoint(self.validator) self.client = Client('foo', callback_uri='https://c.b/cb') self.uri, self.headers, self.body = self.client.sign( 'https://i.b/request_token')
class OAuth1(object): """Signs the request using OAuth 1 (RFC5849)""" 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, decoding='utf-8'): try: signature_type = signature_type.upper() except AttributeError: pass self.client = Client(client_key, client_secret, resource_owner_key, resource_owner_secret, callback_uri, signature_method, signature_type, rsa_key, verifier, decoding=decoding) def __call__(self, r): """Add OAuth parameters to the request. Parameters may be included from the body if the content-type is urlencoded, if no content type is set a guess is made. """ # Overwriting url is safe here as request will not modify it past # this point. content_type = r.headers.get('Content-Type'.encode('utf-8'), '') if not isinstance(content_type, unicode): content_type = content_type.decode('utf-8') is_form_encoded = (CONTENT_TYPE_FORM_URLENCODED in content_type) if is_form_encoded or extract_params(r.body): r.headers['Content-Type'] = CONTENT_TYPE_FORM_URLENCODED r.url, r.headers, r.body = self.client.sign( unicode(r.url), unicode(r.method), r.body or '', r.headers) else: # Omit body data in the signing of non form-encoded requests r.url, r.headers, _ = self.client.sign(unicode(r.url), unicode(r.method), None, r.headers) r.url = to_native_str(r.url) return r
def sign_get_query(self, url=None): client = Client(self.service.consumer_key, client_secret=self.service.consumer_secret, signature_method=SIGNATURE_HMAC, signature_type=SIGNATURE_TYPE_QUERY) uri = update_url_params(url or self.service.service.url, self.parameters) query, headers, body = client.sign(uri, http_method="GET") return query
def test_enforce_ssl(self): """Ensure SSL is enforced by default.""" v = RequestValidator() e = BaseEndpoint(v) c = Client('foo') u, h, b = c.sign('http://example.com') r = e._create_request(u, 'GET', b, h) self.assertRaises(errors.InsecureTransportError, e._check_transport_security, r)
def sign_post_parameters(self, url=None): client = Client(self.service.consumer_key, client_secret=self.service.consumer_secret, signature_method=SIGNATURE_HMAC, signature_type=SIGNATURE_TYPE_BODY) uri, headers, body = client.sign(self._get_url(url), http_method="POST", body=self.parameters, headers={"Content-Type": "application/x-www-form-urlencoded"}) return urldecode(body)
async def _connect( self, method, endpoint, params={}, headers=None, body=None ): oauth_client = OAuthClient(self.consumer_key, self.consumer_secret, self.access_token, self.access_token_secret) url = f"https://stream.twitter.com/1.1/{endpoint}.json" url = str(URL(url).with_query(sorted(params.items()))) url, headers, body = oauth_client.sign( url, http_method=method, headers=headers, body=body ) await super()._connect(method, url, headers=headers, body=body)
def sign_get_query(self, url=None): client = Client(self.service.consumer_key, client_secret=self.service.consumer_secret, signature_method=SIGNATURE_HMAC, signature_type=SIGNATURE_TYPE_QUERY) uri = update_url_params(self._get_url(url), self.parameters) try: query, headers, body = client.sign(uri, http_method="GET") except ValueError as e: raise ValueError("Invalid url %r for %r: %s" % (uri, self.service, e)) return query
def xauth(base_url_template=DEFAULT_READER_URL_TEMPLATE, **xargs): """ Returns an OAuth token tuple that can be used with clients.ReaderClient. :param base_url_template: Template for generating Readability API urls. :param consumer_key: Readability consumer key, otherwise read from READABILITY_CONSUMER_KEY. :param consumer_secret: Readability consumer secret, otherwise read from READABILITY_CONSUMER_SECRET. :param username: A username, otherwise read from READABILITY_USERNAME. :param password: A password, otherwise read from READABILITY_PASSWORD. """ consumer_key = xargs.get('consumer_key') or required_from_env( 'READABILITY_CONSUMER_KEY') consumer_secret = xargs.get('consumer_secret') or required_from_env( 'READABILITY_CONSUMER_SECRET') username = xargs.get('username') or required_from_env( 'READABILITY_USERNAME') password = xargs.get('password') or required_from_env( 'READABILITY_PASSWORD') client = Client(consumer_key, client_secret=consumer_secret, signature_type='BODY') url = base_url_template.format(ACCESS_TOKEN_URL) headers = {'Content-Type': 'application/x-www-form-urlencoded'} params = { 'x_auth_username': username, 'x_auth_password': password, 'x_auth_mode': 'client_auth' } uri, headers, body = client.sign(url, http_method='POST', body=urlencode(params), headers=headers) response = requests.post(uri, data=body) logger.debug('POST to %s.', uri) token = parse_qs(response.content) try: # The indexes below are a little weird. parse_qs above gives us # back a dict where each value is a list. We want the first value # in those lists. token = (token[b'oauth_token'][0].decode(), token[b'oauth_token_secret'][0].decode()) except KeyError: raise ValueError('Invalid Credentials.') return token
def getAuthorization(self, method, url, callback=None, verifier=None): client = Client( safe_unicode(self.consumer_key), safe_unicode(self.consumer_secret), safe_unicode(self.key), safe_unicode(self.secret), callback_uri=safe_unicode(callback), verifier=safe_unicode(verifier), ) safe_method = safe_unicode(method) safe_url = safe_unicode(url) # data is omitted because no www-form-encoded data _, header, _ = client.sign(safe_url, safe_method) return header
def getAuthorization(self, request): client = Client( safe_unicode(self.client_key), safe_unicode(self.client_secret), safe_unicode(self.key), safe_unicode(self.secret), callback_uri=safe_unicode(self.callback), verifier=safe_unicode(self.verifier), ) method = safe_unicode(request.get_method()) url = safe_unicode(request.get_full_url()) # data is omitted because no www-form-encoded data uri, headers, body = client.sign(url, method) return headers['Authorization']
def __init__(self, consumer_key: str, consumer_secret: str, resource_owner_key: Optional[str] = None, resource_owner_secret: Optional[str] = None, callback_uri: Optional[str] = None, oauth_verifier: Optional[str] = None, debug_mode: Optional[bool] = None): if debug_mode: self.session = aiohttp.ClientSession(trace_configs=[AIOTumblrDebugger(logger=log)]) else: self.session = aiohttp.ClientSession() self.oauth_client = Client( client_key=consumer_key, client_secret=consumer_secret, resource_owner_key=resource_owner_key, resource_owner_secret=resource_owner_secret, callback_uri=callback_uri, verifier=oauth_verifier, )
def oauth_client(auth_file): from oauthlib.oauth1 import Client creds = [] for idx, line in enumerate(auth_file): if idx % 2 == 0: continue creds.append(line.strip()) return Client(*creds)
def _sign_lti_message(body, key, secret, url): client = Client(client_key=key, client_secret=secret) __, headers, __ = client.sign( unicode(url), http_method=u'POST', body=body, headers={'Content-Type': 'application/x-www-form-urlencoded'}) auth_header = headers['Authorization'][len('OAuth '):] auth = dict([ param.strip().replace('"', '').split('=') for param in auth_header.split(',') ]) body['oauth_nonce'] = auth['oauth_nonce'] body['oauth_signature'] = auth['oauth_signature'] body['oauth_timestamp'] = auth['oauth_timestamp'] body['oauth_signature_method'] = auth['oauth_signature_method'] body['oauth_version'] = auth['oauth_version']
def _sign_lti_message(self, body, key, secret, url): client = Client(client_key=key, client_secret=secret) __, headers, __ = client.sign( url, http_method=u"POST", body=body, headers={"Content-Type": "application/x-www-form-urlencoded"}, ) auth_header = headers["Authorization"][len("OAuth "):] auth = dict([ param.strip().replace('"', "").split("=") for param in auth_header.split(",") ]) body["oauth_nonce"] = auth["oauth_nonce"] body["oauth_signature"] = auth["oauth_signature"] body["oauth_timestamp"] = auth["oauth_timestamp"] body["oauth_signature_method"] = auth["oauth_signature_method"] body["oauth_version"] = auth["oauth_version"]
def __init__(self, callback_uri=None, signature_method=SIGNATURE_HMAC, signature_type=SIGNATURE_TYPE_AUTH_HEADER, rsa_key=None, verifier=None, decoding='utf-8', **kwargs): kwargs = DotDot(kwargs) if signature_type: signature_type = signature_type.upper() self.client = Client(kwargs.consumer_key, kwargs.consumer_secret, kwargs.access_token_key, kwargs.access_token_secret, callback_uri, signature_method, signature_type, rsa_key, verifier, decoding=decoding)
def _get_oauth_headers(self, method, url, data=None, headers=None): """Basic wrapper around oauthlib that we use for Twitter and Flickr.""" # "Client" == "Consumer" in oauthlib parlance. key = self._account.consumer_key secret = self._account.consumer_secret # "resource_owner" == secret and token. resource_owner_key = self._get_access_token() resource_owner_secret = self._account.secret_token oauth_client = Client(key, secret, resource_owner_key, resource_owner_secret) headers = headers or {} if data is not None: headers['Content-Type'] = 'application/x-www-form-urlencoded' # All we care about is the headers, which will contain the # Authorization header necessary to satisfy OAuth. uri, headers, body = oauth_client.sign(url, body=data, headers=headers or {}, http_method=method) return headers
class OAuth1(object): '''gets an OAuth 1 (RFC5849) header''' authstr = 'Authorization' def __init__(self, callback_uri=None, signature_method=SIGNATURE_HMAC, signature_type=SIGNATURE_TYPE_AUTH_HEADER, rsa_key=None, verifier=None, decoding='utf-8', **kwargs): kwargs = DotDot(kwargs) if signature_type: signature_type = signature_type.upper() self.client = Client(kwargs.consumer_key, kwargs.consumer_secret, kwargs.access_token_key, kwargs.access_token_secret, callback_uri, signature_method, signature_type, rsa_key, verifier, decoding=decoding) def get_oath_header(self, url, http_method, parms): '''call it to get a recalculated OAuth1 header :param str url: request URL :param str http_method: request method GET|POST|PUT etc....... :param dict parms: request parameters :return: an OAuth 1 header ''' rt = self.client.sign('%s?%s' % (url, urlencode(parms)), http_method=http_method) return "%s: %s" % (self.authstr, rt[1][self.authstr].encode('utf-8'))
async def _connect(self, method, endpoint, params={}, headers=None, body=None): error_count = 0 # https://developer.twitter.com/en/docs/twitter-api/v1/tweets/filter-realtime/guides/connecting stall_timeout = 90 network_error_wait = network_error_wait_step = 0.25 network_error_wait_max = 16 http_error_wait = http_error_wait_start = 5 http_error_wait_max = 320 http_420_error_wait_start = 60 oauth_client = OAuthClient(self.consumer_key, self.consumer_secret, self.access_token, self.access_token_secret) if self.session is None or self.session.closed: self.session = aiohttp.ClientSession( headers={"User-Agent": self.user_agent}, timeout=aiohttp.ClientTimeout(sock_read=stall_timeout)) url = f"https://stream.twitter.com/1.1/{endpoint}.json" url = str(URL(url).with_query(sorted(params.items()))) try: while error_count <= self.max_retries: request_url, request_headers, request_body = oauth_client.sign( url, method, body, headers) try: async with self.session.request(method, request_url, headers=request_headers, data=request_body, proxy=self.proxy) as resp: if resp.status == 200: error_count = 0 http_error_wait = http_error_wait_start network_error_wait = network_error_wait_step await self.on_connect() async for line in resp.content: line = line.strip() if line: await self.on_data(line) else: await self.on_keep_alive() await self.on_closed(resp) else: await self.on_request_error(resp.status) error_count += 1 if resp.status == 420: if http_error_wait < http_420_error_wait_start: http_error_wait = http_420_error_wait_start await asyncio.sleep(http_error_wait) http_error_wait *= 2 if resp.status != 420: if http_error_wait > http_error_wait_max: http_error_wait = http_error_wait_max except (aiohttp.ClientConnectionError, aiohttp.ClientPayloadError) as e: await self.on_connection_error() await asyncio.sleep(network_error_wait) network_error_wait += network_error_wait_step if network_error_wait > network_error_wait_max: network_error_wait = network_error_wait_max except asyncio.CancelledError: return except Exception as e: await self.on_exception(e) finally: await self.session.close() await self.on_disconnect()
class BaseEndpointTest(TestCase): def setUp(self): self.validator = MagicMock(spec=RequestValidator) self.validator.allowed_signature_methods = ['HMAC-SHA1'] self.validator.timestamp_lifetime = 600 self.endpoint = RequestTokenEndpoint(self.validator) self.client = Client('foo', callback_uri='https://c.b/cb') self.uri, self.headers, self.body = self.client.sign( 'https://i.b/request_token') def test_ssl_enforcement(self): uri, headers, _ = self.client.sign('http://i.b/request_token') h, b, s = self.endpoint.create_request_token_response(uri, headers=headers) self.assertEqual(s, 400) self.assertIn('insecure_transport_protocol', b) def test_missing_parameters(self): h, b, s = self.endpoint.create_request_token_response(self.uri) self.assertEqual(s, 400) self.assertIn('invalid_request', b) def test_signature_methods(self): headers = {} headers['Authorization'] = self.headers['Authorization'].replace( 'HMAC', 'RSA') h, b, s = self.endpoint.create_request_token_response(self.uri, headers=headers) self.assertEqual(s, 400) self.assertIn('invalid_signature_method', b) def test_invalid_version(self): headers = {} headers['Authorization'] = self.headers['Authorization'].replace( '1.0', '2.0') h, b, s = self.endpoint.create_request_token_response(self.uri, headers=headers) self.assertEqual(s, 400) self.assertIn('invalid_request', b) def test_expired_timestamp(self): headers = {} for pattern in ('12345678901', '4567890123', '123456789K'): headers['Authorization'] = sub(r'timestamp="\d*k?"', 'timestamp="%s"' % pattern, self.headers['Authorization']) h, b, s = self.endpoint.create_request_token_response( self.uri, headers=headers) self.assertEqual(s, 400) self.assertIn('invalid_request', b) def test_client_key_check(self): self.validator.check_client_key.return_value = False h, b, s = self.endpoint.create_request_token_response( self.uri, headers=self.headers) self.assertEqual(s, 400) self.assertIn('invalid_request', b) def test_noncecheck(self): self.validator.check_nonce.return_value = False h, b, s = self.endpoint.create_request_token_response( self.uri, headers=self.headers) self.assertEqual(s, 400) self.assertIn('invalid_request', b) def test_enforce_ssl(self): """Ensure SSL is enforced by default.""" v = RequestValidator() e = BaseEndpoint(v) c = Client('foo') u, h, b = c.sign('http://example.com') r = e._create_request(u, 'GET', b, h) self.assertRaises(errors.InsecureTransportError, e._check_transport_security, r) def test_multiple_source_params(self): """Check for duplicate params""" v = RequestValidator() e = BaseEndpoint(v) self.assertRaises(errors.InvalidRequestError, e._create_request, 'https://a.b/?oauth_signature_method=HMAC-SHA1', 'GET', 'oauth_version=foo', URLENCODED) headers = {'Authorization': 'OAuth oauth_signature="foo"'} headers.update(URLENCODED) self.assertRaises(errors.InvalidRequestError, e._create_request, 'https://a.b/?oauth_signature_method=HMAC-SHA1', 'GET', 'oauth_version=foo', headers) headers = {'Authorization': 'OAuth oauth_signature_method="foo"'} headers.update(URLENCODED) self.assertRaises(errors.InvalidRequestError, e._create_request, 'https://a.b/', 'GET', 'oauth_signature=foo', headers) def test_duplicate_params(self): """Ensure params are only supplied once""" v = RequestValidator() e = BaseEndpoint(v) self.assertRaises(errors.InvalidRequestError, e._create_request, 'https://a.b/?oauth_version=a&oauth_version=b', 'GET', None, URLENCODED) self.assertRaises(errors.InvalidRequestError, e._create_request, 'https://a.b/', 'GET', 'oauth_version=a&oauth_version=b', URLENCODED) def test_mandated_params(self): """Ensure all mandatory params are present.""" v = RequestValidator() e = BaseEndpoint(v) r = e._create_request( 'https://a.b/', 'GET', 'oauth_signature=a&oauth_consumer_key=b&oauth_nonce', URLENCODED) self.assertRaises(errors.InvalidRequestError, e._check_mandatory_parameters, r) def test_oauth_version(self): """OAuth version must be 1.0 if present.""" v = RequestValidator() e = BaseEndpoint(v) r = e._create_request( 'https://a.b/', 'GET', ('oauth_signature=a&oauth_consumer_key=b&oauth_nonce=c&' 'oauth_timestamp=a&oauth_signature_method=RSA-SHA1&' 'oauth_version=2.0'), URLENCODED) self.assertRaises(errors.InvalidRequestError, e._check_mandatory_parameters, r) def test_oauth_timestamp(self): """Check for a valid UNIX timestamp.""" v = RequestValidator() e = BaseEndpoint(v) # Invalid timestamp length, must be 10 r = e._create_request( 'https://a.b/', 'GET', ('oauth_signature=a&oauth_consumer_key=b&oauth_nonce=c&' 'oauth_version=1.0&oauth_signature_method=RSA-SHA1&' 'oauth_timestamp=123456789'), URLENCODED) self.assertRaises(errors.InvalidRequestError, e._check_mandatory_parameters, r) # Invalid timestamp age, must be younger than 10 minutes r = e._create_request( 'https://a.b/', 'GET', ('oauth_signature=a&oauth_consumer_key=b&oauth_nonce=c&' 'oauth_version=1.0&oauth_signature_method=RSA-SHA1&' 'oauth_timestamp=1234567890'), URLENCODED) self.assertRaises(errors.InvalidRequestError, e._check_mandatory_parameters, r) # Timestamp must be an integer r = e._create_request( 'https://a.b/', 'GET', ('oauth_signature=a&oauth_consumer_key=b&oauth_nonce=c&' 'oauth_version=1.0&oauth_signature_method=RSA-SHA1&' 'oauth_timestamp=123456789a'), URLENCODED) self.assertRaises(errors.InvalidRequestError, e._check_mandatory_parameters, r) def test_case_insensitive_headers(self): """Ensure headers are case-insensitive""" v = RequestValidator() e = BaseEndpoint(v) r = e._create_request( 'https://a.b', 'POST', ('oauth_signature=a&oauth_consumer_key=b&oauth_nonce=c&' 'oauth_version=1.0&oauth_signature_method=RSA-SHA1&' 'oauth_timestamp=123456789a'), URLENCODED) self.assertIsInstance(r.headers, CaseInsensitiveDict) def test_signature_method_validation(self): """Ensure valid signature method is used.""" body = ('oauth_signature=a&oauth_consumer_key=b&oauth_nonce=c&' 'oauth_version=1.0&oauth_signature_method=%s&' 'oauth_timestamp=1234567890') uri = 'https://example.com/' class HMACValidator(RequestValidator): @property def allowed_signature_methods(self): return (SIGNATURE_HMAC, ) v = HMACValidator() e = BaseEndpoint(v) r = e._create_request(uri, 'GET', body % 'RSA-SHA1', URLENCODED) self.assertRaises(errors.InvalidSignatureMethodError, e._check_mandatory_parameters, r) r = e._create_request(uri, 'GET', body % 'PLAINTEXT', URLENCODED) self.assertRaises(errors.InvalidSignatureMethodError, e._check_mandatory_parameters, r) r = e._create_request(uri, 'GET', body % 'shibboleth', URLENCODED) self.assertRaises(errors.InvalidSignatureMethodError, e._check_mandatory_parameters, r) class RSAValidator(RequestValidator): @property def allowed_signature_methods(self): return (SIGNATURE_RSA, ) v = RSAValidator() e = BaseEndpoint(v) r = e._create_request(uri, 'GET', body % 'HMAC-SHA1', URLENCODED) self.assertRaises(errors.InvalidSignatureMethodError, e._check_mandatory_parameters, r) r = e._create_request(uri, 'GET', body % 'PLAINTEXT', URLENCODED) self.assertRaises(errors.InvalidSignatureMethodError, e._check_mandatory_parameters, r) r = e._create_request(uri, 'GET', body % 'shibboleth', URLENCODED) self.assertRaises(errors.InvalidSignatureMethodError, e._check_mandatory_parameters, r) class PlainValidator(RequestValidator): @property def allowed_signature_methods(self): return (SIGNATURE_PLAINTEXT, ) v = PlainValidator() e = BaseEndpoint(v) r = e._create_request(uri, 'GET', body % 'HMAC-SHA1', URLENCODED) self.assertRaises(errors.InvalidSignatureMethodError, e._check_mandatory_parameters, r) r = e._create_request(uri, 'GET', body % 'RSA-SHA1', URLENCODED) self.assertRaises(errors.InvalidSignatureMethodError, e._check_mandatory_parameters, r) r = e._create_request(uri, 'GET', body % 'shibboleth', URLENCODED) self.assertRaises(errors.InvalidSignatureMethodError, e._check_mandatory_parameters, r)
def create_client(self, **kwargs): return Client(self.consumer_key, signature_method=SIGNATURE_RSA, rsa_key=self.rsa_key, **kwargs)
class TumblrClient(object): api_base_url = 'https://api.tumblr.com/v2/' request_token_url = 'https://www.tumblr.com/oauth/request_token' authorization_url = 'https://www.tumblr.com/oauth/authorize' access_token_url = 'https://www.tumblr.com/oauth/access_token' def __init__(self, consumer_key: str, consumer_secret: str, resource_owner_key: Optional[str] = None, resource_owner_secret: Optional[str] = None, callback_uri: Optional[str] = None, oauth_verifier: Optional[str] = None, debug_mode: Optional[bool] = None): if debug_mode: self.session = aiohttp.ClientSession(trace_configs=[AIOTumblrDebugger(logger=log)]) else: self.session = aiohttp.ClientSession() self.oauth_client = Client( client_key=consumer_key, client_secret=consumer_secret, resource_owner_key=resource_owner_key, resource_owner_secret=resource_owner_secret, callback_uri=callback_uri, verifier=oauth_verifier, ) async def fetch_request_token(self) -> Dict[str, str]: log.debug(f'Fetching request token...') _, signed_headers, _ = self.oauth_client.sign(self.request_token_url, 'POST') log.debug(f'Signed headers: {signed_headers}') resp = await self.session.post(self.request_token_url, headers=signed_headers) log.debug(f'Response: {resp.status} {resp.reason}') log.debug(f'To: {resp.method} {resp.real_url.human_repr()}') log.debug(f'Requested as: {resp.request_info.method} {resp.request_info.real_url.human_repr()}') log.debug(f'Request headers: {resp.request_info.headers}') token_raw = await resp.text() log.debug(f'Token response: {token_raw}') token = dict(urldecode(token_raw)) self.oauth_client.resource_owner_key = token['oauth_token'] self.oauth_client.resource_owner_secret = token['oauth_token_secret'] # Set callback to None for future signing calls self.oauth_client.callback_uri = None return token def fetch_authorization_url(self, request_token: str = None) -> str: if not request_token: request_token = self.oauth_client.resource_owner_key return f'{self.authorization_url}?oauth_token={request_token}' def parse_authorization_response(self, url: str) -> Dict[str, str]: token = dict(urldecode(urlparse(url).query)) self.oauth_client.resource_owner_key = token['oauth_token'] self.oauth_client.verifier = token['oauth_verifier'] return token async def fetch_access_token(self, verifier: Optional[str] = None) -> Dict[str, str]: if verifier: self.oauth_client.verifier = verifier if not getattr(self.oauth_client, 'verifier', None): raise ValueError('No client verifier set.') # TODO: implement own exceptions log.debug(f'Fetching access token...') _, signed_headers, _ = self.oauth_client.sign(self.access_token_url, 'POST') log.debug(f'Signed headers: {signed_headers}') resp = await self.session.post(self.access_token_url, headers=signed_headers) log.debug(f'Response: {resp.status} {resp.reason}') log.debug(f'To: {resp.method} {resp.real_url.human_repr()}') log.debug(f'Requested as: {resp.request_info.method} {resp.request_info.real_url.human_repr()}') log.debug(f'Request headers: {resp.request_info.headers}') token_raw = await resp.text() log.debug(f'Token response: {token_raw}') token = dict(urldecode(token_raw)) self.oauth_client.resource_owner_key = token['oauth_token'] self.oauth_client.resource_owner_secret = token['oauth_token_secret'] # Unset verifier self.oauth_client.verifier = None return token async def signed_request(self, method: str, endpoint: str, params: Optional[List[Tuple[str, str]]] = None, data: Optional[Dict[str, str]] = None, json: Optional[Any] = None, headers: Optional[Dict[str, str]] = None, **kwargs) -> aiohttp.ClientResponse: url = self.api_base_url + endpoint if data: _, signed_headers, _ = self.oauth_client.sign( add_params_to_uri(url, params), http_method=method, body=data, headers=headers ) return await self.session.request(method, url, params=params, data=data, headers=signed_headers) elif json: # Since it is JSON, body apparently doesn't matter when signing _, signed_headers, _ = self.oauth_client.sign( add_params_to_uri(url, params), http_method=method, headers=headers ) return await self.session.request(method, url, params=params, json=json, headers=signed_headers) else: _, signed_headers, _ = self.oauth_client.sign( add_params_to_uri(url, params), http_method=method, headers=headers ) return await self.session.request(method, url, params=params, headers=signed_headers) @classmethod def register_extension(cls, extension: Type[Extension]): extension.register(cls) @classmethod def unregister_extension(cls, extension: Type[Extension]): extension.unregister(cls) async def close_connection(self): await self.session.close()