From 39365539141c34e9a0df421574d6066c6e27c8b9 Mon Sep 17 00:00:00 2001 From: Nikos Date: Tue, 3 Sep 2024 11:10:22 +0300 Subject: [PATCH] fix: move charm constants to separate file Closes #90 --- src/certificate_transfer_integration.py | 11 +- src/charm.py | 150 ++++++++++++------------ src/constants.py | 43 ++++++- src/kratos.py | 18 ++- tests/integration/test_charm.py | 4 +- tests/unit/conftest.py | 14 +-- tests/unit/test_charm.py | 49 ++++---- tests/unit/test_kratos.py | 11 +- 8 files changed, 170 insertions(+), 130 deletions(-) diff --git a/src/certificate_transfer_integration.py b/src/certificate_transfer_integration.py index 45a01f81..5620f219 100644 --- a/src/certificate_transfer_integration.py +++ b/src/certificate_transfer_integration.py @@ -14,10 +14,7 @@ ) from ops import CharmBase, Object -from constants import CERTIFICATE_TRANSFER_NAME - -LOCAL_CA_CERTS_PATH = Path("/usr/local/share/ca-certificates") -BUNDLE_PATH = "/etc/ssl/certs/ca-certificates.crt" +from constants import CA_BUNDLE_PATH, CERTIFICATE_TRANSFER_RELATION_NAME, LOCAL_CA_CERTS_PATH class CertTransfer(Object): @@ -26,7 +23,7 @@ def __init__( charm: CharmBase, container_name: str, callback_fn: Callable, - cert_transfer_relation_name: str = CERTIFICATE_TRANSFER_NAME, + cert_transfer_relation_name: str = CERTIFICATE_TRANSFER_RELATION_NAME, bundle_name: str = "ca-certificates.crt", ): super().__init__(charm, cert_transfer_relation_name) @@ -61,8 +58,8 @@ def push_ca_certs(self) -> None: subprocess.run(["update-ca-certificates", "--fresh"], capture_output=True) - with open(BUNDLE_PATH) as f: - self.container.push(BUNDLE_PATH, f, make_dirs=True) + with open(CA_BUNDLE_PATH) as f: + self.container.push(CA_BUNDLE_PATH, f, make_dirs=True) def clean_ca_certs(self) -> None: """Remove the cert bundle from the container.""" diff --git a/src/charm.py b/src/charm.py index 6ffeeac6..8d313de9 100755 --- a/src/charm.py +++ b/src/charm.py @@ -11,7 +11,6 @@ import logging from functools import cached_property from os.path import join -from pathlib import Path from secrets import token_hex from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union from urllib.parse import urlparse @@ -85,7 +84,34 @@ import config_map from certificate_transfer_integration import CertTransfer from config_map import IdentitySchemaConfigMap, KratosConfigMap, ProvidersConfigMap -from constants import INTERNAL_INGRESS_RELATION_NAME, WORKLOAD_CONTAINER_NAME +from constants import ( + CONFIG_DIR_PATH, + CONFIG_FILE_PATH, + COOKIE_SECRET_KEY, + DB_RELATION_NAME, + DEFAULT_SCHEMA_ID_FILE_NAME, + EMAIL_TEMPLATE_FILE_PATH, + GRAFANA_DASHBOARD_RELATION_NAME, + HYDRA_RELATION_NAME, + IDENTITY_SCHEMAS_LOCAL_DIR_PATH, + INTERNAL_INGRESS_RELATION_NAME, + KRATOS_ADMIN_PORT, + KRATOS_CONFIG_MAP_NAME, + KRATOS_PUBLIC_PORT, + KRATOS_SERVICE_COMMAND, + LOG_DIR, + LOG_LEVELS, + LOG_PATH, + LOGIN_UI_RELATION_NAME, + LOKI_PUSH_API_RELATION_NAME, + MAPPERS_LOCAL_DIR_PATH, + PEER_KEY_DB_MIGRATE_VERSION, + PEER_RELATION_NAME, + PROMETHEUS_SCRAPE_RELATION_NAME, + SECRET_LABEL, + TRACING_RELATION_NAME, + WORKLOAD_CONTAINER_NAME, +) from kratos import KratosAPI from utils import dict_to_action_output, normalise_url @@ -94,14 +120,6 @@ logger = logging.getLogger(__name__) -KRATOS_ADMIN_PORT = 4434 -KRATOS_PUBLIC_PORT = 4433 -PEER_RELATION_NAME = "kratos-peers" -SECRET_LABEL = "cookie_secret" -COOKIE_SECRET_KEY = "cookiesecret" -PEER_KEY_DB_MIGRATE_VERSION = "db_migrate_version" -DEFAULT_SCHEMA_ID_FILE_NAME = "default.schema" -LOG_LEVELS = ["panic", "fatal", "error", "warn", "info", "debug", "trace"] class KratosCharm(CharmBase): @@ -110,31 +128,9 @@ class KratosCharm(CharmBase): def __init__(self, *args: Any) -> None: super().__init__(*args) self._container = self.unit.get_container(WORKLOAD_CONTAINER_NAME) - self._config_dir_path = Path("/etc/config/kratos") - self._config_file_path = self._config_dir_path / "kratos.yaml" - self._identity_schemas_default_dir_path = self._config_dir_path - self._identity_schemas_config_dir_path = self._config_dir_path / "schemas" / "juju" - self._identity_schemas_local_dir_path = Path("identity_schemas") - self._mappers_dir_path = self._config_dir_path - self._mappers_local_dir_path = Path("claim_mappers") - self._email_template_file_path = Path("/etc/config/templates") / "recovery-body.html.gotmpl" - self._db_name = f"{self.model.name}_{self.app.name}" - self._db_relation_name = "pg-database" - self._hydra_relation_name = "hydra-endpoint-info" - self._login_ui_relation_name = "ui-endpoint-info" - self._prometheus_scrape_relation_name = "metrics-endpoint" - self._loki_push_api_relation_name = "logging" - self._grafana_dashboard_relation_name = "grafana-dashboard" - self._tracing_relation_name = "tracing" - self._kratos_service_command = "kratos serve all" - self._log_dir = Path("/var/log") - self._log_path = self._log_dir / "kratos.log" - self._kratos_config_map_name = "kratos-config" self.client = Client(field_manager=self.app.name, namespace=self.model.name) - self.kratos = KratosAPI( - f"http://127.0.0.1:{KRATOS_ADMIN_PORT}", self._container, str(self._config_file_path) - ) + self.kratos = KratosAPI(f"http://127.0.0.1:{KRATOS_ADMIN_PORT}", self._container) self.kratos_configmap = KratosConfigMap(self.client, self) self.schemas_configmap = IdentitySchemaConfigMap(self.client, self) self.providers_configmap = ProvidersConfigMap(self.client, self) @@ -168,19 +164,17 @@ def __init__(self, *args: Any) -> None: self.database = DatabaseRequires( self, - relation_name=self._db_relation_name, - database_name=self._db_name, + relation_name=DB_RELATION_NAME, + database_name=f"{self.model.name}_{self.app.name}", extra_user_roles="SUPERUSER", ) self.external_provider = ExternalIdpRequirer(self, relation_name="kratos-external-idp") - self.hydra_endpoints = HydraEndpointsRequirer( - self, relation_name=self._hydra_relation_name - ) + self.hydra_endpoints = HydraEndpointsRequirer(self, relation_name=HYDRA_RELATION_NAME) self.login_ui_endpoints = LoginUIEndpointsRequirer( - self, relation_name=self._login_ui_relation_name + self, relation_name=LOGIN_UI_RELATION_NAME ) self.endpoints_provider = KratosEndpointsProvider(self) @@ -188,7 +182,7 @@ def __init__(self, *args: Any) -> None: self.metrics_endpoint = MetricsEndpointProvider( self, - relation_name=self._prometheus_scrape_relation_name, + relation_name=PROMETHEUS_SCRAPE_RELATION_NAME, jobs=[ { "metrics_path": "/metrics/prometheus", @@ -203,17 +197,17 @@ def __init__(self, *args: Any) -> None: self.loki_consumer = LogProxyConsumer( self, - log_files=[str(self._log_path)], - relation_name=self._loki_push_api_relation_name, + log_files=[str(LOG_PATH)], + relation_name=LOKI_PUSH_API_RELATION_NAME, container_name=WORKLOAD_CONTAINER_NAME, ) self.tracing = TracingEndpointRequirer( self, - relation_name=self._tracing_relation_name, + relation_name=TRACING_RELATION_NAME, ) self._grafana_dashboards = GrafanaDashboardProvider( - self, relation_name=self._grafana_dashboard_relation_name + self, relation_name=GRAFANA_DASHBOARD_RELATION_NAME ) self.cert_transfer = CertTransfer( @@ -234,15 +228,15 @@ def __init__(self, *args: Any) -> None: ) self.framework.observe(self.info_provider.on.ready, self._update_kratos_info_relation_data) self.framework.observe( - self.on[self._hydra_relation_name].relation_changed, self._on_config_changed + self.on[HYDRA_RELATION_NAME].relation_changed, self._on_config_changed ) self.framework.observe( - self.on[self._login_ui_relation_name].relation_changed, self._on_config_changed + self.on[LOGIN_UI_RELATION_NAME].relation_changed, self._on_config_changed ) self.framework.observe(self.database.on.database_created, self._on_database_created) self.framework.observe(self.database.on.endpoints_changed, self._on_database_changed) self.framework.observe( - self.on[self._db_relation_name].relation_departed, self._on_database_relation_departed + self.on[DB_RELATION_NAME].relation_departed, self._on_database_relation_departed ) self.framework.observe(self.admin_ingress.on.ready, self._on_admin_ingress_ready) self.framework.observe(self.admin_ingress.on.revoked, self._on_ingress_revoked) @@ -260,9 +254,12 @@ def __init__(self, *args: Any) -> None: self.framework.observe(self.on.delete_identity_action, self._on_delete_identity_action) self.framework.observe(self.on.reset_password_action, self._on_reset_password_action) self.framework.observe( - self.on.invalidate_identity_sessions_action, self._on_invalidate_identity_sessions_action + self.on.invalidate_identity_sessions_action, + self._on_invalidate_identity_sessions_action, + ) + self.framework.observe( + self.on.reset_identity_mfa_action, self._on_reset_identity_mfa_action ) - self.framework.observe(self.on.reset_identity_mfa_action, self._on_reset_identity_mfa_action) self.framework.observe( self.on.create_admin_account_action, self._on_create_admin_account_action ) @@ -305,7 +302,7 @@ def _no_proxy(self) -> str: @property def _kratos_service_params(self) -> str: - ret = ["--config", str(self._config_file_path)] + ret = ["--config", str(CONFIG_FILE_PATH)] if self.config["dev"]: logger.warning("Running Kratos in dev mode, don't do this in production") ret.append("--dev") @@ -334,9 +331,9 @@ def _pebble_layer(self) -> Layer: "summary": "Kratos Operator layer", "startup": "disabled", "command": '/bin/sh -c "{} {} 2>&1 | tee -a {}"'.format( - self._kratos_service_command, + KRATOS_SERVICE_COMMAND, self._kratos_service_params, - str(self._log_path), + str(LOG_PATH), ), "environment": { "DSN": self._dsn, @@ -499,7 +496,7 @@ def _log_level(self) -> str: def _get_available_mappers(self) -> List[str]: return [ schema_file.name[: -len("_schema.jsonnet")] - for schema_file in self._mappers_local_dir_path.iterdir() + for schema_file in MAPPERS_LOCAL_DIR_PATH.iterdir() ] def _validate_config_log_level(self) -> bool: @@ -526,8 +523,7 @@ def _render_conf_file(self) -> str: origin = "" if self._public_url: allowed_return_urls = [ - parsed_public_url._replace(path="", params="", query="", fragment="") - .geturl() + parsed_public_url._replace(path="", params="", query="", fragment="").geturl() + "/" ] origin = f"{parsed_public_url.scheme}://{parsed_public_url.hostname}" @@ -560,7 +556,7 @@ def _render_conf_file(self) -> str: @property def _recovery_email_template(self) -> Optional[str]: if self.config.get("recovery_email_template"): - return f"file://{self._email_template_file_path}" + return f"file://{EMAIL_TEMPLATE_FILE_PATH}" return None @@ -568,8 +564,10 @@ def _recovery_email_template(self) -> Optional[str]: def _smtp_connection_uri(self) -> str: smtp = self.smtp.get_relation_data() if not smtp: - logger.info("No smtp connection url found, the default placeholder value will be used. " - "Use the smtp library to integrate with an email server") + logger.info( + "No smtp connection url found, the default placeholder value will be used. " + "Use the smtp library to integrate with an email server" + ) # smtp_connection_uri is required to start the service, use a default value return "smtps://test:test@mailslurper:1025/?skip_ssl_verify=true" @@ -578,7 +576,11 @@ def _smtp_connection_uri(self) -> str: port = smtp.port transport_security = smtp.transport_security skip_ssl_verify = smtp.skip_ssl_verify - password = self.model.get_secret(id=smtp.password_id).get_content().get("password") if smtp.password_id else None + password = ( + self.model.get_secret(id=smtp.password_id).get_content().get("password") + if smtp.password_id + else None + ) if transport_security == "none": return f"smtp://{username}:{password}@{server}:{port}/?disable_starttls=true" @@ -604,7 +606,7 @@ def _update_config(self) -> None: def _get_hydra_endpoint_info(self) -> Optional[str]: oauth2_provider_url = None - if self.model.relations[self._hydra_relation_name]: + if self.model.relations[HYDRA_RELATION_NAME]: try: hydra_endpoints = self.hydra_endpoints.get_hydra_endpoints() oauth2_provider_url = hydra_endpoints["admin_endpoint"] @@ -673,7 +675,7 @@ def _get_configmap_identity_schema_config(self) -> Optional[Tuple[str, Dict]]: def _get_default_identity_schemas(self) -> Dict: schemas = {} - for schema_file in self._identity_schemas_local_dir_path.glob("*.json"): + for schema_file in IDENTITY_SCHEMAS_LOCAL_DIR_PATH.glob("*.json"): with open(schema_file) as f: schema = f.read() schemas[schema_file.stem] = schema @@ -681,9 +683,7 @@ def _get_default_identity_schemas(self) -> Dict: def _get_default_identity_schema_config(self) -> Tuple[str, Dict]: schemas = self._get_default_identity_schemas() - default_schema_id_file = ( - self._identity_schemas_local_dir_path / DEFAULT_SCHEMA_ID_FILE_NAME - ) + default_schema_id_file = IDENTITY_SCHEMAS_LOCAL_DIR_PATH / DEFAULT_SCHEMA_ID_FILE_NAME with open(default_schema_id_file) as f: default_schema_id = f.read() if default_schema_id not in schemas: @@ -715,7 +715,7 @@ def _set_version(self) -> None: def _get_claims_mappers(self) -> Dict[str, str]: mappers = {} - for file in self._mappers_local_dir_path.glob("*.jsonnet"): + for file in MAPPERS_LOCAL_DIR_PATH.glob("*.jsonnet"): with open(file) as f: mapper = f.read() mappers[file.stem] = mapper @@ -741,7 +741,7 @@ def _get_database_relation_info(self) -> Optional[Dict]: "username": relation_data.get("username"), "password": relation_data.get("password"), "endpoints": relation_data.get("endpoints"), - "database_name": self._db_name, + "database_name": relation_data.get("database"), } def _run_sql_migration(self) -> bool: @@ -824,7 +824,7 @@ def _handle_status_update_config(self, event: HookEvent) -> None: self.unit.status = MaintenanceStatus("Configuring resources") - if not self.model.relations[self._db_relation_name]: + if not self.model.relations[DB_RELATION_NAME]: self.unit.status = BlockedStatus("Missing required relation with postgresql") event.defer() return @@ -862,7 +862,7 @@ def _handle_status_update_config(self, event: HookEvent) -> None: return if template := self.config.get("recovery_email_template"): - self._container.push(self._email_template_file_path, template, make_dirs=True) + self._container.push(EMAIL_TEMPLATE_FILE_PATH, template, make_dirs=True) self.unit.status = ActiveStatus() @@ -893,9 +893,9 @@ def _on_pebble_ready(self, event: PebbleReadyEvent) -> None: event.defer() self.unit.status = WaitingStatus("Waiting to connect to Kratos container") return - if not self._container.isdir(str(self._log_dir)): - self._container.make_dir(path=str(self._log_dir), make_parents=True) - logger.info(f"Created directory {self._log_dir}") + if not self._container.isdir(str(LOG_DIR)): + self._container.make_dir(path=str(LOG_DIR), make_parents=True) + logger.info(f"Created directory {LOG_DIR}") self._set_version() self._handle_status_update_config(event) @@ -951,7 +951,7 @@ def _patch_statefulset(self) -> None: "name": WORKLOAD_CONTAINER_NAME, "volumeMounts": [ { - "mountPath": str(self._config_dir_path), + "mountPath": str(CONFIG_DIR_PATH), "name": "config", "readOnly": True, }, @@ -961,7 +961,7 @@ def _patch_statefulset(self) -> None: "volumes": [ { "name": "config", - "configMap": {"name": self._kratos_config_map_name}, + "configMap": {"name": KRATOS_CONFIG_MAP_NAME}, }, ], } @@ -1234,7 +1234,9 @@ def _on_reset_identity_mfa_action(self, event: ActionEvent) -> None: event.log("Resetting user's second authentication factor") try: - credential_deleted = self.kratos.delete_mfa_credential(identity_id=identity_id, mfa_type=mfa_type) + credential_deleted = self.kratos.delete_mfa_credential( + identity_id=identity_id, mfa_type=mfa_type + ) if not credential_deleted: event.log(f"User has no {mfa_type} credentials") return diff --git a/src/constants.py b/src/constants.py index 135b95ab..b5c61115 100644 --- a/src/constants.py +++ b/src/constants.py @@ -3,6 +3,45 @@ """File containing all constants.""" -INTERNAL_INGRESS_RELATION_NAME = "internal-ingress" +from pathlib import Path + +# Charm constants WORKLOAD_CONTAINER_NAME = "kratos" -CERTIFICATE_TRANSFER_NAME = "receive-ca-cert" +EMAIL_TEMPLATE_FILE_PATH = Path("/etc/config/templates") / "recovery-body.html.gotmpl" +MAPPERS_LOCAL_DIR_PATH = Path("claim_mappers") + +# Application constants +KRATOS_ADMIN_PORT = 4434 +KRATOS_PUBLIC_PORT = 4433 +KRATOS_SERVICE_COMMAND = "kratos serve all" +DEFAULT_SCHEMA_ID_FILE_NAME = "default.schema" +KRATOS_CONFIG_MAP_NAME = "kratos-config" +LOG_LEVELS = ["panic", "fatal", "error", "warn", "info", "debug", "trace"] +CONFIG_DIR_PATH = Path("/etc/config/kratos") +LOG_DIR = Path("/var/log") +LOG_PATH = LOG_DIR / "kratos.log" +CONFIG_FILE_PATH = CONFIG_DIR_PATH / "kratos.yaml" +IDENTITY_SCHEMAS_LOCAL_DIR_PATH = Path("identity_schemas") + +# Integration constants +PEER_RELATION_NAME = "kratos-peers" +INTERNAL_INGRESS_RELATION_NAME = "internal-ingress" +CERTIFICATE_TRANSFER_RELATION_NAME = "receive-ca-cert" +DB_RELATION_NAME = "pg-database" +HYDRA_RELATION_NAME = "hydra-endpoint-info" +LOGIN_UI_RELATION_NAME = "ui-endpoint-info" +PROMETHEUS_SCRAPE_RELATION_NAME = "metrics-endpoint" +LOKI_PUSH_API_RELATION_NAME = "logging" +GRAFANA_DASHBOARD_RELATION_NAME = "grafana-dashboard" +TRACING_RELATION_NAME = "tracing" + +# Peer data keys +PEER_KEY_DB_MIGRATE_VERSION = "db_migrate_version" + +# Secret constants +SECRET_LABEL = "cookie_secret" +COOKIE_SECRET_KEY = "cookiesecret" + +# Cert transfer constants +LOCAL_CA_CERTS_PATH = Path("/usr/local/share/ca-certificates") +CA_BUNDLE_PATH = "/etc/ssl/certs/ca-certificates.crt" diff --git a/src/kratos.py b/src/kratos.py index 1ed4183c..da7e1b2e 100644 --- a/src/kratos.py +++ b/src/kratos.py @@ -13,16 +13,17 @@ import requests from ops.model import Container +from constants import CONFIG_FILE_PATH + logger = logging.getLogger(__name__) class KratosAPI: """A helper object for interacting with the kratos API.""" - def __init__(self, kratos_admin_url: str, container: Container, config_file_path: str) -> None: + def __init__(self, kratos_admin_url: str, container: Container) -> None: self.kratos_admin_url = kratos_admin_url self.container = container - self.config_file_path = config_file_path def create_identity( self, traits: Dict, schema_id: str, password: Optional[str] = None @@ -128,7 +129,9 @@ def reset_password(self, identity_id: str, password: str) -> Dict: credentials = { "password": { "config": { - "hashed_password": bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt()).decode("utf-8") + "hashed_password": bcrypt.hashpw( + password.encode("utf-8"), bcrypt.gensalt() + ).decode("utf-8") } } } @@ -136,7 +139,12 @@ def reset_password(self, identity_id: str, password: str) -> Dict: # Update the identity with new password. # Note that passwords can't be updated with Kratos CLI url = join(self.kratos_admin_url, f"admin/identities/{identity_id}") - data = {"state": state, "traits": traits, "schema_id": schema_id, "credentials": credentials} + data = { + "state": state, + "traits": traits, + "schema_id": schema_id, + "credentials": credentials, + } r = requests.put(url, json=data) r.raise_for_status() @@ -189,7 +197,7 @@ def run_migration(self, dsn=None, timeout: float = 120) -> str: env = {"DSN": dsn} else: cmd.append("--config") - cmd.append(self.config_file_path) + cmd.append(CONFIG_FILE_PATH) env = None return self._run_cmd(cmd, timeout=timeout, environment=env) diff --git a/tests/integration/test_charm.py b/tests/integration/test_charm.py index 14b7ec68..bfee81f9 100644 --- a/tests/integration/test_charm.py +++ b/tests/integration/test_charm.py @@ -177,7 +177,9 @@ async def test_get_identity(ops_test: OpsTest) -> None: async def test_reset_password(ops_test: OpsTest) -> None: secret_name = "password-secret" - secret_id = await ops_test.model.add_secret(name=secret_name, data_args=["password=some-password"]) + secret_id = await ops_test.model.add_secret( + name=secret_name, data_args=["password=some-password"] + ) await ops_test.model.grant_secret(secret_name=secret_name, application=KRATOS_APP) action = ( diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index 2dff7fdb..74453b3f 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -43,7 +43,7 @@ def mocked_kratos_process() -> MagicMock: def kratos_api(mocked_kratos_process: MagicMock) -> KratosAPI: container = MagicMock() container.exec = MagicMock(return_value=mocked_kratos_process) - return KratosAPI("http://localhost:4434", container, "/etc/config/kratos.yaml") + return KratosAPI("http://localhost:4434", container) @pytest.fixture() @@ -218,25 +218,19 @@ def mocked_recover_password_with_code( @pytest.fixture() def mocked_reset_password(mocker: MockerFixture, kratos_identity_json: Dict) -> MagicMock: - mock = mocker.patch( - "charm.KratosAPI.reset_password", return_value=kratos_identity_json - ) + mock = mocker.patch("charm.KratosAPI.reset_password", return_value=kratos_identity_json) return mock @pytest.fixture() def mocked_invalidate_sessions(mocker: MockerFixture) -> MagicMock: - mock = mocker.patch( - "charm.KratosAPI.invalidate_sessions", return_value=True - ) + mock = mocker.patch("charm.KratosAPI.invalidate_sessions", return_value=True) return mock @pytest.fixture() def mocked_delete_mfa_credential(mocker: MockerFixture) -> MagicMock: - mock = mocker.patch( - "charm.KratosAPI.delete_mfa_credential", return_value=True - ) + mock = mocker.patch("charm.KratosAPI.delete_mfa_credential", return_value=True) return mock diff --git a/tests/unit/test_charm.py b/tests/unit/test_charm.py index 4a6caee7..87334dc9 100644 --- a/tests/unit/test_charm.py +++ b/tests/unit/test_charm.py @@ -51,6 +51,7 @@ def setup_postgres_relation(harness: Harness) -> None: "postgresql-k8s", { "data": '{"database": "database", "extra-user-roles": "SUPERUSER"}', + "database": "kratos-model_kratos", "endpoints": DB_ENDPOINTS, "password": DB_PASSWORD, "username": DB_USERNAME, @@ -193,7 +194,7 @@ def setup_smtp_relation(harness: Harness, transport_security: str, skip_ssl_veri "transport_security": transport_security, "domain": "domain", "skip_ssl_verify": skip_ssl_verify, - } + }, ) return smtp_relation_id @@ -509,7 +510,7 @@ def test_on_pebble_ready_lk_called( ("starttls", "smtp", "False", ""), ("tls", "smtps", "True", "?skip_ssl_verify=true"), ("starttls", "smtp", "True", "?skip_ssl_verify=true"), - ] + ], ) def test_config_file_with_smtp_integration( harness: Harness, @@ -555,7 +556,9 @@ def test_config_file_with_smtp_integration( "default_browser_return_url": DEFAULT_BROWSER_RETURN_URL, }, "courier": { - "smtp": {"connection_uri": f"{method}://example_user:some-password@example.smtp:25/{additional_param}"}, + "smtp": { + "connection_uri": f"{method}://example_user:some-password@example.smtp:25/{additional_param}" + }, }, "serve": { "public": { @@ -1024,10 +1027,10 @@ def test_on_client_config_changed_with_ingress( "after": { "default_browser_return_url": login_databag["login_url"], "hooks": [ - { - "hook": "revoke_active_sessions", - }, - ], + { + "hook": "revoke_active_sessions", + }, + ], }, }, "registration": { @@ -1048,15 +1051,15 @@ def test_on_client_config_changed_with_ingress( }, "password": { "enabled": True, - "config": { - "haveibeenpwned_enabled": False, - }, + "config": { + "haveibeenpwned_enabled": False, + }, + }, + "totp": { + "enabled": True, + "config": { + "issuer": "Identity Platform", }, - "totp": { - "enabled": True, - "config": { - "issuer": "Identity Platform", - }, }, "oidc": { "config": { @@ -1661,7 +1664,7 @@ def test_on_config_changed_when_recovery_email_template_set( harness: Harness, mocked_kratos_configmap: MagicMock, mocked_migration_is_needed: MagicMock, - mocked_recovery_email_template: MagicMock + mocked_recovery_email_template: MagicMock, ) -> None: setup_peer_relation(harness) setup_postgres_relation(harness) @@ -1676,9 +1679,7 @@ def test_on_config_changed_when_recovery_email_template_set( def test_on_config_changed_when_local_idp_disabled( - harness: Harness, - mocked_kratos_configmap: MagicMock, - mocked_migration_is_needed: MagicMock + harness: Harness, mocked_kratos_configmap: MagicMock, mocked_migration_is_needed: MagicMock ) -> None: setup_peer_relation(harness) setup_postgres_relation(harness) @@ -1765,9 +1766,7 @@ def test_on_config_changed_when_local_idp_disabled( def test_on_config_changed_when_webauthn_enabled( - harness: Harness, - mocked_kratos_configmap: MagicMock, - mocked_migration_is_needed: MagicMock + harness: Harness, mocked_kratos_configmap: MagicMock, mocked_migration_is_needed: MagicMock ) -> None: setup_peer_relation(harness) setup_postgres_relation(harness) @@ -1855,7 +1854,7 @@ def test_on_config_changed_when_webauthn_enabled( "id": "public", "origins": ["https://public"], "display_name": "Identity Platform", - } + }, }, }, }, @@ -2186,7 +2185,9 @@ def test_error_on_reset_mfa_action_with_identity_id_when_mfa_type_uncorrect( harness.charm._on_reset_identity_mfa_action(event) - event.fail.assert_called_with(f"Unsupported MFA credential type {unsupported_type}, allowed methods are: `totp` and `lookup_secret`") + event.fail.assert_called_with( + f"Unsupported MFA credential type {unsupported_type}, allowed methods are: `totp` and `lookup_secret`" + ) @pytest.mark.parametrize("mfa_type", ["totp", "lookup_secret"]) diff --git a/tests/unit/test_kratos.py b/tests/unit/test_kratos.py index 235a307f..3ad067b2 100644 --- a/tests/unit/test_kratos.py +++ b/tests/unit/test_kratos.py @@ -9,6 +9,7 @@ from ops.pebble import ExecError from pytest_mock import MockerFixture +from constants import CONFIG_FILE_PATH from kratos import KratosAPI @@ -207,9 +208,7 @@ def test_reset_password( assert ret == kratos_identity_json -def test_invalidate_sessions( - kratos_api: KratosAPI, mocker: MockerFixture -) -> None: +def test_invalidate_sessions(kratos_api: KratosAPI, mocker: MockerFixture) -> None: mocker.patch("requests.delete") ret = kratos_api.invalidate_sessions("identity_id") @@ -217,9 +216,7 @@ def test_invalidate_sessions( assert ret -def test_delete_mfa_credential( - kratos_api: KratosAPI, mocker: MockerFixture -) -> None: +def test_delete_mfa_credential(kratos_api: KratosAPI, mocker: MockerFixture) -> None: mocker.patch("requests.delete") ret = kratos_api.delete_mfa_credential("identity_id", "totp") @@ -240,7 +237,7 @@ def test_run_migration(kratos_api: KratosAPI, mocked_kratos_process: MagicMock) "-e", "--yes", "--config", - kratos_api.config_file_path, + CONFIG_FILE_PATH, ] assert expected_output == cmd_output