def _get_credentials(self, request): authorization = request.headers.get('Authorization', '') settings = request.registry.settings try: authmeth, auth = authorization.split(' ', 1) assert authmeth.lower() == 'bearer' except (AssertionError, ValueError): return None # Trace authentication type. request.auth_type = 'FxA' # Use PyFxa defaults if not specified server_url = settings['fxa-oauth.oauth_uri'] scope = settings['fxa-oauth.scope'] auth_client = OAuthClient(server_url=server_url, cache=self.cache) try: profile = auth_client.verify_token(token=auth, scope=scope) user_id = profile['user'] except fxa_errors.OutOfProtocolError: raise httpexceptions.HTTPServiceUnavailable() except (fxa_errors.InProtocolError, fxa_errors.TrustError): return None return 'fxa_%s' % user_id
def _get_credentials(self, request): authorization = request.headers.get('Authorization', '') try: authmeth, auth = authorization.split(' ', 1) except ValueError: return None if authmeth.lower() != 'bearer': return None # Use PyFxa defaults if not specified server_url = fxa_conf(request, 'oauth_uri') scope = aslist(fxa_conf(request, 'required_scope')) auth_cache = self._get_cache(request) auth_client = OAuthClient(server_url=server_url, cache=auth_cache) try: profile = auth_client.verify_token(token=auth, scope=scope) user_id = profile['user'] except fxa_errors.OutOfProtocolError as e: logger.error(e) raise httpexceptions.HTTPServiceUnavailable() except (fxa_errors.InProtocolError, fxa_errors.TrustError) as e: logger.info(e) return None return user_id
def fxa_oauth_token(request): """Return OAuth token from authorization code. """ state = request.validated['state'] code = request.validated['code'] # Require on-going session stored_redirect = request.cache.get(state) # Make sure we cannot try twice with the same code request.registry.cache.delete(state) if not stored_redirect: return authorization_required(request) # Trade the OAuth code for a longer-lived token auth_client = OAuthClient(server_url=fxa_conf(request, 'oauth_uri'), client_id=fxa_conf(request, 'client_id'), client_secret=fxa_conf(request, 'client_secret')) try: token = auth_client.trade_code(code) except fxa_errors.OutOfProtocolError: raise httpexceptions.HTTPServiceUnavailable() except fxa_errors.InProtocolError as error: logger.error(error) error_details = { 'name': 'code', 'location': 'querystring', 'description': 'Firefox Account code validation failed.' } errors.raise_invalid(request, **error_details) return httpexceptions.HTTPFound(location='%s%s' % (stored_redirect, token))
class TestCachedClient(unittest.TestCase): server_url = TEST_SERVER_URL def setUp(self): self.client = Client(server_url=self.server_url) self.body = ('{"user": "******", "scope": ["profile"],' '"client_id": "abc"}') responses.add(responses.POST, 'https://server/v1/verify', body=self.body, content_type='application/json') def test_has_default_cache(self): self.assertIsNotNone(self.client.cache) self.assertEqual(self.client.cache.ttl, 300) def test_can_change_default_cache(self): cache = MemoryCache(0.01) self.client = Client(cache=cache) self.assertEqual(self.client.cache, cache) self.assertEqual(self.client.cache.ttl, 0.01) def test_can_deactivate_cache(self): self.client = Client(cache=None) self.assertIsNone(self.client.cache) @responses.activate def test_client_verify_code_is_cached(self): with mock.patch.object(self.client.cache, 'set') as mocked_set: with mock.patch.object(self.client.cache, 'get', return_value=None): # First call verification = self.client.verify_token(token='abc') self.assertTrue(mocked_set.called) self.assertDictEqual(verification, json.loads(self.body)) @responses.activate def test_client_verify_code_cached_value_is_used(self): with mock.patch.object(self.client.cache, 'set') as mocked_set: with mock.patch.object(self.client.cache, 'get', return_value=self.body): # Second call verification = self.client.verify_token(token='abc') self.assertFalse(mocked_set.called) self.assertDictEqual(verification, json.loads(self.body)) @responses.activate def test_client_verify_code_cached_value_is_not_used_if_no_cache(self): self.client = Client(cache=None, server_url=self.server_url) # First call verification = self.client.verify_token(token='abc') self.assertDictEqual(verification, json.loads(self.body)) # Second call verification = self.client.verify_token(token='abc') self.assertDictEqual(verification, json.loads(self.body))
class TestOAuthClientRedirectURL(unittest.TestCase): server_url = TEST_SERVER_URL @responses.activate def setUp(self): self.client = Client("abcdef", server_url=self.server_url) def test_redirect_url_with_default_arguments(self): redirect_url = urlparse(self.client.get_redirect_url()) server_url = urlparse(self.server_url) self.assertEqual(redirect_url.hostname, server_url.hostname) self.assertEqual(redirect_url.path, server_url.path + "/authorization") params = parse_qs(redirect_url.query, keep_blank_values=True) self.assertEqual(sorted(params.keys()), ["client_id", "state"]) self.assertEqual(params["client_id"][0], self.client.client_id) self.assertEqual(params["state"][0], "") def test_redirect_url_takes_custom_client_id(self): redirect_url = urlparse(self.client.get_redirect_url(client_id="XX")) params = parse_qs(redirect_url.query, keep_blank_values=True) self.assertEqual(sorted(params.keys()), ["client_id", "state"]) self.assertEqual(params["client_id"][0], "XX") def test_redirect_url_takes_custom_url_parameters(self): redirect_url = urlparse(self.client.get_redirect_url( state="applicationstate", redirect_uri="https://my.site/oauth", scope="profile profile:email", action="signup", email="*****@*****.**", code_challenge="challenge", code_challenge_method="S1234", access_type="offline", keys_jwk="MockJWK", )) server_url = urlparse(self.server_url) self.assertEqual(redirect_url.hostname, server_url.hostname) params = parse_qs(redirect_url.query, keep_blank_values=True) all_params = ["action", "email", "client_id", "redirect_uri", "scope", "state", "access_type", "code_challenge", "code_challenge_method", "keys_jwk"] self.assertEqual(sorted(params.keys()), sorted(all_params)) self.assertEqual(params["client_id"][0], self.client.client_id) self.assertEqual(params["state"][0], "applicationstate") self.assertEqual(params["redirect_uri"][0], "https://my.site/oauth") self.assertEqual(params["scope"][0], "profile profile:email") self.assertEqual(params["action"][0], "signup") self.assertEqual(params["email"][0], "*****@*****.**") self.assertEqual(params["code_challenge"][0], "challenge") self.assertEqual(params["code_challenge_method"][0], "S1234") self.assertEqual(params["access_type"][0], "offline") self.assertEqual(params["keys_jwk"][0], "MockJWK")
def get_oauth_token(client_id=CLIENT_ID, oauth_server=OAUTH_SERVER, auth_server=AUTH_SERVER, email=EMAIL, password=PASSWORD): if password is None: raise Exception('You must set FXA_PASSWORD') print('Getting an oauth token from FxA') oauth_client = OAuthClient(client_id, server_url=oauth_server) session = Client(server_url=auth_server).login(email, password=password) assertion = session.get_identity_assertion(oauth_server) return oauth_client.authorize_token(assertion, scope="profile")
def _verify_token(self, token, request): """Verify the token extracted from the Authorization header. This method stores the result in two locations to avoid hitting the auth remote server as much as possible: - on the request object, in case the Pyramid authentication methods like `effective_principals()` or `authenticated_userid()` are called several times during the request cycle; - in the cache backend, to reuse validated token from one request to another (during ``cache_ttl_seconds`` seconds.) """ # First check if this request was already verified. # `request.bound_data` is an attribute provided by Kinto to store # some data that is shared among sub-requests (e.g. default bucket # or batch requests) if REIFY_KEY not in request.bound_data: # Use PyFxa defaults if not specified server_url = fxa_conf(request, 'oauth_uri') auth_cache = self._get_cache(request) auth_client = OAuthClient(server_url=server_url, cache=auth_cache) user_id = None client_name = None for scope, client in request.registry._fxa_oauth_scope_routing.items(): try: profile = auth_client.verify_token(token=token, scope=aslist(scope)) user_id = profile['user'] scope = profile['scope'] client_name = client # Make sure the bearer token scopes don't match multiple configs. routing_scopes = request.registry._fxa_oauth_scope_routing intersecting_scopes = [x for x in routing_scopes.keys() if x and set(x.split()).issubset(set(scope))] if len(intersecting_scopes) > 1: logger.warn("Invalid FxA token: {} matches multiple config" % scope) return None, None break except fxa_errors.OutOfProtocolError as e: logger.exception("Protocol error") raise httpexceptions.HTTPServiceUnavailable() except (fxa_errors.InProtocolError, fxa_errors.TrustError) as e: logger.debug("Invalid FxA token: %s" % e) # Save for next call. request.bound_data[REIFY_KEY] = (user_id, client_name) return request.bound_data[REIFY_KEY]
class TestAuthClientAuthorizeToken(unittest.TestCase): server_url = TEST_SERVER_URL def setUp(self): self.client = Client("abc", "xyz", server_url=self.server_url) responses.add(responses.POST, 'https://server/v1/authorization', body='{"access_token": "izatoken"}', content_type='application/json') @responses.activate def test_authorize_token_with_default_arguments(self): assertion = "A_FAKE_ASSERTION" token = self.client.authorize_token(assertion) self.assertEquals(token, "izatoken") req_body = json.loads(responses.calls[0].request.body) self.assertEquals(req_body, { "assertion": assertion, "client_id": self.client.client_id, "state": "x", "response_type": "token", }) @responses.activate def test_authorize_token_with_explicit_scope(self): assertion = "A_FAKE_ASSERTION" token = self.client.authorize_token(assertion, scope="storage") self.assertEquals(token, "izatoken") req_body = json.loads(responses.calls[0].request.body) self.assertEquals(req_body, { "assertion": assertion, "client_id": self.client.client_id, "state": "x", "response_type": "token", "scope": "storage", }) @responses.activate def test_authorize_token_with_explicit_client_id(self): assertion = "A_FAKE_ASSERTION" token = self.client.authorize_token(assertion, client_id="cba") self.assertEquals(token, "izatoken") req_body = json.loads(responses.calls[0].request.body) self.assertEquals(req_body, { "assertion": assertion, "client_id": "cba", "state": "x", "response_type": "token", })
class TestAuthClientAuthorizeCode(unittest.TestCase): server_url = TEST_SERVER_URL def setUp(self): self.client = Client("abc", "xyz", server_url=self.server_url) body = '{"redirect": "https://relier/page?code=qed&state=blah"}' responses.add(responses.POST, 'https://server/v1/authorization', body=body, content_type='application/json') @responses.activate def test_authorize_code_with_default_arguments(self): assertion = "A_FAKE_ASSERTION" code = self.client.authorize_code(assertion) self.assertEquals(code, "qed") req_body = json.loads(responses.calls[0].request.body) self.assertEquals(req_body, { "assertion": assertion, "client_id": self.client.client_id, "state": "x", }) @responses.activate def test_authorize_code_with_explicit_scope(self): assertion = "A_FAKE_ASSERTION" code = self.client.authorize_code(assertion, scope="profile:email") self.assertEquals(code, "qed") req_body = json.loads(responses.calls[0].request.body) self.assertEquals(req_body, { "assertion": assertion, "client_id": self.client.client_id, "state": "x", "scope": "profile:email", }) @responses.activate def test_authorize_code_with_explicit_client_id(self): assertion = "A_FAKE_ASSERTION" code = self.client.authorize_code(assertion, client_id="cba") self.assertEquals(code, "qed") req_body = json.loads(responses.calls[0].request.body) self.assertEquals(req_body, { "assertion": assertion, "client_id": "cba", "state": "x", })
def setUp(self): self.client = Client("abc", "xyz", server_url=self.server_url) body = '{"redirect": "https://relier/page?code=qed&state=blah"}' responses.add(responses.POST, 'https://server/v1/authorization', body=body, content_type='application/json')
def setUp(self): self.client = Client(server_url=self.server_url) self.body = ('{"user": "******", "scope": ["profile"],' '"client_id": "abc"}') responses.add(responses.POST, 'https://server/v1/verify', body=self.body, content_type='application/json')
def test_client_verify_code_cached_value_is_not_used_if_no_cache(self): self.client = Client(cache=None, server_url=self.server_url) # First call verification = self.client.verify_token(token='abc') self.assertDictEqual(verification, json.loads(self.body)) # Second call verification = self.client.verify_token(token='abc') self.assertDictEqual(verification, json.loads(self.body))
class TestAuthClientVerifyCode(unittest.TestCase): server_url = TEST_SERVER_URL @responses.activate def setUp(self): self.client = Client(server_url=self.server_url) body = '{"user": "******", "scope": ["profile"], "client_id": "abc"}' responses.add(responses.POST, 'https://server/v1/verify', body=body, content_type='application/json') self.verification = self.client.verify_token(token='abc') self.response = responses.calls[0] def test_reaches_server_on_verify_url(self): self.assertEqual(self.response.request.url, 'https://server/v1/verify') def test_posts_token_to_server(self): body = json.loads(_decoded(self.response.request.body)) expected = { "token": "abc", } self.assertEqual(body, expected) def test_returns_response_given_by_server(self): expected = { "user": "******", "scope": ["profile"], "client_id": "abc" } self.assertEqual(self.verification, expected) @responses.activate def test_raises_error_if_some_attributes_are_not_returned(self): responses.add(responses.POST, 'https://server/v1/verify', body='{"missing": "attributes"}', content_type='application/json') self.assertRaises(fxa.errors.OutOfProtocolError, self.client.verify_token, token='1234') @responses.activate def test_raises_error_if_scopes_do_not_match(self): body = '{"user": "******", "scope": ["files"], "client_id": "abc"}' responses.add(responses.POST, 'https://server/v1/verify', body=body, content_type='application/json') self.assertRaises(fxa.errors.ScopeMismatchError, self.client.verify_token, token='1234', scope='readinglist')
def setUp(self): self.client = Client('abc', 'cake', self.server_url) body = '{"access_token": "yeah"}' responses.add(responses.POST, 'https://server/v1/token', body=body, content_type='application/json') self.tokens = self.client.trade_code('1234') self.response = responses.calls[0]
def setUp(self): self.client = Client(server_url=self.server_url) body = '{"user": "******", "scope": ["profile"], "client_id": "abc"}' responses.add(responses.POST, 'https://server/v1/verify', body=body, content_type='application/json') self.verification = self.client.verify_token(token='abc') self.response = responses.calls[0]
def setUp(self): self.client = Client("abc", "xyz", server_url=self.server_url) responses.add(responses.POST, 'https://server/v1/authorization', body='{"access_token": "izatoken"}', content_type='application/json')
def test_without_prefix_added_prefix(self): client = Client('abc', 'cake', "https://server") self.assertEqual(client.apiclient.server_url, TEST_SERVER_URL)
def test_trailing_slash_with_prefix(self): client = Client('abc', 'cake', "https://server/v1/") self.assertEqual(client.apiclient.server_url, TEST_SERVER_URL)
def test_can_deactivate_cache(self): self.client = Client(cache=None) self.assertIsNone(self.client.cache)
def test_can_change_default_cache(self): cache = MemoryCache(0.01) self.client = Client(cache=cache) self.assertEqual(self.client.cache, cache) self.assertEqual(self.client.cache.ttl, 0.01)
class TestClientTradeCode(unittest.TestCase): server_url = TEST_SERVER_URL @responses.activate def setUp(self): self.client = Client('abc', 'cake', self.server_url) body = '{"access_token": "yeah"}' responses.add(responses.POST, 'https://server/v1/token', body=body, content_type='application/json') self.tokens = self.client.trade_code('1234') self.response = responses.calls[0] def _get_request_body(self): return json.loads(_decoded(responses.calls[0].request.body)) def test_reaches_server_on_token_url(self): self.assertEqual(self.response.request.url, 'https://server/v1/token') def test_posts_code_to_server(self): body = json.loads(_decoded(self.response.request.body)) expected = { "client_secret": "cake", "code": "1234", "client_id": "abc" } self.assertEqual(body, expected) def test_returns_access_token_given_by_server(self): self.assertEqual(self.tokens["access_token"], "yeah") @responses.activate def test_raises_error_if_access_token_not_returned(self): responses.add(responses.POST, 'https://server/v1/token', body='{"missing": "token"}', content_type='application/json') self.assertRaises(fxa.errors.OutOfProtocolError, self.client.trade_code, client_id='abc', client_secret='cake', code='1234') @responses.activate def test_trade_token_can_take_client_credentials_as_arguments(self): responses.add(responses.POST, 'https://server/v1/token', body='{"access_token": "tokay"}', content_type='application/json') # As positional arguments. tokens = self.client.trade_code('1234', 'abc', 'cake2') self.assertEqual(tokens, {"access_token": "tokay"}) self.assertEqual(self._get_request_body(), { 'client_id': 'abc', 'client_secret': 'cake2', 'code': '1234', }) # As keyword arguments. tokens = self.client.trade_code( code='1234', client_id='abc', client_secret='cake2' ) self.assertEqual(tokens, {"access_token": "tokay"}) self.assertEqual(self._get_request_body(), { 'client_id': 'abc', 'client_secret': 'cake2', 'code': '1234', }) @responses.activate def test_trade_token_can_take_pkce_verifier_as_argument(self): responses.add(responses.POST, 'https://server/v1/token', body='{"access_token": "tokay"}', content_type='application/json') tokens = self.client.trade_code( code='1234', code_verifier='verifyme', ) self.assertEqual(tokens, {"access_token": "tokay"}) self.assertEqual(self._get_request_body(), { 'client_id': 'abc', 'client_secret': 'cake', 'code': '1234', 'code_verifier': 'verifyme', })
class TestAuthClientAuthorizeCode(unittest.TestCase): server_url = TEST_SERVER_URL def setUp(self): self.client = Client("abc", "xyz", server_url=self.server_url) body = '{"redirect": "https://relier/page?code=qed&state=blah"}' responses.add(responses.POST, 'https://server/v1/authorization', body=body, content_type='application/json') @responses.activate def test_authorize_code_with_default_arguments(self): assertion = "A_FAKE_ASSERTION" code = self.client.authorize_code(assertion) self.assertEquals(code, "qed") req_body = json.loads(_decoded(responses.calls[0].request.body)) self.assertEquals(req_body, { "assertion": assertion, "client_id": self.client.client_id, "state": "x", }) @responses.activate def test_authorize_code_with_explicit_scope(self): assertion = "A_FAKE_ASSERTION" code = self.client.authorize_code(assertion, scope="profile:email") self.assertEquals(code, "qed") req_body = json.loads(_decoded(responses.calls[0].request.body)) self.assertEquals(req_body, { "assertion": assertion, "client_id": self.client.client_id, "state": "x", "scope": "profile:email", }) @responses.activate def test_authorize_code_with_explicit_client_id(self): assertion = "A_FAKE_ASSERTION" code = self.client.authorize_code(assertion, client_id="cba") self.assertEquals(code, "qed") req_body = json.loads(_decoded(responses.calls[0].request.body)) self.assertEquals(req_body, { "assertion": assertion, "client_id": "cba", "state": "x", }) @responses.activate def test_authorize_code_with_pkce_challenge(self): assertion = "A_FAKE_ASSERTION" challenge, verifier = self.client.generate_pkce_challenge() self.assertEqual(sorted(challenge), ["code_challenge", "code_challenge_method"]) self.assertEqual(sorted(verifier), ["code_verifier"]) code = self.client.authorize_code(assertion, **challenge) self.assertEquals(code, "qed") req_body = json.loads(_decoded(responses.calls[0].request.body)) self.assertEquals(req_body, { "assertion": assertion, "client_id": self.client.client_id, "state": "x", "code_challenge": challenge["code_challenge"], "code_challenge_method": challenge["code_challenge_method"], }) @responses.activate def test_authorize_code_with_session_object(self): session = mock.Mock() session.get_identity_assertion.return_value = "IDENTITY" code = self.client.authorize_code(session) session.get_identity_assertion.assert_called_once_with( audience=TEST_SERVER_URL, service=self.client.client_id ) self.assertEquals(code, "qed") req_body = json.loads(_decoded(responses.calls[0].request.body)) self.assertEquals(req_body, { "assertion": "IDENTITY", "client_id": self.client.client_id, "state": "x", })
def setUp(self): self.client = Client("abcdef", server_url=self.server_url)
def verify_oauth_token(token, scope='profile', oauth_server=OAUTH_SERVER, client_id=CLIENT_ID): oauth_client = OAuthClient(client_id, server_url=oauth_server) return oauth_client.verify_token(token, scope=scope)