diff --git a/backend/capellacollab/config/models.py b/backend/capellacollab/config/models.py index 66bece20a..39f1d9026 100644 --- a/backend/capellacollab/config/models.py +++ b/backend/capellacollab/config/models.py @@ -259,7 +259,7 @@ class AuthenticationConfig(BaseConfig): mapping: ClaimMappingConfig = ClaimMappingConfig() scopes: list[str] = pydantic.Field( default=["openid", "profile", "offline_access"], - description="List of scopes that application neeeds to access the required attributes.", + description="List of scopes that the application needs to access the required attributes.", ) client: AuthOauthClientConfig = AuthOauthClientConfig() redirect_uri: str = pydantic.Field( diff --git a/backend/capellacollab/core/authentication/api_key_cookie.py b/backend/capellacollab/core/authentication/api_key_cookie.py index 3c68b7376..772508e59 100644 --- a/backend/capellacollab/core/authentication/api_key_cookie.py +++ b/backend/capellacollab/core/authentication/api_key_cookie.py @@ -18,28 +18,24 @@ auth_config = config.authentication -class JWTConfigBorg: - _shared_state: dict[str, str] = {} - - def __init__( - self, provider_config: oidc_provider.AbstractOIDCProviderConfig - ): - self.__dict__ = self._shared_state - self.provider_config = provider_config - - if not hasattr(self, "jwks_client"): - self.jwks_client = jwt.PyJWKClient( - uri=self.provider_config.get_jwks_uri() +class JWTConfig: + _jwks_client = None + + def __init__(self, oidc_config: oidc_provider.AbstractOIDCProviderConfig): + self.oidc_config = oidc_config + + if JWTConfig._jwks_client is None: + JWTConfig._jwks_client = jwt.PyJWKClient( + uri=self.oidc_config.get_jwks_uri() ) + self.jwks_client = JWTConfig._jwks_client class JWTAPIKeyCookie(security.APIKeyCookie): - def __init__( - self, provider_config: oidc_provider.AbstractOIDCProviderConfig - ): + def __init__(self, oidc_config: oidc_provider.AbstractOIDCProviderConfig): super().__init__(name="id_token", auto_error=True) - self.provider_config = provider_config - self.jwt_config = JWTConfigBorg(provider_config) + self.oidc_config = oidc_config + self.jwt_config = JWTConfig(oidc_config) async def __call__(self, request: fastapi.Request) -> str: token: str | None = await super().__call__(request) @@ -59,14 +55,10 @@ def validate_token(self, token: str) -> dict[str, t.Any]: return jwt.decode( jwt=token, key=signing_key.key, - algorithms=self.provider_config.get_supported_signing_algorithms(), - audience=self.provider_config.get_client_id(), - issuer=self.provider_config.get_issuer(), - options={ - "verify_exp": True, - "verify_iat": True, - "verify_nbf": True, - }, + algorithms=self.oidc_config.get_supported_signing_algorithms(), + audience=self.oidc_config.get_client_id(), + issuer=self.oidc_config.get_issuer(), + options={"require": ["exp", "iat"]}, ) except jwt_exceptions.ExpiredSignatureError: raise exceptions.TokenSignatureExpired() diff --git a/backend/capellacollab/core/authentication/injectables.py b/backend/capellacollab/core/authentication/injectables.py index d19982173..844c1a749 100644 --- a/backend/capellacollab/core/authentication/injectables.py +++ b/backend/capellacollab/core/authentication/injectables.py @@ -30,24 +30,20 @@ @functools.lru_cache -def get_cached_oidc_provider_config() -> ( - oidc_provider.AbstractOIDCProviderConfig -): +def get_cached_oidc_config() -> oidc_provider.AbstractOIDCProviderConfig: return oidc_provider.WellKnownOIDCProviderConfig() -async def get_oidc_provider_config() -> ( - oidc_provider.AbstractOIDCProviderConfig -): - return get_cached_oidc_provider_config() +async def get_oidc_config() -> oidc_provider.AbstractOIDCProviderConfig: + return get_cached_oidc_config() async def get_oidc_provider( - oidc_provider_config: oidc_provider.AbstractOIDCProviderConfig = fastapi.Depends( - get_oidc_provider_config + oidc_config: oidc_provider.AbstractOIDCProviderConfig = fastapi.Depends( + get_oidc_config ), ) -> oidc_provider.AbstractOIDCProvider: - return oidc_provider.OIDCProvider(oidc_provider_config) + return oidc_provider.OIDCProvider(oidc_config) class OpenAPIFakeBase(security_base.SecurityBase): @@ -83,15 +79,13 @@ class OpenAPIPersonalAccessToken(OpenAPIFakeBase): async def get_username( request: fastapi.Request, - provider_config: oidc_provider.AbstractOIDCProviderConfig = fastapi.Depends( - get_oidc_provider_config + oidc_config: oidc_provider.AbstractOIDCProviderConfig = fastapi.Depends( + get_oidc_config ), _unused1=fastapi.Depends(OpenAPIPersonalAccessToken()), ) -> str: if request.cookies.get("id_token"): - username = await api_key_cookie.JWTAPIKeyCookie(provider_config)( - request - ) + username = await api_key_cookie.JWTAPIKeyCookie(oidc_config)(request) return username authorization = request.headers.get("Authorization") diff --git a/backend/capellacollab/core/authentication/routes.py b/backend/capellacollab/core/authentication/routes.py index 82efdb9ac..1974833d4 100644 --- a/backend/capellacollab/core/authentication/routes.py +++ b/backend/capellacollab/core/authentication/routes.py @@ -46,8 +46,8 @@ async def api_get_token( provider: oidc_provider.AbstractOIDCProvider = fastapi.Depends( injectables.get_oidc_provider ), - provider_config: oidc_provider.AbstractOIDCProviderConfig = fastapi.Depends( - injectables.get_oidc_provider_config + oidc_config: oidc_provider.AbstractOIDCProviderConfig = fastapi.Depends( + injectables.get_oidc_config ), ): tokens = provider.exchange_code_for_tokens( @@ -55,7 +55,7 @@ async def api_get_token( ) validated_id_token = validate_id_token( - tokens["id_token"], provider_config, None + tokens["id_token"], oidc_config, None ) user = create_or_update_user(db, validated_id_token) @@ -72,8 +72,8 @@ async def api_refresh_token( provider: oidc_provider.AbstractOIDCProvider = fastapi.Depends( injectables.get_oidc_provider ), - provider_config: oidc_provider.AbstractOIDCProviderConfig = fastapi.Depends( - injectables.get_oidc_provider_config + oidc_config: oidc_provider.AbstractOIDCProviderConfig = fastapi.Depends( + injectables.get_oidc_config ), ): if refresh_token is None or refresh_token == "": @@ -82,7 +82,7 @@ async def api_refresh_token( tokens = provider.refresh_token(refresh_token) validated_id_token = validate_id_token( - tokens["id_token"], provider_config, None + tokens["id_token"], oidc_config, None ) user = create_or_update_user(db, validated_id_token) @@ -102,11 +102,11 @@ async def validate_token( request: fastapi.Request, scope: users_models.Role | None = None, db: orm.Session = fastapi.Depends(database.get_db), - provider_config: oidc_provider.AbstractOIDCProviderConfig = fastapi.Depends( - injectables.get_oidc_provider_config + oidc_config: oidc_provider.AbstractOIDCProviderConfig = fastapi.Depends( + injectables.get_oidc_config ), ): - username = await api_key_cookie.JWTAPIKeyCookie(provider_config)(request) + username = await api_key_cookie.JWTAPIKeyCookie(oidc_config)(request) if scope and scope.ADMIN: auth_injectables.RoleVerification( required_role=users_models.Role.ADMIN @@ -116,17 +116,17 @@ async def validate_token( def validate_id_token( id_token: str, - provider_config: oidc_provider.AbstractOIDCProviderConfig, + oidc_config: oidc_provider.AbstractOIDCProviderConfig, nonce: str | None, ) -> dict[str, str]: validated_id_token = api_key_cookie.JWTAPIKeyCookie( - provider_config + oidc_config ).validate_token(id_token) if nonce and not hmac.compare_digest(validated_id_token["nonce"], nonce): raise exceptions.NonceMismatchError() - if provider_config.get_client_id() not in validated_id_token["aud"]: + if oidc_config.get_client_id() not in validated_id_token["aud"]: raise exceptions.UnauthenticatedError() return validated_id_token diff --git a/backend/capellacollab/core/logging/__init__.py b/backend/capellacollab/core/logging/__init__.py index 0bc5be658..58cef5f9f 100644 --- a/backend/capellacollab/core/logging/__init__.py +++ b/backend/capellacollab/core/logging/__init__.py @@ -80,11 +80,9 @@ async def dispatch( call_next: base.RequestResponseEndpoint, ): try: - oidc_provider_config = ( - await auth_injectables.get_oidc_provider_config() - ) + oidc_config = await auth_injectables.get_oidc_config() username = await auth_injectables.get_username( - request, oidc_provider_config + request, oidc_config ) except fastapi.HTTPException: username = "anonymous" diff --git a/backend/tests/conftest.py b/backend/tests/conftest.py index 1218ba50d..da5343f59 100644 --- a/backend/tests/conftest.py +++ b/backend/tests/conftest.py @@ -18,6 +18,7 @@ from testcontainers import postgres from capellacollab.__main__ import app +from capellacollab.core.authentication import oidc_provider from capellacollab.core.database import migration os.environ["DEVELOPMENT_MODE"] = "1" @@ -93,8 +94,8 @@ def commit(*args, **kwargs): @pytest.fixture() def client(monkeypatch: pytest.MonkeyPatch) -> testclient.TestClient: monkeypatch.setattr( - "capellacollab.core.authentication.api_key_cookie.JWTConfigBorg", - core_conftest.MockJWTConfigBorg, + "capellacollab.core.authentication.api_key_cookie.JWTConfig", + core_conftest.MockJWTConfig, ) return testclient.TestClient(app, cookies={"id_token": "any"}) @@ -103,3 +104,15 @@ def client(monkeypatch: pytest.MonkeyPatch) -> testclient.TestClient: @pytest.fixture(name="logger") def fixture_logger() -> logging.LoggerAdapter: return logging.LoggerAdapter(logging.getLogger()) + + +@pytest.fixture(name="mock_oidc_config") +def fixture_mock_oidc_config(): + return core_conftest.MockOIDCProviderConfig() + + +@pytest.fixture(name="mock_oidc_provider") +def fixture_mock_oidc_provider( + mock_oidc_config: oidc_provider.AbstractOIDCProviderConfig, +): + return core_conftest.MockOIDCProvider(mock_oidc_config) diff --git a/backend/tests/core/conftest.py b/backend/tests/core/conftest.py index 5ab7c365f..3be79f875 100644 --- a/backend/tests/core/conftest.py +++ b/backend/tests/core/conftest.py @@ -2,10 +2,14 @@ # SPDX-License-Identifier: Apache-2.0 +import typing as t + import pytest from fastapi import testclient from capellacollab.__main__ import app +from capellacollab.core.authentication import injectables as auth_injectables +from capellacollab.core.authentication import oidc_provider class MockPyJWK: @@ -18,24 +22,103 @@ def get_signing_key_from_jwt(self, token: str): return MockPyJWK() -class MockJWTConfigBorg: - _shared_state: dict[str, str] = {} +class MockJWTConfig: + def __init__( + self, oidc_config: oidc_provider.AbstractOIDCProviderConfig + ) -> None: + self.oidc_config = oidc_config + self.jwks_client = MockJWKSClient() + + +class MockOIDCProviderConfig(oidc_provider.AbstractOIDCProviderConfig): + def get_authorization_endpoint(self) -> str: + return "mock-authorization-endpoint" + + def get_token_endpoint(self) -> str: + return "mock-token-endpoint" + + def get_jwks_uri(self) -> str: + return "mock-jwks-uri" + + def get_supported_signing_algorithms(self) -> list[str]: + return ["RS256"] + + def get_issuer(self) -> str: + return "mock-issuer" + + def get_scopes(self) -> list[str]: + return ["openid", "offline_access", "email"] + + def get_client_secret(self) -> str: + return "mock-secret" + + def get_client_id(self) -> str: + return "mock-client-id" - def __init__(self) -> None: - self.__dict__ = self._shared_state - if not hasattr(self, "_jwks_client"): - self.jwks_client = MockJWKSClient() +class MockOIDCProvider(oidc_provider.AbstractOIDCProvider): + def __init__(self, oidc_config: oidc_provider.AbstractOIDCProviderConfig): + super().__init__(oidc_config) + self.oidc_config = oidc_config - if not hasattr(self, "_supported_signing_algorithms"): - self.supported_signing_algorithms = ["RS256"] + def get_authorization_url_with_parameters( + self, + ) -> t.Tuple[str, str, str, str]: + return ( + "mock-auth-url", + "mock-state", + "mock-nonce", + "mock-code-verifier", + ) + def exchange_code_for_tokens( + self, authorization_code: str, code_verifier: str + ) -> dict[str, t.Any]: + return { + "id_token": "mock-id-token", + "access-token": "mock-access-token", + "refresh-token": "mock-refresh-token", + } + def refresh_token(self, _refresh_token: str) -> dict[str, t.Any]: + return { + "id_token": "mock-id-token", + "access-token": "mock-access-token", + "refresh-token": "mock-refresh-token", + } + + +@pytest.fixture(name="mock_oidc_provider_and_config") +def fixture_mock_oidc_provider_and_config( + mock_oidc_config: oidc_provider.AbstractOIDCProviderConfig, + mock_oidc_provider: oidc_provider.AbstractOIDCProvider, +): + async def get_mock_oidc_config() -> ( + oidc_provider.AbstractOIDCProviderConfig + ): + return mock_oidc_config + + async def get_mock_oidc_provider() -> oidc_provider.AbstractOIDCProvider: + return mock_oidc_provider + + app.dependency_overrides[auth_injectables.get_oidc_config] = ( + get_mock_oidc_config + ) + app.dependency_overrides[auth_injectables.get_oidc_provider] = ( + get_mock_oidc_provider + ) + + yield + + del app.dependency_overrides[auth_injectables.get_oidc_config] + del app.dependency_overrides[auth_injectables.get_oidc_provider] + + +@pytest.mark.usefixtures("mock_oidc_provider_and_config") @pytest.fixture(name="unauthorized_client") def fixture_unauthorized_client(monkeypatch: pytest.MonkeyPatch): monkeypatch.setattr( - "capellacollab.core.authentication.api_key_cookie.JWTConfigBorg", - MockJWTConfigBorg, + "capellacollab.core.authentication.api_key_cookie.JWTConfig", + MockJWTConfig, ) - return testclient.TestClient(app) diff --git a/backend/tests/core/test_auth.py b/backend/tests/core/test_auth.py index 4ace9293f..6f47388e4 100644 --- a/backend/tests/core/test_auth.py +++ b/backend/tests/core/test_auth.py @@ -6,90 +6,15 @@ import pytest from fastapi import status, testclient -from sqlalchemy import orm from capellacollab.core.authentication import ( api_key_cookie, exceptions, - flow, + oidc_provider, routes, ) -@pytest.fixture(name="mock_auth_endpoints") -def fixture_mock_auth_endpoints(monkeypatch: pytest.MonkeyPatch): - def mock_get_auth_endpoints() -> flow.AuthEndpoints: - return { - "authorization_endpoint": "https://pytest.mock/authorize", - "token_endpoint": "https://pytest.mock/token", - "jwks_uri": "https://pytest.mock/jwks_uri", - } - - monkeypatch.setattr(flow, "get_auth_endpoints", mock_get_auth_endpoints) - - -@pytest.mark.usefixtures("mock_auth_endpoints") -def test_get_authorization_url_with_parameters( - monkeypatch: pytest.MonkeyPatch, -): - mock_generate_token = mock.Mock(return_value="mock-token") - monkeypatch.setattr("oauthlib.common.generate_token", mock_generate_token) - - mock_generate_nonce = mock.Mock(return_value="mock-nonce") - monkeypatch.setattr("oauthlib.common.generate_nonce", mock_generate_nonce) - - monkeypatch.setattr( - flow.auth_config, "redirect_uri", "https://pytest.mock/callback" - ) - monkeypatch.setattr(flow.web_client, "client_id", "mock-clientID") - monkeypatch.setattr(flow.auth_config, "scopes", ["openid", "profile"]) - - auth_url, state, nonce, _ = flow.get_authorization_url_with_parameters() - - assert "https://pytest.mock/authorize" in auth_url - assert "response_type=code" in auth_url - assert "client_id=mock-clientID" in auth_url - assert "redirect_uri=https%3A%2F%2Fpytest.mock%2Fcallback" in auth_url - assert state == "mock-token" - assert "state=mock-token" in auth_url - assert nonce == "mock-nonce" - assert "nonce=mock-nonce" in auth_url - assert "code_challenge" in auth_url - assert f"code_challenge_method={flow.CODE_CHALLENGE_METHOD}" in auth_url - - -def test_get_redirect_url( - unauthorized_client: testclient.TestClient, monkeypatch: pytest.MonkeyPatch -): - def mock_get_authorization_url_with_parameters(): - return ( - "mock-auth_url", - "mock-state", - "mock-nonce", - "mock-code_verifier", - ) - - monkeypatch.setattr( - flow, - "get_authorization_url_with_parameters", - mock_get_authorization_url_with_parameters, - ) - - response = unauthorized_client.get("api/v1/authentication") - json_response = response.json() - - cookies = "".join(response.headers.get_list("set-cookie")) - - assert response.status_code == 200 - assert "auth_url" in json_response - assert "state" in json_response - assert "nonce" in json_response - assert "code_verifier" in json_response - - assert 'id_token=""' in cookies - assert 'refresh_token=""' in cookies - - def test_missing_refresh_token(unauthorized_client: testclient.TestClient): response = unauthorized_client.put("api/v1/authentication/tokens") json_response = response.json() @@ -100,7 +25,8 @@ def test_missing_refresh_token(unauthorized_client: testclient.TestClient): @pytest.mark.usefixtures("mock_auth_endpoints") def test_validate_id_token_nonce_mismatch( - db: orm.Session, monkeypatch: pytest.MonkeyPatch + mock_oidc_config: oidc_provider.AbstractOIDCProviderConfig, + monkeypatch: pytest.MonkeyPatch, ): mock_jwt_api_cookie = mock.MagicMock() mock_jwt_api_cookie.return_value.validate_token.return_value = { @@ -110,12 +36,13 @@ def test_validate_id_token_nonce_mismatch( monkeypatch.setattr(api_key_cookie, "JWTAPIKeyCookie", mock_jwt_api_cookie) with pytest.raises(exceptions.NonceMismatchError): - routes.validate_id_token(db, "any", "correct-nonce") + routes.validate_id_token("any", mock_oidc_config, "correct-nonce") @pytest.mark.usefixtures("mock_auth_endpoints") def test_validate_id_token_audience_mismatch( - db: orm.Session, monkeypatch: pytest.MonkeyPatch + mock_oidc_config: oidc_provider.AbstractOIDCProviderConfig, + monkeypatch: pytest.MonkeyPatch, ): mock_jwt_api_cookie = mock.MagicMock() mock_jwt_api_cookie.return_value.validate_token.return_value = { @@ -124,7 +51,6 @@ def test_validate_id_token_audience_mismatch( } monkeypatch.setattr(api_key_cookie, "JWTAPIKeyCookie", mock_jwt_api_cookie) - monkeypatch.setattr(flow.auth_config.client, "id", "mismatch-clientId") with pytest.raises(exceptions.UnauthenticatedError): - routes.validate_id_token(db, "any", "mock-nonce") + routes.validate_id_token("any", mock_oidc_config, "mock-nonce") diff --git a/backend/tests/core/test_auth_injectables.py b/backend/tests/core/test_auth_injectables.py index 7b637dcc5..e8d22fa56 100644 --- a/backend/tests/core/test_auth_injectables.py +++ b/backend/tests/core/test_auth_injectables.py @@ -20,12 +20,16 @@ def fixture_verify(request: pytest.FixtureRequest) -> bool: @pytest.fixture(name="user2") def fixture_user2(db: orm.Session) -> users_models.DatabaseUser: - return users_crud.create_user(db, "user2", users_models.Role.USER) + return users_crud.create_user( + db, "user2", "user2", None, users_models.Role.USER + ) @pytest.fixture(name="admin2") def fixture_admin2(db: orm.Session) -> users_models.DatabaseUser: - return users_crud.create_user(db, "admin2", users_models.Role.ADMIN) + return users_crud.create_user( + db, "admin2", "admin2", None, users_models.Role.ADMIN + ) def test_role_verification_user_not_found(db: orm.Session, verify: bool): @@ -106,7 +110,11 @@ def fixture_project_user_lead( db: orm.Session, project: projects_models.DatabaseProject ) -> projects_users_models.ProjectUserAssociation: user = users_crud.create_user( - db, "project_user_lead", users_models.Role.USER + db, + "project_user_lead", + "project_user_lead", + None, + users_models.Role.USER, ) return projects_users_crud.add_user_to_project( db, @@ -122,7 +130,11 @@ def fixture_project_user_write( db: orm.Session, project: projects_models.DatabaseProject ) -> projects_users_models.ProjectUserAssociation: user = users_crud.create_user( - db, "project_user_write", users_models.Role.USER + db, + "project_user_write", + "project_user_write", + None, + users_models.Role.USER, ) return projects_users_crud.add_user_to_project( db, @@ -138,7 +150,11 @@ def fixture_project_user_read( db: orm.Session, project: projects_models.DatabaseProject ) -> projects_users_models.ProjectUserAssociation: user = users_crud.create_user( - db, "project_user_read", users_models.Role.USER + db, + "project_user_read", + "project_user_read", + None, + users_models.Role.USER, ) return projects_users_crud.add_user_to_project( db, diff --git a/backend/tests/projects/test_projects_routes.py b/backend/tests/projects/test_projects_routes.py index e6e71de87..29b6ff917 100644 --- a/backend/tests/projects/test_projects_routes.py +++ b/backend/tests/projects/test_projects_routes.py @@ -21,7 +21,9 @@ def test_get_internal_default_project_as_user( client: testclient.TestClient, db: orm.Session, executor_name: str ): - users_crud.create_user(db, executor_name, users_models.Role.USER) + users_crud.create_user( + db, executor_name, executor_name, None, users_models.Role.USER + ) response = client.get("/api/v1/projects/default") @@ -40,7 +42,9 @@ def test_get_internal_default_project_as_user( def test_get_projects_as_user_only_shows_default_internal_project( client: testclient.TestClient, db: orm.Session, executor_name: str ): - users_crud.create_user(db, executor_name, users_models.Role.USER) + users_crud.create_user( + db, executor_name, executor_name, None, users_models.Role.USER + ) response = client.get("/api/v1/projects") @@ -74,7 +78,9 @@ def test_get_projects_as_admin( project = projects_crud.create_project( db, "test project", visibility=projects_models.Visibility.PRIVATE ) - users_crud.create_user(db, executor_name, users_models.Role.ADMIN) + users_crud.create_user( + db, executor_name, executor_name, None, users_models.Role.ADMIN + ) response = client.get("/api/v1/projects") @@ -93,7 +99,9 @@ def test_get_internal_projects_as_user( project = projects_crud.create_project( db, "test project", visibility=projects_models.Visibility.INTERNAL ) - users_crud.create_user(db, executor_name, users_models.Role.USER) + users_crud.create_user( + db, executor_name, executor_name, None, users_models.Role.USER + ) response = client.get("/api/v1/projects") @@ -109,7 +117,9 @@ def test_get_internal_projects_as_user( def test_get_internal_projects_as_user_without_duplicates( client: testclient.TestClient, db: orm.Session, executor_name: str ): - user = users_crud.create_user(db, executor_name, users_models.Role.USER) + user = users_crud.create_user( + db, executor_name, executor_name, None, users_models.Role.USER + ) project = projects_crud.create_project( db, "test project", visibility=projects_models.Visibility.INTERNAL ) @@ -141,7 +151,9 @@ def test_get_internal_projects_as_user_without_duplicates( def test_create_private_project_as_admin( client: testclient.TestClient, db: orm.Session, executor_name: str ): - users_crud.create_user(db, executor_name, users_models.Role.ADMIN) + users_crud.create_user( + db, executor_name, executor_name, None, users_models.Role.ADMIN + ) response = client.post( "/api/v1/projects/", @@ -162,7 +174,9 @@ def test_create_private_project_as_admin( def test_create_internal_project_as_admin( client: testclient.TestClient, db: orm.Session, executor_name: str ): - users_crud.create_user(db, executor_name, users_models.Role.ADMIN) + users_crud.create_user( + db, executor_name, executor_name, None, users_models.Role.ADMIN + ) response = client.post( "/api/v1/projects/", @@ -184,7 +198,9 @@ def test_create_internal_project_as_admin( def test_update_project_as_admin( client: testclient.TestClient, db: orm.Session, executor_name: str ): - users_crud.create_user(db, executor_name, users_models.Role.ADMIN) + users_crud.create_user( + db, executor_name, executor_name, None, users_models.Role.ADMIN + ) project = projects_crud.create_project(db, "new project") assert project.slug == "new-project" @@ -248,7 +264,9 @@ def test_delete_pipeline_called_when_archiving_project( mock_model = mock.Mock(name="DatabaseModel") mock_pipeline = mock.Mock(name="DatabaseBackup") - users_crud.create_user(db, executor_name, users_models.Role.ADMIN) + users_crud.create_user( + db, executor_name, executor_name, None, users_models.Role.ADMIN + ) mock_project.models = [mock_model] with ( @@ -300,7 +318,9 @@ def test_get_project_per_role_manager(client: testclient.TestClient): def test_get_project_per_role_admin( client: testclient.TestClient, executor_name: str, db: orm.Session ): - users_crud.create_user(db, executor_name, users_models.Role.ADMIN) + users_crud.create_user( + db, executor_name, executor_name, None, users_models.Role.ADMIN + ) response = client.get("/api/v1/projects/?minimum_role=administrator") assert response.status_code == 200 diff --git a/backend/tests/projects/test_projects_users_routes.py b/backend/tests/projects/test_projects_users_routes.py index 63b0b1d95..3b5f59bf9 100644 --- a/backend/tests/projects/test_projects_users_routes.py +++ b/backend/tests/projects/test_projects_users_routes.py @@ -20,8 +20,12 @@ def test_assign_read_write_permission_when_adding_manager( unique_username: str, project: projects_models.DatabaseProject, ): - users_crud.create_user(db, executor_name, users_models.Role.ADMIN) - user = users_crud.create_user(db, unique_username, users_models.Role.USER) + users_crud.create_user( + db, executor_name, executor_name, None, users_models.Role.ADMIN + ) + user = users_crud.create_user( + db, unique_username, unique_username, None, users_models.Role.USER + ) response = client.post( f"/api/v1/projects/{project.slug}/users/", @@ -53,8 +57,12 @@ def test_assign_read_write_permission_when_changing_project_role_to_manager( unique_username: str, project: projects_models.DatabaseProject, ): - users_crud.create_user(db, executor_name, users_models.Role.ADMIN) - user = users_crud.create_user(db, unique_username, users_models.Role.USER) + users_crud.create_user( + db, executor_name, executor_name, None, users_models.Role.ADMIN + ) + user = users_crud.create_user( + db, unique_username, unique_username, None, users_models.Role.USER + ) projects_users_crud.add_user_to_project( db, @@ -92,8 +100,12 @@ def test_http_exception_when_updating_permission_of_manager( unique_username: str, project: projects_models.DatabaseProject, ): - users_crud.create_user(db, executor_name, users_models.Role.ADMIN) - user = users_crud.create_user(db, unique_username, users_models.Role.USER) + users_crud.create_user( + db, executor_name, executor_name, None, users_models.Role.ADMIN + ) + user = users_crud.create_user( + db, unique_username, unique_username, None, users_models.Role.USER + ) projects_users_crud.add_user_to_project( db, @@ -131,7 +143,9 @@ def test_current_user_rights_for_internal_project( visibility=projects_models.Visibility.INTERNAL ), ) - users_crud.create_user(db, executor_name, users_models.Role.USER) + users_crud.create_user( + db, executor_name, executor_name, None, users_models.Role.USER + ) response = client.get( f"/api/v1/projects/{project.slug}/users/current", @@ -155,7 +169,9 @@ def test_no_user_rights_on_internal_permissions( visibility=projects_models.Visibility.PRIVATE ), ) - users_crud.create_user(db, executor_name, users_models.Role.USER) + users_crud.create_user( + db, executor_name, executor_name, None, users_models.Role.USER + ) response = client.get( f"/api/v1/projects/{project.slug}/users/current", diff --git a/backend/tests/projects/toolmodels/pipelines/pipeline-runs/test_pipeline_runs.py b/backend/tests/projects/toolmodels/pipelines/pipeline-runs/test_pipeline_runs.py index ad8f0d244..f285acb1d 100644 --- a/backend/tests/projects/toolmodels/pipelines/pipeline-runs/test_pipeline_runs.py +++ b/backend/tests/projects/toolmodels/pipelines/pipeline-runs/test_pipeline_runs.py @@ -196,7 +196,9 @@ def test_mask_logs( db: orm.Session, executor_name: str, ): - users_crud.create_user(db, executor_name, users_models.Role.ADMIN) + users_crud.create_user( + db, executor_name, executor_name, None, users_models.Role.ADMIN + ) mock_fetch_logs.return_value = [ { diff --git a/backend/tests/projects/toolmodels/test_toolmodel_routes.py b/backend/tests/projects/toolmodels/test_toolmodel_routes.py index 293fd807f..9ed96f258 100644 --- a/backend/tests/projects/toolmodels/test_toolmodel_routes.py +++ b/backend/tests/projects/toolmodels/test_toolmodel_routes.py @@ -49,7 +49,9 @@ def test_rename_toolmodel_successful( executor_name: str, db: orm.Session, ): - users_crud.create_user(db, executor_name, users_models.Role.ADMIN) + users_crud.create_user( + db, executor_name, executor_name, None, users_models.Role.ADMIN + ) response = client.patch( f"/api/v1/projects/{project.slug}/models/{capella_model.slug}", @@ -70,7 +72,9 @@ def test_rename_toolmodel_where_name_already_exists( executor_name: str, db: orm.Session, ): - users_crud.create_user(db, executor_name, users_models.Role.ADMIN) + users_crud.create_user( + db, executor_name, executor_name, None, users_models.Role.ADMIN + ) with mock.patch( "capellacollab.projects.toolmodels.crud.get_model_by_slugs", @@ -97,7 +101,9 @@ def test_update_toolmodel_order_successful( executor_name: str, db: orm.Session, ): - users_crud.create_user(db, executor_name, users_models.Role.ADMIN) + users_crud.create_user( + db, executor_name, executor_name, None, users_models.Role.ADMIN + ) response = client.patch( f"/api/v1/projects/{project.slug}/models/{capella_model.slug}", diff --git a/backend/tests/sessions/hooks/test_t4c_hook.py b/backend/tests/sessions/hooks/test_t4c_hook.py index 9945294ee..458c13b6b 100644 --- a/backend/tests/sessions/hooks/test_t4c_hook.py +++ b/backend/tests/sessions/hooks/test_t4c_hook.py @@ -235,7 +235,11 @@ def test_t4c_connection_hook_shared_session( session: sessions_models.DatabaseSession, ): user2 = users_crud.create_user( - db, "shared_with_user", users_models.Role.USER + db, + "shared_with_user", + "shared_with_user", + None, + users_models.Role.USER, ) session.owner = user2 result = t4c.T4CIntegration().session_connection_hook( diff --git a/backend/tests/sessions/test_session_routes.py b/backend/tests/sessions/test_session_routes.py index ca2663e68..1f3b15d0b 100644 --- a/backend/tests/sessions/test_session_routes.py +++ b/backend/tests/sessions/test_session_routes.py @@ -267,7 +267,7 @@ def test_own_sessions( tool_version: tools_models.DatabaseVersion, ): another_user = users_crud.create_user( - db, "other-user", users_models.Role.USER + db, "other-user", "other-user", None, users_models.Role.USER ) session_of_other_user = sessions_models.DatabaseSession( diff --git a/backend/tests/sessions/test_session_sharing.py b/backend/tests/sessions/test_session_sharing.py index bb690e28b..e71be0c41 100644 --- a/backend/tests/sessions/test_session_sharing.py +++ b/backend/tests/sessions/test_session_sharing.py @@ -28,7 +28,11 @@ def fixture_enable_tool_session_sharing( @pytest.fixture(name="shared_with_user") def fixture_shared_with_user(db: orm.Session) -> users_models.DatabaseUser: user2 = users_crud.create_user( - db, "shared_with_user", users_models.Role.USER + db, + "shared_with_user", + "shared_with_user", + None, + users_models.Role.USER, ) return user2 @@ -86,7 +90,9 @@ def test_session_is_already_shared( client: testclient.TestClient, db: orm.Session, ): - user2 = users_crud.create_user(db, "user2", users_models.Role.USER) + user2 = users_crud.create_user( + db, "user2", "user2", None, users_models.Role.USER + ) response = client.post( f"/api/v1/sessions/{session.id}/shares", json={ @@ -130,7 +136,9 @@ def test_share_session_not_owned( shared_session: sessions_models.DatabaseSession, client: testclient.TestClient, ): - user3 = users_crud.create_user(db, "user3", users_models.Role.USER) + user3 = users_crud.create_user( + db, "user3", "user3", None, users_models.Role.USER + ) response = client.post( f"/api/v1/sessions/{shared_session.id}/shares", @@ -161,7 +169,9 @@ def test_share_session( client: testclient.TestClient, db: orm.Session, ): - user2 = users_crud.create_user(db, "user2", users_models.Role.USER) + user2 = users_crud.create_user( + db, "user2", "user2", None, users_models.Role.USER + ) response = client.post( f"/api/v1/sessions/{session.id}/shares", json={ @@ -224,7 +234,9 @@ def test_tool_doesnt_support_sharing( db: orm.Session, client: testclient.TestClient, ): - user2 = users_crud.create_user(db, "user2", users_models.Role.USER) + user2 = users_crud.create_user( + db, "user2", "user2", None, users_models.Role.USER + ) response = client.post( f"/api/v1/sessions/{session.id}/shares", json={ diff --git a/backend/tests/settings/teamforcapella/test_t4c_instances.py b/backend/tests/settings/teamforcapella/test_t4c_instances.py index 99f6911c6..63e1836c4 100644 --- a/backend/tests/settings/teamforcapella/test_t4c_instances.py +++ b/backend/tests/settings/teamforcapella/test_t4c_instances.py @@ -89,7 +89,9 @@ def test_create_t4c_instance_already_existing_name( def test_get_t4c_instances( client: testclient.TestClient, db: orm.Session, executor_name: str ): - users_crud.create_user(db, executor_name, users_models.Role.ADMIN) + users_crud.create_user( + db, executor_name, executor_name, None, users_models.Role.ADMIN + ) response = client.get( "/api/v1/settings/modelsources/t4c", @@ -109,7 +111,9 @@ def test_get_t4c_instance( executor_name: str, t4c_instance: t4c_models.DatabaseT4CInstance, ): - users_crud.create_user(db, executor_name, users_models.Role.ADMIN) + users_crud.create_user( + db, executor_name, executor_name, None, users_models.Role.ADMIN + ) response = client.get( f"/api/v1/settings/modelsources/t4c/{t4c_instance.id}", @@ -127,7 +131,9 @@ def test_patch_t4c_instance( executor_name: str, t4c_instance: t4c_models.DatabaseT4CInstance, ): - users_crud.create_user(db, executor_name, users_models.Role.ADMIN) + users_crud.create_user( + db, executor_name, executor_name, None, users_models.Role.ADMIN + ) response = client.patch( f"/api/v1/settings/modelsources/t4c/{t4c_instance.id}", @@ -156,7 +162,9 @@ def test_patch_archived_t4c_instance_error( executor_name: str, t4c_instance: t4c_models.DatabaseT4CInstance, ): - users_crud.create_user(db, executor_name, users_models.Role.ADMIN) + users_crud.create_user( + db, executor_name, executor_name, None, users_models.Role.ADMIN + ) t4c_crud.update_t4c_instance( db, t4c_instance, t4c_models.PatchT4CInstance(is_archived=True) @@ -178,7 +186,9 @@ def test_unarchive_t4c_instance( executor_name: str, t4c_instance: t4c_models.DatabaseT4CInstance, ): - users_crud.create_user(db, executor_name, users_models.Role.ADMIN) + users_crud.create_user( + db, executor_name, executor_name, None, users_models.Role.ADMIN + ) t4c_crud.update_t4c_instance( db, t4c_instance, t4c_models.PatchT4CInstance(is_archived=True) @@ -253,7 +263,9 @@ def test_injectables_raise_when_archived_instance( executor_name: str, t4c_instance: t4c_models.DatabaseT4CInstance, ): - users_crud.create_user(db, executor_name, users_models.Role.ADMIN) + users_crud.create_user( + db, executor_name, executor_name, None, users_models.Role.ADMIN + ) t4c_crud.update_t4c_instance( db, t4c_instance, t4c_models.PatchT4CInstance(is_archived=True) @@ -271,7 +283,9 @@ def test_update_t4c_instance_password_empty_string( executor_name: str, t4c_instance: t4c_models.DatabaseT4CInstance, ): - users_crud.create_user(db, executor_name, users_models.Role.ADMIN) + users_crud.create_user( + db, executor_name, executor_name, None, users_models.Role.ADMIN + ) expected_password = t4c_instance.password @@ -297,7 +311,9 @@ def test_get_t4c_license_usage( executor_name: str, t4c_instance: t4c_models.DatabaseT4CInstance, ): - users_crud.create_user(db, executor_name, users_models.Role.ADMIN) + users_crud.create_user( + db, executor_name, executor_name, None, users_models.Role.ADMIN + ) response = client.get( f"/api/v1/settings/modelsources/t4c/{t4c_instance.id}/licenses", ) @@ -314,7 +330,9 @@ def test_get_t4c_license_usage_no_status( executor_name: str, t4c_instance: t4c_models.DatabaseT4CInstance, ): - users_crud.create_user(db, executor_name, users_models.Role.ADMIN) + users_crud.create_user( + db, executor_name, executor_name, None, users_models.Role.ADMIN + ) responses.get( "http://localhost:8086/status/json", status=status.HTTP_200_OK, diff --git a/backend/tests/settings/test_alerts.py b/backend/tests/settings/test_alerts.py index 5420c5afc..48893596b 100644 --- a/backend/tests/settings/test_alerts.py +++ b/backend/tests/settings/test_alerts.py @@ -14,7 +14,9 @@ def test_get_alerts(client: TestClient, db: orm.Session, executor_name: str): - users_crud.create_user(db, executor_name, users_models.Role.USER) + users_crud.create_user( + db, executor_name, executor_name, None, users_models.Role.USER + ) notices_crud.create_notice( db, notices_models.CreateNoticeRequest( @@ -38,7 +40,9 @@ def test_get_alerts(client: TestClient, db: orm.Session, executor_name: str): def test_create_alert2( client: TestClient, db: orm.Session, executor_name: str ): - users_crud.create_user(db, executor_name, users_models.Role.ADMIN) + users_crud.create_user( + db, executor_name, executor_name, None, users_models.Role.ADMIN + ) response = client.post( "/api/v1/notices", @@ -57,7 +61,9 @@ def test_create_alert2( def test_delete_alert(client: TestClient, db: orm.Session, executor_name: str): - users_crud.create_user(db, executor_name, users_models.Role.ADMIN) + users_crud.create_user( + db, executor_name, executor_name, None, users_models.Role.ADMIN + ) alert = notices_crud.create_notice( db, notices_models.CreateNoticeRequest( diff --git a/backend/tests/settings/test_global_configuration.py b/backend/tests/settings/test_global_configuration.py index aa6b5022c..2c8531939 100644 --- a/backend/tests/settings/test_global_configuration.py +++ b/backend/tests/settings/test_global_configuration.py @@ -111,7 +111,9 @@ def test_update_general_configuration_additional_properties_fails( def test_metadata_is_updated( client: testclient.TestClient, db: orm.Session, executor_name: str ): - admin = users_crud.create_user(db, executor_name, users_models.Role.ADMIN) + admin = users_crud.create_user( + db, executor_name, executor_name, None, users_models.Role.ADMIN + ) def get_mock_own_user(): return admin diff --git a/backend/tests/test_event_creation.py b/backend/tests/test_event_creation.py index 13d379bcf..2589dc684 100644 --- a/backend/tests/test_event_creation.py +++ b/backend/tests/test_event_creation.py @@ -37,7 +37,7 @@ def test_create_admin_user_by_system(db): def test_create_user_created_event(client, db, executor_name, unique_username): executor = users_crud.create_user( - db, executor_name, users_models.Role.ADMIN + db, executor_name, executor_name, None, users_models.Role.ADMIN ) response = client.post( @@ -61,7 +61,7 @@ def test_create_user_created_event(client, db, executor_name, unique_username): def test_user_deleted_cleanup(client, db, executor_name, unique_username): executor = users_crud.create_user( - db, executor_name, users_models.Role.ADMIN + db, executor_name, executor_name, None, users_models.Role.ADMIN ) response = client.post( @@ -106,9 +106,11 @@ def test_create_assign_user_role_event( expected_event_type, ): executor = users_crud.create_user( - db, executor_name, users_models.Role.ADMIN + db, executor_name, executor_name, None, users_models.Role.ADMIN + ) + user = users_crud.create_user( + db, unique_username, unique_username, None, initial_role ) - user = users_crud.create_user(db, unique_username, initial_role) response = client.patch( f"/api/v1/users/{user.id}", @@ -152,9 +154,11 @@ def test_create_user_added_to_project_event( expected_permission_event_type, ): executor = users_crud.create_user( - db, executor_name, users_models.Role.ADMIN + db, executor_name, executor_name, None, users_models.Role.ADMIN + ) + user = users_crud.create_user( + db, unique_username, unique_username, None, users_models.Role.USER ) - user = users_crud.create_user(db, unique_username, users_models.Role.USER) response = client.post( f"/api/v1/projects/{project.slug}/users/", @@ -191,9 +195,11 @@ def test_create_user_removed_from_project_event( client, db, executor_name, unique_username, project ): executor = users_crud.create_user( - db, executor_name, users_models.Role.ADMIN + db, executor_name, executor_name, None, users_models.Role.ADMIN + ) + user = users_crud.create_user( + db, unique_username, unique_username, None, users_models.Role.USER ) - user = users_crud.create_user(db, unique_username, users_models.Role.USER) projects_users_crud.add_user_to_project( db, @@ -228,9 +234,11 @@ def test_create_manager_added_to_project_event( client, db, executor_name, unique_username, project ): executor = users_crud.create_user( - db, executor_name, users_models.Role.ADMIN + db, executor_name, executor_name, None, users_models.Role.ADMIN + ) + user = users_crud.create_user( + db, unique_username, unique_username, None, users_models.Role.USER ) - user = users_crud.create_user(db, unique_username, users_models.Role.USER) response = client.post( f"/api/v1/projects/{project.slug}/users/", @@ -287,9 +295,11 @@ def test_create_user_permission_change_event( expected_permission_event_type, ): executor = users_crud.create_user( - db, executor_name, users_models.Role.ADMIN + db, executor_name, executor_name, None, users_models.Role.ADMIN + ) + user = users_crud.create_user( + db, unique_username, unique_username, None, users_models.Role.USER ) - user = users_crud.create_user(db, unique_username, users_models.Role.USER) projects_users_crud.add_user_to_project( db, @@ -347,9 +357,11 @@ def test_create_user_role_change_event( expected_role_event_type, ): executor = users_crud.create_user( - db, executor_name, users_models.Role.ADMIN + db, executor_name, executor_name, None, users_models.Role.ADMIN + ) + user = users_crud.create_user( + db, unique_username, unique_username, None, users_models.Role.USER ) - user = users_crud.create_user(db, unique_username, users_models.Role.USER) projects_users_crud.add_user_to_project( db, diff --git a/backend/tests/users/conftest.py b/backend/tests/users/conftest.py index 60e5a8c2e..38ccb53e4 100644 --- a/backend/tests/users/conftest.py +++ b/backend/tests/users/conftest.py @@ -13,7 +13,9 @@ @pytest.fixture(name="unauthenticated_user") def fixture_unauthenticated_user(db): - user = users_crud.create_user(db, str(uuid1()), users_models.Role.USER) + user = users_crud.create_user( + db, str(uuid1()), str(uuid1()), None, users_models.Role.USER + ) def get_mock_own_user(): return user diff --git a/backend/tests/users/fixtures.py b/backend/tests/users/fixtures.py index d26b37789..4d614dd7e 100644 --- a/backend/tests/users/fixtures.py +++ b/backend/tests/users/fixtures.py @@ -39,7 +39,9 @@ def fixture_unique_username() -> str: def fixture_basic_user( db: orm.Session, executor_name: str ) -> users_models.DatabaseUser: - return users_crud.create_user(db, executor_name, users_models.Role.USER) + return users_crud.create_user( + db, executor_name, executor_name, None, users_models.Role.USER + ) @pytest.fixture(name="user") @@ -60,7 +62,9 @@ def get_mock_own_user(): def fixture_admin( db: orm.Session, executor_name: str ) -> t.Generator[users_models.DatabaseUser, None, None]: - admin = users_crud.create_user(db, executor_name, users_models.Role.ADMIN) + admin = users_crud.create_user( + db, executor_name, executor_name, None, users_models.Role.ADMIN + ) def get_mock_own_user(): return admin @@ -74,7 +78,9 @@ def get_mock_own_user(): @pytest.fixture(name="test_user") def fixture_test_user(db: orm.Session) -> users_models.DatabaseUser: - return users_crud.create_user(db, "testuser", users_models.Role.USER) + return users_crud.create_user( + db, "testuser", "testuser", None, users_models.Role.USER + ) @pytest.fixture(name="user_workspace") diff --git a/backend/tests/users/test_oauth.py b/backend/tests/users/test_oauth.py index 21f517ff1..ec6eee91e 100644 --- a/backend/tests/users/test_oauth.py +++ b/backend/tests/users/test_oauth.py @@ -14,7 +14,9 @@ def test_validate_tokens_routes( client: testclient.TestClient, executor_name: str, ): - users_crud.create_user(db, executor_name, users_models.Role.ADMIN) + users_crud.create_user( + db, executor_name, executor_name, None, users_models.Role.ADMIN + ) response = client.get("/api/v1/authentication/tokens") assert response.status_code == 200 diff --git a/backend/tests/users/test_users.py b/backend/tests/users/test_users.py index e99ee9c4e..f6acc8da6 100644 --- a/backend/tests/users/test_users.py +++ b/backend/tests/users/test_users.py @@ -20,8 +20,10 @@ def test_get_user_by_id_admin( client: testclient.TestClient, db: orm.Session, executor_name: str ): - users_crud.create_user(db, executor_name, users_models.Role.ADMIN) - user = users_crud.create_user(db, "test_user") + users_crud.create_user( + db, executor_name, executor_name, None, users_models.Role.ADMIN + ) + user = users_crud.create_user(db, "test_user", "test_user") response = client.get(f"/api/v1/users/{user.id}") assert response.status_code == 200 @@ -32,7 +34,7 @@ def test_get_user_by_id_admin( def test_get_user_by_id_non_admin( client: testclient.TestClient, db: orm.Session ): - user = users_crud.create_user(db, "test_user") + user = users_crud.create_user(db, "test_user", "test_user") response = client.get(f"/api/v1/users/{user.id}") assert response.status_code == 403 @@ -44,7 +46,7 @@ def test_get_user_by_id_common_project( db: orm.Session, project: projects_models.DatabaseProject, ): - user2 = users_crud.create_user(db, "user2") + user2 = users_crud.create_user(db, "user2", "user2") projects_users_crud.add_user_to_project( db, project, @@ -62,7 +64,7 @@ def test_get_user_by_id_common_project( def test_get_no_common_projects( client: testclient.TestClient, db: orm.Session ): - user2 = users_crud.create_user(db, "user2") + user2 = users_crud.create_user(db, "user2", "user2") response = client.get(f"/api/v1/users/{user2.id}/common-projects") assert response.status_code == 200 assert len(response.json()) == 0 @@ -74,7 +76,7 @@ def test_get_common_projects( db: orm.Session, project: projects_models.DatabaseProject, ): - user2 = users_crud.create_user(db, "user2") + user2 = users_crud.create_user(db, "user2", "user2") projects_users_crud.add_user_to_project( db, project,