Ejemplo n.º 1
0
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')
        add_jwks_response()

    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))
Ejemplo n.º 2
0
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))
Ejemplo n.º 3
0
    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
Ejemplo n.º 4
0
    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
Ejemplo n.º 5
0
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')
Ejemplo n.º 6
0
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')
        add_jwks_response()
        self.verification = self.client.verify_token(token='abc')
        self.response = responses.calls[1]

    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')
        add_jwks_response()
        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')
        add_jwks_response()
        self.assertRaises(fxa.errors.ScopeMismatchError,
                          self.client.verify_token,
                          token='1234',
                          scope='readinglist')
Ejemplo n.º 7
0
class FxaOAuthClient:
    def __init__(self, server_url=None, jwks=None):
        self._client = Client(server_url=server_url, jwks=jwks)

    def verify_token(self, token):
        try:
            token_data = self._client.verify_token(token, DEFAULT_OAUTH_SCOPE)

            # Serialize the data to make it easier to parse in Rust
            return json.dumps(token_data)
        except (ClientError, TrustError):
            return None
Ejemplo n.º 8
0
    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]
Ejemplo n.º 9
0
    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)
        key = 'fxa_verified_token'
        if key in request.bound_data:
            return request.bound_data[key]

        # 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=token, scope=scope)
            user_id = profile['user']
        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)
            user_id = None

        # Save for next call.
        request.bound_data[key] = user_id

        return user_id
Ejemplo n.º 10
0
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)
Ejemplo n.º 11
0
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)
Ejemplo n.º 12
0
class TestJwtToken(unittest.TestCase):

    server_url = TEST_SERVER_URL

    def callback(self, request):
        if self.verify_will_succeed:
            return (200, {}, self.body)
        return (500, {}, '{}')

    def _make_jwt(self, payload, key, alg="RS256", header={"typ": "at+jwt"}):
        header = header.copy(
        )  # So we don't accidentally mutate argument in-place
        return six.ensure_text(jwt.encode(payload, key, alg, header))

    def setUp(self):
        self.client = Client(server_url=self.server_url)
        self.body = ('{"user": "******", "scope": ["profile"],'
                     '"client_id": "abc"}')
        responses.add_callback(responses.POST,
                               'https://server/v1/verify',
                               callback=self.callback,
                               content_type='application/json')
        add_jwks_response()
        self.verify_will_succeed = True

    def get_file_contents(self, filename):
        return jwt.algorithms.RSAAlgorithm.from_jwk(
            open(os.path.join(os.path.dirname(__file__), filename)).read())

    @responses.activate
    def test_good_jwt_token(self):
        private_key = self.get_file_contents("private-key.json")
        token = self._make_jwt(
            {
                "sub": "asdf",
                "scope": "qwer",
                "client_id": "foo"
            }, private_key)
        self.client.verify_token(token)
        for c in responses.calls:
            if c.request.url == 'https://server/v1/verify':
                raise Exception("testing with a good token should not have \
                                 resulted in a call to /verify, but it did.")

    @responses.activate
    def test_wrong_key_jwt_token(self):
        self.verify_will_succeed = False
        bad_key = self.get_file_contents("bad-key.json")
        token = self._make_jwt({}, bad_key)
        with self.assertRaises(fxa.errors.TrustError):
            self.client.verify_token(token)
        for c in responses.calls:
            if c.request.url == 'https://server/v1/verify':
                raise Exception(
                    "testing with a well-formed token with invalid signature \
                                 should not have resulted in a call to /verify, but it did."
                )

    @responses.activate
    def test_expired_jwt_token(self):
        private_key = self.get_file_contents("private-key.json")
        token = self._make_jwt({"qwer": "asdf", "exp": 0}, private_key)
        with self.assertRaises(fxa.errors.TrustError):
            self.client.verify_token(token)

    @responses.activate
    def test_garbage_jwt_token(self):
        self.verify_will_succeed = False
        with self.assertRaises(fxa.errors.ServerError):
            self.client.verify_token("garbage")
        for c in responses.calls:
            if c.request.url == 'https://server/v1/verify':
                break
        else:
            raise Exception("testing with a garbage token should have \
                             called /verify, but it did not.")