diff --git a/config.docker.yml b/config.docker.yml index 2e33f8c4..3e501fcd 100644 --- a/config.docker.yml +++ b/config.docker.yml @@ -19,13 +19,16 @@ auth: # Keycloak Auth # provider: syncmaster.server.providers.auth.keycloak_provider.KeycloakAuthProvider # keycloak: - # server_url: http://keycloak:8080 + # api_url: http://keycloak:8080/auth # realm_name: manually_created # client_id: manually_created # client_secret: generated_by_keycloak - # redirect_uri: http://localhost:3000/auth/callback + # ui_auth_callback_url: http://localhost:3000/auth/callback # scope: email # verify_ssl: False + # cookie: + # secret_key: generate_some_random_string + # max_age: 86400 ui: @@ -37,11 +40,6 @@ ui: server: debug: true # !!! NEVER USE ON PRODUCTION !!! - session: - enabled: true - secret_key: generate_some_random_string - max_age: 86400 - cors: enabled: true diff --git a/config.yml b/config.yml index 4cdb89b4..a59a50ba 100644 --- a/config.yml +++ b/config.yml @@ -19,13 +19,16 @@ auth: # Keycloak Auth # provider: syncmaster.server.providers.auth.keycloak_provider.KeycloakAuthProvider # keycloak: - # server_url: http://localhost:8080 + # api_url: http://localhost:8080/auth # realm_name: manually_created # client_id: manually_created # client_secret: generated_by_keycloak - # redirect_uri: http://localhost:3000/auth/callback + # ui_auth_callback_url: http://localhost:3000/auth/callback # scope: email # verify_ssl: False + # cookie: + # secret_key: generate_some_random_string + # max_age: 86400 ui: @@ -37,11 +40,6 @@ ui: server: debug: true # !!! NEVER USE ON PRODUCTION !!! - session: - enabled: true - secret_key: generate_some_random_string - max_age: 86400 - cors: enabled: true allow_origins: [http://localhost:3000] diff --git a/docs/changelog/next_release/304.breaking.rst b/docs/changelog/next_release/304.breaking.rst new file mode 100644 index 00000000..29b6bb49 --- /dev/null +++ b/docs/changelog/next_release/304.breaking.rst @@ -0,0 +1,29 @@ +Move ``server.session`` middleware settings to ``auth`` block. +Also rename some fields in ``auth.keycloak`` settings block. + +Before: + +.. code:: yaml + + auth: + provider: ... + keycloak: + server_url: ... + redirect_url: ... + + server: + session: + enabled: true + secret_key: ... + +Now: + +.. code:: yaml + + auth: + provider: + keycloak: + api_url: ... + ui_callback_url: ... + cookie: + secret_key: diff --git a/docs/reference/server/auth/keycloak/index.rst b/docs/reference/server/auth/keycloak/index.rst index fc86741c..67839cd7 100644 --- a/docs/reference/server/auth/keycloak/index.rst +++ b/docs/reference/server/auth/keycloak/index.rst @@ -79,7 +79,7 @@ Basic configuration .. autopydantic_model:: syncmaster.server.settings.auth.keycloak.KeycloakAuthProviderSettings .. autopydantic_model:: syncmaster.server.settings.auth.keycloak.KeycloakSettings -.. autopydantic_model:: syncmaster.server.settings.auth.jwt.JWTSettings +.. autopydantic_model:: syncmaster.server.settings.auth.keycloak.KeycloakCookieSettings OAuth2 Gateway Provider @@ -95,9 +95,10 @@ This provider ensures integration with OAuth2 Gateway and maintains the standard **Configuration** -OAuth2GatewayProvider uses the same configuration models as KeycloakAuthProvider — namely: +OAuth2GatewayProvider uses almost the same configuration models as KeycloakAuthProvider — namely: .. autopydantic_model:: syncmaster.server.settings.auth.oauth2_gateway.OAuth2GatewayProviderSettings +.. autopydantic_model:: syncmaster.server.settings.auth.oauth2_gateway.OAuth2GatewayKeycloakSettings .. toctree:: :maxdepth: 1 diff --git a/docs/reference/server/auth/keycloak/local_installation.rst b/docs/reference/server/auth/keycloak/local_installation.rst index 697b72a2..5f464dc1 100644 --- a/docs/reference/server/auth/keycloak/local_installation.rst +++ b/docs/reference/server/auth/keycloak/local_installation.rst @@ -76,7 +76,7 @@ Configure Redirect URI Set URI to redirect from Keycloak login page for exchanging the code for an access token: -.. image:: images/keycloak-client-redirect_uri.png +.. image:: images/keycloak-client-ui_callback_url.png :width: 400px :align: center @@ -86,7 +86,7 @@ Set URI to redirect from Keycloak login page for exchanging the code for an acce auth: keycloak: # Set here URL of SyncMaster UI page handling callback redirects - redirect_uri: http://localhost:3000/auth/callback + ui_callback_url: http://localhost:3000/auth/callback # ... Configure the client secret @@ -108,17 +108,17 @@ Now go to **Credentials** tab and generate a client secret: Now you can use create users in this realm, check `Keycloak documentation `_ on how to manage users creation. -Enable session middleware -~~~~~~~~~~~~~~~~~~~~~~~~~ +Cookie encryption secret +~~~~~~~~~~~~~~~~~~~~~~~~ -Enable :ref:`SesionMiddleware `, and generate random string to use for cookie encryption. +Keycloak access & refresh tokens are stored in cookie with server-side encryption. +So we need to generate random string to use as encryption key: .. code-block:: yaml :caption: config.yml - server: - session: - enabled: true + auth: + cookie: secret_key: secret_key_for_session_cookie Replace login page with Keycloak redirect button @@ -132,8 +132,7 @@ Replace login page with Keycloak redirect button :caption: config.yml ui: - # required by KeycloakAuthProvider - auth_provider: keycloakAuth + auth_provider: keycloakAuthProvider Final configuration ~~~~~~~~~~~~~~~~~~~ @@ -147,23 +146,18 @@ After this you can use ``KeycloakAuthProvider`` in your application: provider: syncmaster.server.providers.auth.keycloak_provider.KeycloakAuthProvider keycloak: # Keycloak URL accessible from both SyncMaster server and from browser - server_url: http://keycloak:8080 + api_url: http://keycloak:8080 # Set here URL of SyncMaster UI page handling callback redirects - redirect_uri: http://localhost:3000/auth/callback + ui_callback_url: http://localhost:3000/auth/callback realm_name: fastapi_realm client_id: fastapi_client client_secret: 6x6gn8uJdWSBmP8FqbNRSoGdvaoaFeez scope: email verify_ssl: false - - server: - session: - # required by KeycloakAuthProvider - enabled: true + cookie: secret_key: secret_key_for_session_cookie ui: - # required by KeycloakAuthProvider - auth_provider: keycloakAuth + auth_provider: keycloakAuthProvider # SyncMaster API URL, accessible from browser api_browser_url: http://localhost:8000 diff --git a/docs/reference/server/configuration/index.rst b/docs/reference/server/configuration/index.rst index 8cb8534a..e1bbf44f 100644 --- a/docs/reference/server/configuration/index.rst +++ b/docs/reference/server/configuration/index.rst @@ -12,7 +12,6 @@ Configuration broker credentials logging - session cors debug monitoring diff --git a/docs/reference/server/configuration/session.rst b/docs/reference/server/configuration/session.rst deleted file mode 100644 index 097df71b..00000000 --- a/docs/reference/server/configuration/session.rst +++ /dev/null @@ -1,8 +0,0 @@ -.. _server-configuration-session: - -Session settings -================ - -These settings used to control `Session `_ options. - -.. autopydantic_model:: syncmaster.server.settings.server.session.SessionSettings \ No newline at end of file diff --git a/syncmaster/server/middlewares/__init__.py b/syncmaster/server/middlewares/__init__.py index e62a8d4e..b2592053 100644 --- a/syncmaster/server/middlewares/__init__.py +++ b/syncmaster/server/middlewares/__init__.py @@ -9,7 +9,6 @@ ) from syncmaster.server.middlewares.openapi import apply_openapi_middleware from syncmaster.server.middlewares.request_id import apply_request_id_middleware -from syncmaster.server.middlewares.session import apply_session_middleware from syncmaster.server.middlewares.static_files import apply_static_files from syncmaster.server.settings import ServerAppSettings as Settings @@ -25,6 +24,5 @@ def apply_middlewares( apply_request_id_middleware(application, settings.server.request_id) apply_openapi_middleware(application, settings.server.openapi) apply_static_files(application, settings.server.static_files) - apply_session_middleware(application, settings.server.session) return application diff --git a/syncmaster/server/middlewares/session.py b/syncmaster/server/middlewares/session.py deleted file mode 100644 index d9259600..00000000 --- a/syncmaster/server/middlewares/session.py +++ /dev/null @@ -1,19 +0,0 @@ -# SPDX-FileCopyrightText: 2023-2024 MTS PJSC -# SPDX-License-Identifier: Apache-2.0 - -from fastapi import FastAPI -from starlette.middleware.sessions import SessionMiddleware - -from syncmaster.server.settings.server.session import SessionSettings - - -def apply_session_middleware(app: FastAPI, settings: SessionSettings) -> FastAPI: - """Add SessionMiddleware middleware to the application.""" - if not settings.enabled: - return app - - settings_dict = settings.model_dump(exclude={"secret_key", "enabled"}) - settings_dict["secret_key"] = settings.secret_key.get_secret_value() # type: ignore[union-attr] - - app.add_middleware(SessionMiddleware, **settings_dict) - return app diff --git a/syncmaster/server/providers/auth/keycloak_provider.py b/syncmaster/server/providers/auth/keycloak_provider.py index 38c400cc..af957a2e 100644 --- a/syncmaster/server/providers/auth/keycloak_provider.py +++ b/syncmaster/server/providers/auth/keycloak_provider.py @@ -6,6 +6,7 @@ from fastapi import Depends, FastAPI, Request from jwcrypto.common import JWException from keycloak import KeycloakOpenID, KeycloakOperationError +from starlette.middleware.sessions import SessionMiddleware from syncmaster.db.models.user import User from syncmaster.exceptions import EntityNotFoundError @@ -28,7 +29,7 @@ def __init__( self.settings = settings self._uow = unit_of_work self.keycloak_openid = KeycloakOpenID( - server_url=self.settings.keycloak.server_url, + server_url=str(self.settings.keycloak.api_url).rstrip("/") + "/", # noqa: WPS336 client_id=self.settings.keycloak.client_id, realm_name=self.settings.keycloak.realm_name, client_secret_key=self.settings.keycloak.client_secret.get_secret_value(), @@ -41,6 +42,17 @@ def setup(cls, app: FastAPI) -> FastAPI: log.info("Using %s provider with settings:\n%s", cls.__name__, settings) app.dependency_overrides[AuthProvider] = cls app.dependency_overrides[KeycloakAuthProviderSettings] = lambda: settings + + app.add_middleware( + SessionMiddleware, + secret_key=settings.cookie.secret_key.get_secret_value(), + session_cookie=settings.cookie.name, + max_age=settings.cookie.max_age, + path=settings.cookie.path, + same_site=settings.cookie.same_site, + https_only=settings.cookie.https_only, + domain=settings.cookie.domain, + ) return app async def get_token_password_grant( @@ -67,7 +79,7 @@ async def get_token_authorization_code_grant( return await self.keycloak_openid.a_token( grant_type="authorization_code", code=code, - redirect_uri=self.settings.keycloak.redirect_uri, + redirect_uri=self.settings.keycloak.ui_callback_url, ) except KeycloakOperationError as e: raise AuthorizationError("Failed to get token") from e @@ -135,7 +147,7 @@ async def get_current_user(self, access_token: str | None, request: Request) -> async def redirect_to_auth(self) -> NoReturn: auth_url = await self.keycloak_openid.a_auth_url( - redirect_uri=self.settings.keycloak.redirect_uri, + redirect_uri=self.settings.keycloak.ui_callback_url, scope=self.settings.keycloak.scope, ) raise RedirectException(redirect_url=auth_url) diff --git a/syncmaster/server/providers/auth/oauth2_gateway_provider.py b/syncmaster/server/providers/auth/oauth2_gateway_provider.py index 73e0a160..e6b17028 100644 --- a/syncmaster/server/providers/auth/oauth2_gateway_provider.py +++ b/syncmaster/server/providers/auth/oauth2_gateway_provider.py @@ -4,29 +4,34 @@ from typing import Annotated, Any from fastapi import Depends, FastAPI, Request +from keycloak import KeycloakOpenID, KeycloakOperationError from syncmaster.db.models import User from syncmaster.exceptions import EntityNotFoundError from syncmaster.exceptions.auth import AuthorizationError from syncmaster.server.dependencies import Stub from syncmaster.server.providers.auth.base_provider import AuthProvider -from syncmaster.server.providers.auth.keycloak_provider import ( - KeycloakAuthProvider, - KeycloakOperationError, -) from syncmaster.server.services.unit_of_work import UnitOfWork from syncmaster.server.settings.auth.oauth2_gateway import OAuth2GatewayProviderSettings log = logging.getLogger(__name__) -class OAuth2GatewayProvider(KeycloakAuthProvider): +class OAuth2GatewayProvider(AuthProvider): def __init__( # noqa: WPS612 self, settings: Annotated[OAuth2GatewayProviderSettings, Depends(Stub(OAuth2GatewayProviderSettings))], unit_of_work: Annotated[UnitOfWork, Depends()], ) -> None: - super().__init__(settings, unit_of_work) # type: ignore[arg-type] + self.settings = settings + self._uow = unit_of_work + self.keycloak_openid = KeycloakOpenID( + server_url=str(self.settings.keycloak.api_url).rstrip("/") + "/", # noqa: WPS336 + client_id=self.settings.keycloak.client_id, + realm_name=self.settings.keycloak.realm_name, + client_secret_key=self.settings.keycloak.client_secret.get_secret_value(), + verify=self.settings.keycloak.verify_ssl, + ) @classmethod def setup(cls, app: FastAPI) -> FastAPI: @@ -81,6 +86,19 @@ async def get_current_user( # noqa: WPS231, WPS217, WPS238 ) return user + async def get_token_password_grant( + self, + grant_type: str | None = None, + login: str | None = None, + password: str | None = None, + scopes: list[str] | None = None, + client_id: str | None = None, + client_secret: str | None = None, + ) -> dict[str, Any]: + raise NotImplementedError( + f"Password grant is not supported by {self.__class__.__name__}.", # noqa: WPS237 + ) + async def get_token_authorization_code_grant( self, code: str, diff --git a/syncmaster/server/settings/auth/keycloak.py b/syncmaster/server/settings/auth/keycloak.py index e355ff4b..4d50cde2 100644 --- a/syncmaster/server/settings/auth/keycloak.py +++ b/syncmaster/server/settings/auth/keycloak.py @@ -1,19 +1,109 @@ # SPDX-FileCopyrightText: 2023-2024 MTS PJSC # SPDX-License-Identifier: Apache-2.0 -from pydantic import BaseModel, Field, SecretStr +import textwrap +from typing import Literal +from pydantic import ( + BaseModel, + ConfigDict, + Field, + HttpUrl, + SecretStr, +) -class KeycloakSettings(BaseModel): - server_url: str = Field(description="Keycloak server URL") +class KeycloakSettings(BaseModel): + api_url: HttpUrl = Field(description="Keycloak API URL") client_id: str = Field(description="Keycloak client ID") client_secret: SecretStr = Field(description="Keycloak client secret") realm_name: str = Field(description="Keycloak realm name") - redirect_uri: str = Field(description="Redirect URI") + ui_callback_url: str = Field(description="SyncMaster UI auth callback endpoint") verify_ssl: bool = Field(default=True, description="Verify SSL certificates") scope: str = Field(default="openid", description="Keycloak scope") +class KeycloakCookieSettings(BaseModel): + """Keycloak cookie Middleware Settings. + + See `SessionMiddleware `_ documentation. + + .. note:: + + You can pass here any extra option supported by ``SessionMiddleware``, + even if it is not mentioned in documentation. + + Examples + -------- + + For development environment: + + .. code-block:: yaml + :caption: config.yml + + auth: + provider: syncmaster.server.providers.auth.keycloak_provider.KeycloakAuthProvider + keycloak: ... + cookie: + secret_key: cookie_secret + name: custom_name + max_age: null + same_site: lax + https_only: false + domain: localhost + + For production environment: + + .. code-block:: yaml + :caption: config.yml + + auth: + provider: syncmaster.server.providers.auth.keycloak_provider.KeycloakAuthProvider + keycloak: ... + cookie: + secret_key: cookie_secret + name: custom_name + max_age: 2678400 # 31 days + same_site: strict + https_only: true + domain: example.com + + """ + + secret_key: SecretStr = Field( + description=textwrap.dedent( + """ + Secret key for encrypting cookies. + + Can be any string. It is recommended to generate random value for every application instance, e.g.: + + .. code:: shell + + pwgen 32 1 + """, + ), + ) + name: str = Field( + default="session", + description="Name of the session cookie. Change this if there are multiple application under the same domain.", + ) + max_age: int | None = Field( + default=14 * 24 * 60 * 60, + description="Session expiry time in seconds. Defaults to 2 weeks.", + ) + same_site: Literal["strict", "lax", "none"] = Field( + default="lax", + description="Prevents cookie from being sent with cross-site requests.", + ) + path: str = Field(default="/", description="Path to restrict session cookie access.") + https_only: bool = Field(default=False, description="Secure flag for HTTPS-only access.") + domain: str | None = Field( + default=None, + description="Domain for sharing cookies between subdomains or cross-domains.", + ) + + model_config = ConfigDict(extra="allow") + + class KeycloakAuthProviderSettings(BaseModel): """Settings for KeycloakAuthProvider. @@ -26,15 +116,20 @@ class KeycloakAuthProviderSettings(BaseModel): auth: provider: syncmaster.server.providers.auth.keycloak_provider.KeycloakAuthProvider keycloak: - server_url: http://localhost:8080/auth + api_url: http://localhost:8080/auth client_id: my_keycloak_client client_secret: keycloak_client_secret realm_name: my_realm - redirect_uri: http://localhost:8000/auth/realms/my_realm/protocol/openid-connect/auth + ui_callback_url: http://localhost:8000/auth/realms/my_realm/protocol/openid-connect/auth verify_ssl: false scope: openid + cookie: + secret_key: cookie_secret """ keycloak: KeycloakSettings = Field( description="Keycloak settings", ) + cookie: KeycloakCookieSettings = Field( + description="Keycloak cookie settings", + ) diff --git a/syncmaster/server/settings/auth/oauth2_gateway.py b/syncmaster/server/settings/auth/oauth2_gateway.py index d4880b17..db90e41d 100644 --- a/syncmaster/server/settings/auth/oauth2_gateway.py +++ b/syncmaster/server/settings/auth/oauth2_gateway.py @@ -1,8 +1,14 @@ # SPDX-FileCopyrightText: 2023-2025 MTS PJSC # SPDX-License-Identifier: Apache-2.0 -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, HttpUrl, SecretStr -from syncmaster.server.settings.auth.keycloak import KeycloakSettings + +class OAuth2GatewayKeycloakSettings(BaseModel): + api_url: HttpUrl = Field(description="Keycloak API URL") + client_id: str = Field(description="Keycloak client ID") + client_secret: SecretStr = Field(description="Keycloak client secret") + realm_name: str = Field(description="Keycloak realm name") + verify_ssl: bool = Field(default=True, description="Verify SSL certificates") class OAuth2GatewayProviderSettings(BaseModel): @@ -17,15 +23,13 @@ class OAuth2GatewayProviderSettings(BaseModel): auth: provider: syncmaster.server.providers.auth.oauth2_gateway_provider.OAuth2GatewayProvider keycloak: - server_url: http://localhost:8080/auth + api_url: http://localhost:8080/auth client_id: my_keycloak_client client_secret: keycloak_client_secret realm_name: my_realm - redirect_uri: http://localhost:8000/auth/realms/my_realm/protocol/openid-connect/auth verify_ssl: false - scope: openid """ - keycloak: KeycloakSettings = Field( + keycloak: OAuth2GatewayKeycloakSettings = Field( description="Keycloak settings", ) diff --git a/syncmaster/server/settings/server/__init__.py b/syncmaster/server/settings/server/__init__.py index 2068b319..2cebddf3 100644 --- a/syncmaster/server/settings/server/__init__.py +++ b/syncmaster/server/settings/server/__init__.py @@ -9,7 +9,6 @@ from syncmaster.server.settings.server.monitoring import MonitoringSettings from syncmaster.server.settings.server.openapi import OpenAPISettings from syncmaster.server.settings.server.request_id import RequestIDSettings -from syncmaster.server.settings.server.session import SessionSettings from syncmaster.server.settings.server.static_files import StaticFilesSettings @@ -26,8 +25,6 @@ class ServerSettings(BaseModel): debug: true request_id: enabled: true - session: - secret_key: super-secret-key cors: enabled: true monitoring: @@ -50,10 +47,6 @@ class ServerSettings(BaseModel): request_id: RequestIDSettings = Field( default_factory=RequestIDSettings, ) - session: SessionSettings = Field( - default_factory=SessionSettings, # type: ignore[arg-type] - description=":ref:`Session settings `", - ) cors: CORSSettings = Field( default_factory=CORSSettings, description=":ref:`CORS settings `", diff --git a/syncmaster/server/settings/server/openapi.py b/syncmaster/server/settings/server/openapi.py index cd7375d1..dc7c064a 100644 --- a/syncmaster/server/settings/server/openapi.py +++ b/syncmaster/server/settings/server/openapi.py @@ -3,7 +3,7 @@ import textwrap from typing import Any -from pydantic import AnyHttpUrl, BaseModel, Field +from pydantic import BaseModel, Field, HttpUrl class SwaggerSettings(BaseModel): @@ -104,7 +104,7 @@ class LogoSettings(BaseModel): default="Syncmaster logo", description="Alternative text for ```` tag", ) - href: AnyHttpUrl | None = Field( # type: ignore[assignment] + href: HttpUrl | None = Field( # type: ignore[assignment] default="https://github.com/MobileTeleSystems/syncmaster", description="Clicking on logo will redirect to this URL", ) diff --git a/syncmaster/server/settings/server/session.py b/syncmaster/server/settings/server/session.py deleted file mode 100644 index d0171088..00000000 --- a/syncmaster/server/settings/server/session.py +++ /dev/null @@ -1,108 +0,0 @@ -# SPDX-FileCopyrightText: 2023-2024 MTS PJSC -# SPDX-License-Identifier: Apache-2.0 - - -import textwrap - -from pydantic import ( - BaseModel, - ConfigDict, - Field, - SecretStr, - ValidationInfo, - field_validator, -) - - -class SessionSettings(BaseModel): - """Session Middleware Settings. - - Required for :ref:`keycloak-auth-provider`. - - See `SessionMiddleware `_ documentation. - - .. note:: - - You can pass here any extra option supported by ``SessionMiddleware``, - even if it is not mentioned in documentation. - - Examples - -------- - - For development environment: - - .. code-block:: yaml - :caption: config.yml - - server: - session: - enabled: true - secret_key: cookie_secret - session_cookie: custom_cookie_name - max_age: null - same_site: lax - https_only: True - domain: localhost - - For production environment: - - .. code-block:: yaml - :caption: config.yml - - server: - session: - enabled: true - secret_key: cookie_secret - session_cookie: custom_cookie_name - max_age: 3600 - same_site: strict - https_only: True - domain: example.com - - """ - - enabled: bool = Field( - default=False, - description="Set to ``True`` to enable SessionMiddleware", - ) - secret_key: SecretStr | None = Field( - default=None, - description=textwrap.dedent( - """ - Secret key for encrypting cookies. - - Can be any string. It is recommended to generate random value for every application instance, e.g.: - - .. code:: shell - - pwgen 32 1 - """, - ), - ) - session_cookie: str | None = Field( - default="session", - description="Name of the session cookie. Change this if there are multiple application under the same domain.", - ) - max_age: int | None = Field( - default=14 * 24 * 60 * 60, - description="Session expiry time in seconds. Defaults to 2 weeks.", - ) - same_site: str | None = Field( - default="lax", - description="Prevents cookie from being sent with cross-site requests.", - ) - path: str | None = Field(default="/", description="Path to restrict session cookie access.") - https_only: bool = Field(default=False, description="Secure flag for HTTPS-only access.") - domain: str | None = Field( - default=None, - description="Domain for sharing cookies between subdomains or cross-domains.", - ) - - model_config = ConfigDict(extra="allow") - - @field_validator("secret_key") - @classmethod - def _validate_secret_key(cls, value: SecretStr | None, info: ValidationInfo) -> SecretStr | None: - if not value and info.data.get("enabled"): - raise ValueError("secret_key is required") - return value diff --git a/tests/test_unit/test_auth/auth_fixtures/keycloak_fixture.py b/tests/test_unit/test_auth/auth_fixtures/keycloak_fixture.py index 30d4c993..faf31aca 100644 --- a/tests/test_unit/test_auth/auth_fixtures/keycloak_fixture.py +++ b/tests/test_unit/test_auth/auth_fixtures/keycloak_fixture.py @@ -53,7 +53,8 @@ def get_public_key_pem(public_key): def create_session_cookie(rsa_keys, settings): def _create_session_cookie(user, expire_in_msec=60000) -> str: private_pem = rsa_keys["private_pem"] - session_secret_key = settings.server.session.secret_key.get_secret_value() + cookie_settings = settings.auth.model_dump()["cookie"] + session_secret_key = cookie_settings["secret_key"] payload = { "sub": str(user.id), @@ -87,18 +88,18 @@ def _create_session_cookie(user, expire_in_msec=60000) -> str: @pytest_asyncio.fixture async def mock_keycloak_api(settings): # noqa: F811 keycloak_settings = settings.auth.model_dump()["keycloak"] - server_url = keycloak_settings["server_url"] + api_url = keycloak_settings["api_url"] - async with respx.mock(base_url=server_url, assert_all_called=False) as respx_mock: + async with respx.mock(base_url=api_url, assert_all_called=False) as respx_mock: yield respx_mock @pytest.fixture def mock_keycloak_well_known(settings, mock_keycloak_api): keycloak_settings = settings.auth.model_dump()["keycloak"] - server_url = keycloak_settings["server_url"] + api_url = keycloak_settings["api_url"] realm_name = keycloak_settings["client_id"] - realm_url = f"{server_url}/realms/{realm_name}" + realm_url = f"{api_url}/realms/{realm_name}" well_known_url = f"{realm_url}/.well-known/openid-configuration" openid_url = f"{realm_url}/protocol/openid-connect" @@ -119,9 +120,9 @@ def mock_keycloak_well_known(settings, mock_keycloak_api): @pytest.fixture def mock_keycloak_realm(settings, rsa_keys, mock_keycloak_api): keycloak_settings = settings.auth.model_dump()["keycloak"] - server_url = keycloak_settings["server_url"] + api_url = keycloak_settings["api_url"] realm_name = keycloak_settings["client_id"] - realm_url = f"{server_url}/realms/{realm_name}" + realm_url = f"{api_url}/realms/{realm_name}" public_pem_str = get_public_key_pem(rsa_keys["public_key"]) mock_keycloak_api.get(realm_url).respond( @@ -139,9 +140,9 @@ def mock_keycloak_realm(settings, rsa_keys, mock_keycloak_api): @pytest.fixture def mock_keycloak_token_refresh(settings, rsa_keys, mock_keycloak_api): keycloak_settings = settings.auth.model_dump()["keycloak"] - server_url = keycloak_settings["server_url"] + api_url = keycloak_settings["api_url"] realm_name = keycloak_settings["client_id"] - realm_url = f"{server_url}/realms/{realm_name}" + realm_url = f"{api_url}/realms/{realm_name}" token_url = f"{realm_url}/protocol/openid-connect/token" # generate new access and refresh tokens @@ -175,9 +176,9 @@ def mock_keycloak_token_refresh(settings, rsa_keys, mock_keycloak_api): @pytest.fixture def mock_keycloak_logout(settings, mock_keycloak_api): keycloak_settings = settings.auth.model_dump()["keycloak"] - server_url = keycloak_settings["server_url"] + api_url = keycloak_settings["api_url"] realm_name = keycloak_settings["client_id"] - realm_url = f"{server_url}/realms/{realm_name}" + realm_url = f"{api_url}/realms/{realm_name}" logout_url = f"{realm_url}/protocol/openid-connect/logout" mock_keycloak_api.post(logout_url).respond(status_code=204) @@ -187,9 +188,9 @@ def mock_keycloak_logout(settings, mock_keycloak_api): def mock_keycloak_introspect_token(settings, mock_keycloak_api): def _mock_keycloak_introspect_token(user): keycloak_settings = settings.auth.model_dump()["keycloak"] - server_url = keycloak_settings["server_url"] + api_url = keycloak_settings["api_url"] realm_name = keycloak_settings["client_id"] - realm_url = f"{server_url}/realms/{realm_name}" + realm_url = f"{api_url}/realms/{realm_name}" payload = { "preferred_username": user.username, diff --git a/tests/test_unit/test_auth/test_keycloak.py b/tests/test_unit/test_auth/test_keycloak.py index d48c0ffa..620ece77 100644 --- a/tests/test_unit/test_auth/test_keycloak.py +++ b/tests/test_unit/test_auth/test_keycloak.py @@ -13,14 +13,18 @@ "auth": { "provider": "syncmaster.server.providers.auth.keycloak_provider.KeycloakAuthProvider", "keycloak": { - "server_url": "http://localhost:8080", + "api_url": "http://localhost:8080/auth", "realm_name": "manually_created", "client_id": "manually_created", "client_secret": "generated_by_keycloak", - "redirect_uri": "http://localhost:3000/auth/callback", + "ui_callback_url": "http://localhost:3000/auth/callback", "scope": "email", "verify_ssl": False, }, + "cookie": { + "secret_key": "generate_some_random_string", + "max_age": 86400, + }, }, } diff --git a/tests/test_unit/test_auth/test_oauth2_gateway.py b/tests/test_unit/test_auth/test_oauth2_gateway.py index 92c30493..d43569a1 100644 --- a/tests/test_unit/test_auth/test_oauth2_gateway.py +++ b/tests/test_unit/test_auth/test_oauth2_gateway.py @@ -10,12 +10,10 @@ "auth": { "provider": "syncmaster.server.providers.auth.oauth2_gateway_provider.OAuth2GatewayProvider", "keycloak": { - "server_url": "http://localhost:8080", + "api_url": "http://localhost:8080/auth", "realm_name": "manually_created", "client_id": "manually_created", "client_secret": "generated_by_keycloak", - "redirect_uri": "http://localhost:3000/auth/callback", - "scope": "email", "verify_ssl": False, }, },