Skip to content

Commit

Permalink
feat: add lib/dl_zitadel (#495)
Browse files Browse the repository at this point in the history
* chore: add terrarium/dl_repmanager Taskfile

* feat: init lib/dl_zitadel

* chore(lib/dl_zitadel): add compose

* feat(lib/dl_zitadel): add client

* feat(lib/dl_zitadel): add token storage

* feat(lib/dl_zitadel): add aiohttp middleware

* feat(lib/dl_zitadel): add flask middleware

* feat(lib/dl_zitadel): add to apps

* fix(lib/dl_zitadel): metapackage and app deps

* fix(lib/dl_zitadel): mypy

* tests(lib/dl_zitadel): disable db tests

* fix(lib/dl_zitadel): mypy

* tests(lib/dl_zitadel): disable db tests

* fix(lib/dl_zitadel): lost Bearer prefix, settings parent

* fix(lib/dl_zitadel): lost Bearer prefix, settings parent

* fix(lib/dl_zitadel): lost Bearer prefix, settings parent
  • Loading branch information
ovsds authored Jun 24, 2024
1 parent eef7bab commit 4ecfb8d
Show file tree
Hide file tree
Showing 40 changed files with 3,821 additions and 27 deletions.
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,4 @@ ci_artifacts
artifacts
Taskfile.yml
.obsidian
terrarium/dl_repmanager/poetry.lock
datalens-ui/
6 changes: 3 additions & 3 deletions app/dl_control_api/dl_control_api/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
hook_configure_configure_sentry_for_flask,
)
from dl_api_lib.app_settings import (
ControlApiAppSettings,
ControlApiAppSettingsOS,
ControlApiAppTestingsSettings,
)
from dl_api_lib.loader import (
Expand Down Expand Up @@ -38,7 +38,7 @@


def create_app(
app_settings: ControlApiAppSettings,
app_settings: ControlApiAppSettingsOS,
connectors_settings: dict[ConnectionType, ConnectorSettingsBase],
testing_app_settings: Optional[ControlApiAppTestingsSettings] = None,
close_loop_after_request: bool = True,
Expand All @@ -53,7 +53,7 @@ def create_app(

def create_uwsgi_app() -> flask.Flask:
preload_api_lib()
settings = load_settings_from_env_with_fallback(ControlApiAppSettings)
settings = load_settings_from_env_with_fallback(ControlApiAppSettingsOS)
load_api_lib(
ApiLibraryConfig(
api_connector_ep_names=settings.BI_API_CONNECTOR_WHITELIST,
Expand Down
62 changes: 51 additions & 11 deletions app/dl_control_api/dl_control_api/app_factory.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import logging
from typing import Optional

import flask
Expand All @@ -13,7 +14,7 @@
StandaloneServiceRegistryFactory,
)
from dl_api_lib.app_settings import (
ControlApiAppSettings,
ControlApiAppSettingsOS,
ControlApiAppTestingsSettings,
)
from dl_api_lib.connector_availability.base import ConnectorAvailabilityConfig
Expand All @@ -26,37 +27,42 @@
from dl_core.services_registry.rqe_caches import RQECachesSetting


class StandaloneControlApiSRFactoryBuilder(SRFactoryBuilder[ControlApiAppSettings]):
def _get_required_services(self, settings: ControlApiAppSettings) -> set[RequiredService]:
LOGGER = logging.getLogger(__name__)


class StandaloneControlApiSRFactoryBuilder(SRFactoryBuilder[ControlApiAppSettingsOS]):
def _get_required_services(self, settings: ControlApiAppSettingsOS) -> set[RequiredService]:
return set()

def _get_env_manager_factory(self, settings: ControlApiAppSettings) -> EnvManagerFactory:
def _get_env_manager_factory(self, settings: ControlApiAppSettingsOS) -> EnvManagerFactory:
return InsecureEnvManagerFactory()

def _get_inst_specific_sr_factory(
self,
settings: ControlApiAppSettings,
settings: ControlApiAppSettingsOS,
ca_data: bytes,
) -> StandaloneServiceRegistryFactory:
return StandaloneServiceRegistryFactory()

def _get_entity_usage_checker(self, settings: ControlApiAppSettings) -> Optional[EntityUsageChecker]:
def _get_entity_usage_checker(self, settings: ControlApiAppSettingsOS) -> Optional[EntityUsageChecker]:
return None

def _get_bleeding_edge_users(self, settings: ControlApiAppSettings) -> tuple[str, ...]:
def _get_bleeding_edge_users(self, settings: ControlApiAppSettingsOS) -> tuple[str, ...]:
return tuple()

def _get_rqe_caches_settings(self, settings: ControlApiAppSettings) -> Optional[RQECachesSetting]:
def _get_rqe_caches_settings(self, settings: ControlApiAppSettingsOS) -> Optional[RQECachesSetting]:
return None

def _get_default_cache_ttl_settings(self, settings: ControlApiAppSettings) -> Optional[CacheTTLConfig]:
def _get_default_cache_ttl_settings(self, settings: ControlApiAppSettingsOS) -> Optional[CacheTTLConfig]:
return None

def _get_connector_availability(self, settings: ControlApiAppSettings) -> Optional[ConnectorAvailabilityConfig]:
def _get_connector_availability(self, settings: ControlApiAppSettingsOS) -> Optional[ConnectorAvailabilityConfig]:
return settings.CONNECTOR_AVAILABILITY


class StandaloneControlApiAppFactory(ControlApiAppFactory[ControlApiAppSettings], StandaloneControlApiSRFactoryBuilder):
class StandaloneControlApiAppFactory(
ControlApiAppFactory[ControlApiAppSettingsOS], StandaloneControlApiSRFactoryBuilder
):
def set_up_environment(
self,
app: flask.Flask,
Expand All @@ -74,5 +80,39 @@ def set_up_environment(
us_auth_mode_override = None if testing_app_settings is None else testing_app_settings.us_auth_mode_override
us_auth_mode = USAuthMode.master if us_auth_mode_override is None else us_auth_mode_override

self._setup_auth_middleware(app=app)

result = EnvSetupResult(us_auth_mode=us_auth_mode)
return result

def _setup_auth_middleware(self, app: flask.Flask) -> None:
self._settings: ControlApiAppSettingsOS

if self._settings.AUTH is None:
LOGGER.warning("No auth settings found, continuing without auth setup")
return

# TODO: Add support for other auth types
assert self._settings.AUTH.TYPE == "ZITADEL"
import httpx

import dl_zitadel

zitadel_client = dl_zitadel.ZitadelSyncClient(
base_client=httpx.Client(),
base_url=self._settings.AUTH.BASE_URL,
project_id=self._settings.AUTH.PROJECT_ID,
client_id=self._settings.AUTH.CLIENT_ID,
client_secret=self._settings.AUTH.CLIENT_SECRET,
app_client_id=self._settings.AUTH.APP_CLIENT_ID,
app_client_secret=self._settings.AUTH.APP_CLIENT_SECRET,
)
token_storage = dl_zitadel.ZitadelSyncTokenStorage(
client=zitadel_client,
)

dl_zitadel.FlaskMiddleware(
client=zitadel_client,
token_storage=token_storage,
).set_up(app=app)
LOGGER.info("Zitadel auth setup complete")
1 change: 1 addition & 0 deletions app/dl_control_api/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ datalens-api-commons = {path = "../../lib/dl_api_commons"}
datalens-cache-engine = {path = "../../lib/dl_cache_engine"}
datalens-configs = {path = "../../lib/dl_configs"}
datalens-constants = {path = "../../lib/dl_constants"}
datalens-zitadel = {path = "../../lib/dl_zitadel", extras = ["flask"]}

[build-system]
requires = ["poetry-core"]
Expand Down
6 changes: 3 additions & 3 deletions app/dl_data_api/dl_data_api/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from aiohttp import web

from dl_api_lib.app_settings import DataApiAppSettings
from dl_api_lib.app_settings import DataApiAppSettingsOS
from dl_api_lib.loader import (
ApiLibraryConfig,
load_api_lib,
Expand Down Expand Up @@ -35,7 +35,7 @@


def create_app(
setting: DataApiAppSettings,
setting: DataApiAppSettingsOS,
connectors_settings: dict[ConnectionType, ConnectorSettingsBase],
) -> web.Application:
data_api_app_factory = StandaloneDataApiAppFactory(settings=setting)
Expand All @@ -46,7 +46,7 @@ def create_app(

async def create_gunicorn_app(start_selfcheck: bool = True) -> web.Application:
preload_api_lib()
settings = load_settings_from_env_with_fallback(DataApiAppSettings)
settings = load_settings_from_env_with_fallback(DataApiAppSettingsOS)
load_api_lib(
ApiLibraryConfig(
api_connector_ep_names=settings.BI_API_CONNECTOR_WHITELIST,
Expand Down
44 changes: 40 additions & 4 deletions app/dl_data_api/dl_data_api/app_factory.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import logging
from typing import Optional

from dl_api_commons.aio.middlewares.auth_trust_middleware import auth_trust_middleware
Expand All @@ -12,7 +13,7 @@
from dl_api_lib.app_common_settings import ConnOptionsMutatorsFactory
from dl_api_lib.app_settings import (
AppSettings,
DataApiAppSettings,
DataApiAppSettingsOS,
)
from dl_api_lib.connector_availability.base import ConnectorAvailabilityConfig
from dl_cache_engine.primitives import CacheTTLConfig
Expand All @@ -30,6 +31,9 @@
from dl_data_api import app_version


LOGGER = logging.getLogger(__name__)


class StandaloneDataApiSRFactoryBuilder(SRFactoryBuilder[AppSettings]):
@property
def _is_async_env(self) -> bool:
Expand All @@ -54,17 +58,17 @@ def _get_entity_usage_checker(self, settings: AppSettings) -> Optional[EntityUsa
def _get_bleeding_edge_users(self, settings: AppSettings) -> tuple[str, ...]:
return tuple()

def _get_rqe_caches_settings(self, settings: DataApiAppSettings) -> Optional[RQECachesSetting]: # type: ignore[override]
def _get_rqe_caches_settings(self, settings: DataApiAppSettingsOS) -> Optional[RQECachesSetting]: # type: ignore[override]
return None

def _get_default_cache_ttl_settings(self, settings: DataApiAppSettings) -> Optional[CacheTTLConfig]: # type: ignore[override]
def _get_default_cache_ttl_settings(self, settings: DataApiAppSettingsOS) -> Optional[CacheTTLConfig]: # type: ignore[override]
return None

def _get_connector_availability(self, settings: AppSettings) -> Optional[ConnectorAvailabilityConfig]:
return None


class StandaloneDataApiAppFactory(DataApiAppFactory[DataApiAppSettings], StandaloneDataApiSRFactoryBuilder):
class StandaloneDataApiAppFactory(DataApiAppFactory[DataApiAppSettingsOS], StandaloneDataApiSRFactoryBuilder):
@property
def _is_public(self) -> bool:
return False
Expand Down Expand Up @@ -126,3 +130,35 @@ def set_up_environment(
)

return result

def _get_auth_middleware(self) -> AIOHTTPMiddleware | None:
self._settings: DataApiAppSettingsOS

if self._settings.AUTH is None:
LOGGER.warning("No auth settings found, continuing without auth setup")
return None

# TODO: Add support for other auth types
assert self._settings.AUTH.TYPE == "ZITADEL"
import httpx

import dl_zitadel

zitadel_client = dl_zitadel.ZitadelAsyncClient(
base_client=httpx.AsyncClient(),
base_url=self._settings.AUTH.BASE_URL,
project_id=self._settings.AUTH.PROJECT_ID,
client_id=self._settings.AUTH.CLIENT_ID,
client_secret=self._settings.AUTH.CLIENT_SECRET,
app_client_id=self._settings.AUTH.APP_CLIENT_ID,
app_client_secret=self._settings.AUTH.APP_CLIENT_SECRET,
)
token_storage = dl_zitadel.ZitadelAsyncTokenStorage(
client=zitadel_client,
)
middleware = dl_zitadel.AioHTTPMiddleware(
client=zitadel_client,
token_storage=token_storage,
)

return middleware.get_middleware()
1 change: 1 addition & 0 deletions app/dl_data_api/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ datalens-api-commons = {path = "../../lib/dl_api_commons"}
datalens-cache-engine = {path = "../../lib/dl_cache_engine"}
datalens-configs = {path = "../../lib/dl_configs"}
datalens-constants = {path = "../../lib/dl_constants"}
datalens-zitadel = {path = "../../lib/dl_zitadel", extras = ["zitadel"]}

[build-system]
requires = ["poetry-core"]
Expand Down
44 changes: 44 additions & 0 deletions lib/dl_api_lib/dl_api_lib/app_settings.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import typing
from typing import (
Any,
ClassVar,
Expand Down Expand Up @@ -291,3 +292,46 @@ def jaeger_service_name(self) -> str:
class ControlApiAppTestingsSettings:
us_auth_mode_override: Optional[USAuthMode] = attr.ib(default=None)
fake_tenant: Optional[TenantDef] = attr.ib(default=None)


# TODO: move to dl_api_lib_os
@attr.s(frozen=True)
class AuthSettingsOS(SettingsBase):
TYPE: str = s_attrib("TYPE") # type: ignore
BASE_URL: str = s_attrib("BASE_URL") # type: ignore
PROJECT_ID: str = s_attrib("PROJECT_ID") # type: ignore
CLIENT_ID: str = s_attrib("CLIENT_ID") # type: ignore
CLIENT_SECRET: str = s_attrib("CLIENT_SECRET", sensitive=True) # type: ignore
APP_CLIENT_ID: str = s_attrib("APP_CLIENT_ID") # type: ignore
APP_CLIENT_SECRET: str = s_attrib("APP_CLIENT_SECRET", sensitive=True) # type: ignore


@attr.s(frozen=True)
class AppSettingsOS(AppSettings):
AUTH: typing.Optional[AuthSettingsOS] = s_attrib( # type: ignore
"AUTH",
fallback_factory=(
lambda cfg: AuthSettingsOS( # type: ignore
TYPE=cfg.AUTH_TYPE,
BASE_URL=cfg.AUTH_BASE_URL,
PROJECT_ID=cfg.AUTH_PROJECT_ID,
CLIENT_ID=cfg.AUTH_CLIENT_ID,
CLIENT_SECRET=cfg.AUTH_CLIENT_SECRET,
APP_CLIENT_ID=cfg.AUTH_APP_CLIENT_ID,
APP_CLIENT_SECRET=cfg.AUTH_APP_CLIENT_SECRET,
)
if is_setting_applicable(cfg, "AUTH_TYPE")
else None
),
missing=None,
)


@attr.s(frozen=True)
class ControlApiAppSettingsOS(AppSettingsOS, ControlApiAppSettings):
...


@attr.s(frozen=True)
class DataApiAppSettingsOS(AppSettingsOS, DataApiAppSettings):
...
Loading

0 comments on commit 4ecfb8d

Please sign in to comment.