Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 5 additions & 7 deletions config.docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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

Expand Down
12 changes: 5 additions & 7 deletions config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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]
Expand Down
29 changes: 29 additions & 0 deletions docs/changelog/next_release/304.breaking.rst
Original file line number Diff line number Diff line change
@@ -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:
5 changes: 3 additions & 2 deletions docs/reference/server/auth/keycloak/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
32 changes: 13 additions & 19 deletions docs/reference/server/auth/keycloak/local_installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand All @@ -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 <https://www.keycloak.org/docs/latest/server_admin/#assembly-managing-users_server_administration_guide>`_ on how to manage users creation.

Enable session middleware
~~~~~~~~~~~~~~~~~~~~~~~~~
Cookie encryption secret
~~~~~~~~~~~~~~~~~~~~~~~~

Enable :ref:`SesionMiddleware <server-configuration-session>`, 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
Expand All @@ -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
~~~~~~~~~~~~~~~~~~~
Expand All @@ -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
1 change: 0 additions & 1 deletion docs/reference/server/configuration/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ Configuration
broker
credentials
logging
session
cors
debug
monitoring
Expand Down
8 changes: 0 additions & 8 deletions docs/reference/server/configuration/session.rst

This file was deleted.

2 changes: 0 additions & 2 deletions syncmaster/server/middlewares/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
19 changes: 0 additions & 19 deletions syncmaster/server/middlewares/session.py

This file was deleted.

18 changes: 15 additions & 3 deletions syncmaster/server/providers/auth/keycloak_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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(),
Expand All @@ -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(
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down
30 changes: 24 additions & 6 deletions syncmaster/server/providers/auth/oauth2_gateway_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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,
Expand Down
Loading
Loading