From 7b88b890a533c0f7f2d057c93ef7546087cc6f22 Mon Sep 17 00:00:00 2001 From: maxim-lixakov Date: Thu, 14 Nov 2024 12:23:04 +0300 Subject: [PATCH] [DOP-21268] - add SessionSettings --- .env.docker | 11 ++-- .env.local | 3 + .readthedocs.yaml | 2 +- docs/backend/configuration/cors.rst | 2 +- docs/backend/configuration/index.rst | 1 + docs/backend/configuration/monitoring.rst | 2 +- docs/backend/configuration/openapi.rst | 10 ++-- docs/backend/configuration/session.rst | 8 +++ docs/backend/configuration/static_files.rst | 2 +- syncmaster/backend/middlewares/__init__.py | 2 +- syncmaster/backend/middlewares/session.py | 12 ++-- .../backend/settings/server/__init__.py | 5 ++ syncmaster/backend/settings/server/session.py | 56 +++++++++++++++++++ 13 files changed, 97 insertions(+), 19 deletions(-) create mode 100644 docs/backend/configuration/session.rst create mode 100644 syncmaster/backend/settings/server/session.py diff --git a/.env.docker b/.env.docker index c1545987..b8d9d73a 100644 --- a/.env.docker +++ b/.env.docker @@ -9,23 +9,24 @@ SYNCMASTER__LOGGING__SETUP=True SYNCMASTER__LOGGING__PRESET=colored SYNCMASTER__LOG_URL_TEMPLATE=https://grafana.example.com?correlation_id={{ correlation_id }}&run_id={{ run.id }} +# Session +SYNCMASTER__SERVER__SESSION__SECRET_KEY=session_secret_key + # Encrypt / Decrypt credentials data SYNCMASTER__CRYPTO_KEY=UBgPTioFrtH2unlC4XFDiGf5sYfzbdSf_VgiUSaQc94= # Postgres SYNCMASTER__DATABASE__URL=postgresql+asyncpg://syncmaster:changeme@db:5432/syncmaster +# TODO: add to KeycloakAuthProvider documentation about creating new realms, add users, etc. # KEYCLOAK Auth SYNCMASTER__AUTH__SERVER_URL=http://keycloak:8080/ -SYNCMASTER__AUTH__REALM_NAME=fastapi-realm -SYNCMASTER__AUTH__CLIENT_ID=fastapi-client +SYNCMASTER__AUTH__REALM_NAME=manually_created +SYNCMASTER__AUTH__CLIENT_ID=manually_created SYNCMASTER__AUTH__CLIENT_SECRET=VoLrqGz1HGjp6MiwzRaGWIu7z7imKIHb SYNCMASTER__AUTH__REDIRECT_URI=http://localhost:8000/v1/auth/callback -SYNCMASTER__AUTH__ADMIN_REDIRECT_URI=http://localhost:8000/v1/auth/callback SYNCMASTER__AUTH__SCOPE=email -SYNCMASTER__AUTH__KEYCLOAK_INTROSPECTION_DELAY=60 SYNCMASTER__AUTH__PROVIDER=syncmaster.backend.providers.auth.keycloak_provider.KeycloakAuthProvider -SYNCMASTER__AUTH__KEYCLOAK_TOKEN_URL=http://keycloak:8080/realms/fastapi-realm/protocol/openid-connect/token # Dummy Auth SYNCMASTER__AUTH__PROVIDER=syncmaster.backend.providers.auth.dummy_provider.DummyAuthProvider diff --git a/.env.local b/.env.local index 951faa6d..13f34f7e 100644 --- a/.env.local +++ b/.env.local @@ -9,6 +9,9 @@ export SYNCMASTER__LOGGING__SETUP=True export SYNCMASTER__LOGGING__PRESET=colored export SYNCMASTER__LOG_URL_TEMPLATE="https://grafana.example.com?correlation_id={{ correlation_id }}&run_id={{ run.id }}" +# Session +export SYNCMASTER__SERVER__SESSION__SECRET_KEY=session_secret_key + # Encrypt / Decrypt credentials data export SYNCMASTER__CRYPTO_KEY=UBgPTioFrtH2unlC4XFDiGf5sYfzbdSf_VgiUSaQc94= diff --git a/.readthedocs.yaml b/.readthedocs.yaml index f6b8364b..6c6d1d3a 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -17,7 +17,7 @@ build: - VIRTUAL_ENV=$READTHEDOCS_VIRTUALENV_PATH python -m poetry install --no-root --all-extras --with docs --without dev,test - VIRTUAL_ENV=$READTHEDOCS_VIRTUALENV_PATH python -m poetry show -v - python -m pip list -v - - SYNCMASTER__DATABASE__URL=postgresql+psycopg://fake:fake@127.0.0.1:5432/fake SYNCMASTER__BROKER__URL=amqp://fake:faket@fake:5672/ SYNCMASTER__CRYPTO_KEY=crypto_key SYNCMASTER__AUTH__ACCESS_TOKEN__SECRET_KEY=fakepython python -m syncmaster.backend.export_openapi_schema docs/_static/openapi.json + - SYNCMASTER__DATABASE__URL=postgresql+psycopg://fake:fake@127.0.0.1:5432/fake SYNCMASTER__SERVER__SESSION__SECRET_KEY=session_secret_key SYNCMASTER__BROKER__URL=amqp://fake:faket@fake:5672/ SYNCMASTER__CRYPTO_KEY=crypto_key SYNCMASTER__AUTH__ACCESS_TOKEN__SECRET_KEY=fakepython python -m syncmaster.backend.export_openapi_schema docs/_static/openapi.json sphinx: configuration: docs/conf.py diff --git a/docs/backend/configuration/cors.rst b/docs/backend/configuration/cors.rst index 4768d864..6c4bd5cf 100644 --- a/docs/backend/configuration/cors.rst +++ b/docs/backend/configuration/cors.rst @@ -5,4 +5,4 @@ CORS settings These settings used to control `CORS `_ options. -.. autopydantic_model:: syncmaster.settings.server.cors.CORSSettings \ No newline at end of file +.. autopydantic_model:: syncmaster.backend.settings.server.cors.CORSSettings \ No newline at end of file diff --git a/docs/backend/configuration/index.rst b/docs/backend/configuration/index.rst index ba68d1df..90ee6507 100644 --- a/docs/backend/configuration/index.rst +++ b/docs/backend/configuration/index.rst @@ -11,6 +11,7 @@ Configuration database broker logging + session cors debug monitoring diff --git a/docs/backend/configuration/monitoring.rst b/docs/backend/configuration/monitoring.rst index f12b25d6..c2b8273e 100644 --- a/docs/backend/configuration/monitoring.rst +++ b/docs/backend/configuration/monitoring.rst @@ -13,4 +13,4 @@ REST API server provides the following endpoints with Prometheus compatible metr These endpoints are enabled and configured using settings below: -.. autopydantic_model:: syncmaster.settings.server.monitoring.MonitoringSettings +.. autopydantic_model:: syncmaster.backend.settings.server.monitoring.MonitoringSettings diff --git a/docs/backend/configuration/openapi.rst b/docs/backend/configuration/openapi.rst index b1a4603a..27620d89 100644 --- a/docs/backend/configuration/openapi.rst +++ b/docs/backend/configuration/openapi.rst @@ -5,8 +5,8 @@ OpenAPI settings These settings used to control exposing OpenAPI.json and SwaggerUI/ReDoc endpoints. -.. autopydantic_model:: syncmaster.settings.server.openapi.OpenAPISettings -.. autopydantic_model:: syncmaster.settings.server.openapi.SwaggerSettings -.. autopydantic_model:: syncmaster.settings.server.openapi.RedocSettings -.. autopydantic_model:: syncmaster.settings.server.openapi.LogoSettings -.. autopydantic_model:: syncmaster.settings.server.openapi.FaviconSettings +.. autopydantic_model:: syncmaster.backend.settings.server.openapi.OpenAPISettings +.. autopydantic_model:: syncmaster.backend.settings.server.openapi.SwaggerSettings +.. autopydantic_model:: syncmaster.backend.settings.server.openapi.RedocSettings +.. autopydantic_model:: syncmaster.backend.settings.server.openapi.LogoSettings +.. autopydantic_model:: syncmaster.backend.settings.server.openapi.FaviconSettings diff --git a/docs/backend/configuration/session.rst b/docs/backend/configuration/session.rst new file mode 100644 index 00000000..893dd76b --- /dev/null +++ b/docs/backend/configuration/session.rst @@ -0,0 +1,8 @@ +.. _backend-configuration-server-session: + +Session settings +================ + +These settings used to control `Session `_ options. + +.. autopydantic_model:: syncmaster.backend.settings.server.session.SessionSettings \ No newline at end of file diff --git a/docs/backend/configuration/static_files.rst b/docs/backend/configuration/static_files.rst index dd31ddd5..6628a962 100644 --- a/docs/backend/configuration/static_files.rst +++ b/docs/backend/configuration/static_files.rst @@ -5,4 +5,4 @@ Serving static files These settings used to control serving static files by a server. -.. autopydantic_model:: syncmaster.settings.server.static_files.StaticFilesSettings +.. autopydantic_model:: syncmaster.backend.settings.server.static_files.StaticFilesSettings diff --git a/syncmaster/backend/middlewares/__init__.py b/syncmaster/backend/middlewares/__init__.py index 6287d53c..e1a13c02 100644 --- a/syncmaster/backend/middlewares/__init__.py +++ b/syncmaster/backend/middlewares/__init__.py @@ -29,6 +29,6 @@ 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) + apply_session_middleware(application, settings.server.session) return application diff --git a/syncmaster/backend/middlewares/session.py b/syncmaster/backend/middlewares/session.py index 61399136..d29d30a4 100644 --- a/syncmaster/backend/middlewares/session.py +++ b/syncmaster/backend/middlewares/session.py @@ -1,13 +1,17 @@ # SPDX-FileCopyrightText: 2023-2024 MTS PJSC # SPDX-License-Identifier: Apache-2.0 -import secrets from fastapi import FastAPI from starlette.middleware.sessions import SessionMiddleware +from syncmaster.backend.settings.server.session import SessionSettings -def apply_session_middleware(app: FastAPI) -> FastAPI: + +def apply_session_middleware(app: FastAPI, settings: SessionSettings) -> FastAPI: """Add SessionMiddleware middleware to the application.""" - secret_key = secrets.token_urlsafe(32) - app.add_middleware(SessionMiddleware, secret_key=secret_key) + + app.add_middleware( + SessionMiddleware, + **settings.dict(), + ) return app diff --git a/syncmaster/backend/settings/server/__init__.py b/syncmaster/backend/settings/server/__init__.py index f1e7a86b..c83fbfdb 100644 --- a/syncmaster/backend/settings/server/__init__.py +++ b/syncmaster/backend/settings/server/__init__.py @@ -9,6 +9,7 @@ from syncmaster.backend.settings.server.monitoring import MonitoringSettings from syncmaster.backend.settings.server.openapi import OpenAPISettings from syncmaster.backend.settings.server.request_id import RequestIDSettings +from syncmaster.backend.settings.server.session import SessionSettings from syncmaster.backend.settings.server.static_files import StaticFilesSettings @@ -36,6 +37,10 @@ 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/backend/settings/server/session.py b/syncmaster/backend/settings/server/session.py new file mode 100644 index 00000000..ed2a008a --- /dev/null +++ b/syncmaster/backend/settings/server/session.py @@ -0,0 +1,56 @@ +# SPDX-FileCopyrightText: 2023-2024 MTS PJSC +# SPDX-License-Identifier: Apache-2.0 + + +from pydantic import BaseModel, Field + + +class SessionSettings(BaseModel): + """Session 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:: bash + + SYNCMASTER__SERVER__SESSION__SECRET_KEY=secret + SYNCMASTER__SERVER__SESSION__SESSION_COOKIE=custom_cookie_name + SYNCMASTER__SERVER__SESSION__MAX_AGE=None # cookie will last as long as the browser session + SYNCMASTER__SERVER__SESSION__SAME_SITE=strict + SYNCMASTER__SERVER__SESSION__HTTPS_ONLY=True + SYNCMASTER__SERVER__SESSION__DOMAIN=example.com + + For production environment: + + .. code-block:: bash + + SYNCMASTER__SERVER__SESSION__SECRET_KEY=secret + SYNCMASTER__SERVER__SESSION__HTTPS_ONLY=True + + """ + + secret_key: str = Field(description="A random string for signing cookies.") + session_cookie: str | None = Field(default="session", description="Name of the session cookie.") + max_age: int | None = Field(default=1209600, 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.", + ) + + class Config: + extra = "allow"