def _test_handle_template(self, client_id, redirect_uri, scope, state, encrypted_login, expected_redirect=None, encrypted_access=None, redirect_qparam=None): '''This method provides a template for testing handle_grant method from implicit grant provider.''' if not encrypted_access: encrypted_access = "encrypted access token" if not expected_redirect: scope = scope or "" state = state or "" expected_redirect = "%s#access_token=%s&state=%s&token_type=access&expires_in=%s&scope=%s" % \ (redirect_uri, encrypted_access, urllib.parse.quote(state), self._EXPIRES_IN, urllib.parse.quote(scope)) login_token = Token({"user_id": 1}) access_token = Token({"type": "access"}) request = Mock() request.params = { "client_id": client_id, "redirect_uri": redirect_uri, "scope": scope, "state": state, "login_token": encrypted_login, "redirect": redirect_qparam } self._tokens_service.decrypt = Mock(return_value=login_token) self._tokens_service.generate = Mock(return_value=access_token) self._tokens_service.encrypt = Mock(return_value=encrypted_access) response = self._handler.handle_grant(request) if not redirect_qparam: self.assertIsInstance(response, RedirectResponse) else: self.assertIsInstance(response, Response) self.assertEqual(expected_redirect, response.headers.get("Location")) self._tokens_service.decrypt.assert_called_once_with(encrypted_login) self._tokens_service.validate.assert_called_once_with(login_token) self._tokens_service.generate.assert_called_once_with( { "client_id": client_id, "user_id": login_token.user_id, "scopes": scope, "expires_in": self._EXPIRES_IN }, TokenGeneratorFactory.ACCESS_TOKEN) self._tokens_service.encrypt.assert_called_once_with( access_token, client_id)
def _test_validate_security_context_template(self, valid=None, side_effect=None): '''This method provides a template for checking various behaviors of validate_security_context method.''' access_token = Token({}) kwargs = {} if side_effect: kwargs["side_effect"] = side_effect else: kwargs["return_value"] = valid request = Mock() request.context = Mock() request.context.security = Mock() request.context.security.access_token = access_token request.context.security.validate_context = Mock(**kwargs) self.assertEqual( access_token, self._controller.validate_security_context(request, attr_scope="scopes")) request.context.security.validate_context.assert_called_once_with( "scopes")
def _test_authenticate_encrypt_ex(self, ex): '''This method provides a template test case which allows token encrypt method to raise various exceptions.''' creation_time, expiration_time = self._mock_creationexpiration_time() user = User(username="******", password="******") user.session = Mock() return_url = "/test/url" token = Token({ "client_id": self._IDP_CLIENTID, "type": "login", "user_id": user.user_id, "creation_time": creation_time, "expiration_time": expiration_time }) request, user_repo_cls, user_repo, tokens_service_cls, \ tokens_service, clienturl_facade = self._mock_authenticate_dependencies(token, user, return_url) tokens_service.encrypt = Mock(side_effect=ex) self._idp_controller.authenticate( request, tokens_service_cls=tokens_service_cls, user_repo_cls=user_repo_cls)
def generate(self, token_desc, time_provider=time): '''This method generates a login token. In order to succeed token descriptor must contain the following keys: * client_id - a unique identifier for the idp which generated the token. * user_id - idp user unique identifier. * expires_in - an integer value in seconds determining the maximum validity of the token. If any of the above keys are missing an oauth 2 exception is raised. ''' token_desc = token_desc or {} client_id = self._validate_missing_attr("client_id", token_desc) user_id = self._validate_missing_attr("user_id", token_desc) creation_time = int(time_provider.time()) expires_in = self._validate_missing_attr("expires_in", token_desc) expiration_time = creation_time + expires_in token = { "client_id": client_id, "type": self.TOKEN_TYPE, "user_id": user_id, "creation_time": creation_time, "expiration_time": expiration_time } return Token(token)
def generate(self, token_desc, time_provider=time): '''This method generates a new access token starting from the givent token descriptor. In order to succeed the token descriptor must contain the following keys: * client_id - Client unique identifier. * user_id - User unique identifier. * scopes - The scopes requested for this client (a space delimited list of strings). * expires_in - The time to live period (in seconds) for the newly generated access token. ''' token_desc = token_desc or {} client_id = self._validate_missing_attr("client_id", token_desc) user_id = self._validate_missing_attr("user_id", token_desc) scopes = self._validate_missing_attr("scopes", token_desc).split(" ") expires_in = self._validate_missing_attr("expires_in", token_desc) client = self._validate_client(client_id) self._validate_client_scopes(client.scopes, scopes) creation_time = time_provider.time() expiration_time = int(creation_time) + expires_in token = Token({ "client_id": client_id, "type": "access", "user_id": user_id, "scopes": scopes, "creation_time": int(creation_time), "expiration_time": expiration_time }) return token
def test_validate_context_ok_noscopes(self): '''This test case ensures a security context is valid when no required scopes are necessary.''' access_token = Token({"scopes": ["scope1"]}) security_ctx = SecurityContext(access_token) self.assertTrue(security_ctx.validate_context())
def test_aes_encrypt_unexpected_error(self): '''This test case ensures all unexpected exceptions from encrypt are converted correctly into OAuth2 encryption errors.''' aes_provider = AesTokenEncryption() with self.assertRaises(OAuth2TokenEncryptionError): aes_provider.encrypt_token(Token({}), "Simple IV", "Simple Key")
def test_authenticate_different_passwords(self): '''This test case ensures an exception is raised when passwords do not match.''' creation_time, expiration_time = self._mock_creationexpiration_time() user = User(username="******", password="******") user.session = Mock() return_url = "/test/url" token = Token({ "client_id": self._IDP_CLIENTID, "type": "login", "user_id": user.user_id, "creation_time": creation_time, "expiration_time": expiration_time }) request, user_repo_cls, user_repo, tokens_service_cls, \ tokens_service, clienturl_facade = self._mock_authenticate_dependencies(token, user, return_url) with self.assertRaises(OAuth2AuthenticationError): self._idp_controller.authenticate( request, tokens_service_cls=tokens_service_cls, user_repo_cls=user_repo_cls)
def test_encrypt_invalidclient(self): '''This test case ensures all exceptions occuring during client load are converted to oauth2 invalid client exceptions.''' self._client_repo.load = Mock( side_effect=Exception("Unexpected exception.")) with self.assertRaises(OAuth2InvalidClientError): self._tokens_service.encrypt(Token({}), "mock-client")
def test_aes_descrypt_notokeniv(self): '''This test case ensures aes decrypt fails if no token iv is given.''' aes_provider = AesTokenEncryption() with self.assertRaises(OAuth2InvalidTokenDescriptorError) as ctx: aes_provider.decrypt_token(Token({}), None, None) self.assertEqual("token_iv", ctx.exception.attr_name)
def test_aes_encrypt_notokenkey(self): '''This test case ensures aes encrypt fails if no token_key is given.''' aes_provider = AesTokenEncryption() with self.assertRaises(OAuth2InvalidTokenDescriptorError) as ctx: aes_provider.encrypt_token(Token({}), "simple key", None) self.assertEqual("token_key", ctx.exception.attr_name)
def test_middleware_ok_header(self): '''This test case ensures OAuth2TokensMiddleware executes correctly when configured according to spec (runs after all native Fantastico middlewares executed) and access token is sent in header **Authorization**.''' param_token = "encrypted token value" token = Token({"scopes": ["scope1", "scope2"]}) self._request.params = {} self._request.headers = {"Authorization": "Bearer %s" % param_token} self._test_middleware_template(param_token, token)
def test_validate_context_invalid(self): '''This test case ensures a security context is invalid when required scopes are not found in access_token scopes.''' for attr_scope in self._ATTR_SCOPES: access_token = Token({"scopes": []}) required_scopes = ["scope1", "scope2"] required_scopes_obj = self._mock_required_scopes_obj( attr_scope, required_scopes) security_ctx = SecurityContext(access_token, required_scopes_obj) self.assertFalse(security_ctx.validate_context(attr_scope))
def test_invalidate_generator_ex(self): '''This test case ensures generator unexpected exceptions are converted to oauth2 concrete exceptions.''' token = Token({"type": "mock-type"}) ex = Exception("Unexpected exception.") self._tokens_generator.invalidate = Mock(side_effect=ex) with self.assertRaises(OAuth2InvalidTokenTypeError): self._tokens_service.invalidate(token)
def test_middleware_ok_query(self): '''This test case ensures OAuth2TokensMiddleware executes correctly when configured according to spec (runs after all native Fantastico middlewares executed) and access token is sent in query parameter **token**.''' param_token = "encrypted token value" token = Token({"scopes": ["scope1", "scope2"]}) self._request.params = {OAuth2TokensMiddleware.TOKEN_QPARAM: param_token} self._request.headers = {} self._test_middleware_template(param_token, token)
def test_invalidate_ok(self): '''This test case ensures tokens can be invalidated successfully.''' token = Token({"type": "mock-type"}) self._tokens_generator.invalidate = Mock() self.assertIsNone(self._tokens_service.invalidate(token)) self._tokens_factory.get_generator.assert_called_once_with( token.type, self._db_conn) self._tokens_generator.invalidate.assert_called_once_with(token)
def test_required_scopes_unauthorized(self): '''This test case ensures a concrete exception is raised if the requested scopes are not available in the current security context.''' access_token = Token({"scopes": []}) request = Mock() request.context = Mock() request.context.security = SecurityContext(access_token) mock_controller = MockController(Mock()) self.assertRaises(OAuth2UnauthorizedError, lambda: mock_controller.do_stuff(request))
def test_invalidate_generator_oauth2ex(self): '''This test case ensures generator oauth2 exceptions are bubbled up.''' token = Token({"type": "mock-type"}) ex = OAuth2Error(error_code=-1) self._tokens_generator.invalidate = Mock(side_effect=ex) with self.assertRaises(OAuth2Error) as ctx: self._tokens_service.invalidate(token) self.assertEqual(ex, ctx.exception)
def test_validate_token_ok(self): '''This test case ensures a valid token does not throw an exception.''' creation_time = time.time() expiration_time = int(creation_time + 300) token = Token({ "client_id": "test-idp", "type": LoginTokenGenerator.TOKEN_TYPE, "user_id": 1, "creation_time": int(creation_time), "expiration_time": expiration_time }) self.assertTrue(self._generator.validate(token))
def test_validate_token_expired(self): '''This test case ensures a token which is expired fails validation of login token generator.''' creation_time = int(time.time() - 7200) expiration_time = int(creation_time - 300) token = Token({ "client_id": "test-idp", "type": LoginTokenGenerator.TOKEN_TYPE, "user_id": 1, "creation_time": int(creation_time), "expiration_time": expiration_time }) self.assertRaises(OAuth2TokenExpiredError, lambda: self._generator.validate(token))
def test_validate_tokenexpired(self): '''This test case ensures an exception is raised if the token is expired.''' creation_time = int(time.time() - 7200) expiration_time = int(time.time() - 3600) token = Token({ "client_id": "sample-app", "type": "access", "user_id": 1, "creation_time": creation_time, "expiration_time": expiration_time }) with self.assertRaises(OAuth2TokenExpiredError): self._generator.validate(token)
def test_validate_ok(self): '''This test case ensures token validation works correctly for a given token.''' token_desc = { "client_id": "abc", "type": "mock-type", "attr": "test-attr" } token = Token(token_desc) self._tokens_generator.validate = Mock(return_value=True) self.assertTrue(self._tokens_service.validate(token)) self._tokens_factory.get_generator.assert_called_once_with( token.type, self._db_conn) self._tokens_generator.validate.assert_called_once_with(token)
def test_generate_ok(self): '''This test case ensures tokens service can generate a token starting from a given dictionary.''' token_type = "mock-type" token_desc = {"client_id": "abc", "test_attr": 1} expected_token = Token(token_desc) self._tokens_generator.generate = Mock(return_value=expected_token) token = self._tokens_service.generate(token_desc, token_type) self.assertEqual(expected_token, token) self._tokens_factory.get_generator.assert_called_once_with( token_type, self._db_conn) self._tokens_generator.generate.assert_called_once_with(token_desc)
def _test_authenticate_ok(self, return_url, expected_url): '''This method provides a template test case for ensuring authenticate succeeds for various return_url values.''' user = User(username="******", password="******", person_id=1) user.user_id = 123 creation_time, expiration_time = self._mock_creationexpiration_time() token = Token({ "client_id": self._IDP_CLIENTID, "type": "login", "user_id": user.user_id, "creation_time": creation_time, "expiration_time": expiration_time }) request, user_repo_cls, user_repo, tokens_service_cls, \ tokens_service, clienturl_facade = self._mock_authenticate_dependencies(token, user, return_url) response = self._idp_controller.authenticate( request, tokens_service_cls=tokens_service_cls, user_repo_cls=user_repo_cls) self.assertIsNotNone(response) self.assertEqual(302, response.status_code) location = response.headers.get("Location") self.assertEqual(expected_url, location) user_repo.load_by_username.assert_called_once_with(user.username) self._hasher.hash_password.assert_called_once_with( user.password, DictionaryObject({"salt": user.user_id})) tokens_service_cls.assert_called_once_with(clienturl_facade.session) tokens_service.generate.assert_called_once_with( { "client_id": self._IDP_CLIENTID, "user_id": user.user_id, "expires_in": self._EXPIRES_IN }, TokenGeneratorFactory.LOGIN_TOKEN) tokens_service.encrypt.assert_called_once_with(token, token.client_id)
def test_validate_token_invalidtype(self): '''This test case ensures a token with a different type than login can not be validated by login token generator.''' creation_time = time.time() expiration_time = int(creation_time + 300) token = Token({ "client_id": "test-idp", "type": "Unknown", "user_id": 1, "creation_time": int(creation_time), "expiration_time": expiration_time }) with self.assertRaises(OAuth2InvalidTokenTypeError) as ctx: self._generator.validate(token) self.assertEqual(token.type, ctx.exception.token_type)
def _test_required_scopes_method(self, method, expected_scopes): '''This method provides a template test case for invoking and asserting result of a method decorated with @RequiredScopes.''' expected_scopes = sorted(expected_scopes) access_token = Token({"scopes": expected_scopes}) request = Mock() request.context = Mock() request.context.security = SecurityContext(access_token) self.assertIsNone(method(request)) security_ctx = request.context.security self.assertEqual(access_token, security_ctx.access_token) self.assertEqual(expected_scopes, security_ctx.required_scopes.scopes)
def test_aes_ok(self): '''This test case ensures token encryption correctly supports AES-128 algorithm.''' aes_provider = AesTokenEncryption() for key_length in [16, 24, 32]: token_key = Random.new().read(key_length) token = Token({"client_id": "test client", "attr1": "test"}) token_encrypted = aes_provider.encrypt_token( token, self._aes_iv, token_key) token_decrypted = aes_provider.decrypt_token( token_encrypted, self._aes_iv, token_key) self.assertIsNotNone(token_decrypted) self.assertEqual(token.client_id, token_decrypted.client_id) self.assertEqual(token.attr1, token_decrypted.attr1)
def test_validate_ok(self): '''This test case ensures a valid token passes validation.''' creation_time = int(time.time()) expiration_time = creation_time + 3600 token = Token({ "client_id": "sample-app", "type": "access", "user_id": 1, "creation_time": creation_time, "expiration_time": expiration_time }) expected_client = Client(client_id=token.client_id, revoked=False) self._mock_client_search(expected_client) self.assertTrue(self._generator.validate(token))
def test_validate_invalidtype(self): '''This test case ensures an exception is raised when the token type is not compatible with generator.''' creation_time = int(time.time()) expiration_time = creation_time + 3600 token = Token({ "client_id": "sample-app", "type": "unknown", "user_id": 1, "creation_time": creation_time, "expiration_time": expiration_time }) with self.assertRaises(OAuth2InvalidTokenTypeError) as ctx: self._generator.validate(token) self.assertEqual(token.type, ctx.exception.token_type)
def test_validate_token_clientrevoked(self): '''This test case ensures an expection is raised if the token client is not valid.''' creation_time = int(time.time()) expiration_time = creation_time + 3600 token = Token({ "client_id": "sample-app", "type": "access", "user_id": 1, "creation_time": creation_time, "expiration_time": expiration_time }) expected_client = Client(client_id=token.client_id, revoked=True) self._mock_client_search(expected_client) with self.assertRaises(OAuth2InvalidClientError): self._generator.validate(token)