from __future__ import annotations from collections.abc import Mapping from dataclasses import dataclass, replace import re from typing import Any from sqlalchemy import func, or_, select from sqlalchemy.orm import Session from govoplan_core.core.configuration_packages import ( ConfigurationApplyResult, ConfigurationDiagnostic, ConfigurationExportResult, ConfigurationExportSelection, ConfigurationPackageFragment, ConfigurationPlanItem, ConfigurationPreflightContext, ConfigurationPreflightResult, ConfigurationProvider, ConfigurationProviderDescription, ConfigurationRequiredData, ) from govoplan_core.core.infrastructure_capabilities import ( InfrastructureCapability, InfrastructureCapabilityReceipt, InfrastructureDependency, InfrastructureDependencyProvider, ) from govoplan_core.security.credential_envelopes import ( CredentialAccessContext, CredentialEnvelope, credential_visible_to_context, ) from govoplan_core.db.session import get_database from govoplan_mail.backend.config import SmtpConfig, SmtpServerConfig from govoplan_mail.backend.db.models import ( MailServerCredentialBinding, MailServerEndpoint, MailServerProfile, ) from govoplan_mail.backend.mail_profiles import ( create_mail_server_profile, slugify_profile_name, update_mail_server_profile, ) from govoplan_mail.backend.server_hierarchy import ( bind_mail_credential, initialize_profile_hierarchy, mail_server_ref, sync_default_profile_server, ) MAIL_CONFIGURATION_CAPABILITY = "mail.configuration" MAIL_INFRASTRUCTURE_DEPENDENCY_CAPABILITY = ( "infrastructure.dependency_inventory.mail" ) SMTP_PROFILE_FRAGMENT = "smtp_profile" _SYSTEM_CONFIGURATION_SCOPES = frozenset( {"system:settings:write", "system:governance:write"} ) _PAYLOAD_KEYS = frozenset( { "capability_id", "profile", "smtp", "credential_envelope_id", "credential_is_default", "input_keys", "on_conflict", } ) _PROFILE_KEYS = frozenset( { "name", "slug", "description", "scope_type", "inherit_to_lower_scopes", } ) _SMTP_KEYS = frozenset({"host", "port", "security", "timeout_seconds"}) _INPUT_KEY_FIELDS = frozenset({"host", "port", "security", "credential_envelope_id"}) @dataclass(frozen=True, slots=True) class _DesiredSmtpProfile: fragment_ref: str tenant_id: str | None scope_type: str scope_id: str | None name: str slug: str description: str | None inherit_to_lower_scopes: bool smtp: SmtpConfig credential_envelope_id: str | None credential_is_default: bool on_conflict: str capability_source: str installation_id: str operator_user_id: str | None @property def smtp_payload(self) -> dict[str, object]: payload = self.smtp.model_dump(mode="json") payload.pop("username", None) payload.pop("password", None) return payload @dataclass(frozen=True, slots=True) class _ParsedMailFragment: payload: Mapping[str, Any] profile: Mapping[str, Any] smtp: Mapping[str, Any] input_keys: Mapping[str, Any] @dataclass(frozen=True, slots=True) class _ResolvedSmtpInputs: smtp: SmtpConfig credential_envelope_id: str | None @dataclass(frozen=True, slots=True) class _ProfileTarget: tenant_id: str | None scope_type: str scope_id: str | None name: str slug: str description: str | None inherit_to_lower_scopes: bool credential_is_default: bool on_conflict: str class SqlMailConfigurationProvider( ConfigurationProvider, InfrastructureDependencyProvider, ): module_id = "mail" capability_ids = ("mail.smtp",) def describe(self) -> ConfigurationProviderDescription: return ConfigurationProviderDescription( module_id=self.module_id, fragment_types=(SMTP_PROFILE_FRAGMENT,), schema_refs={ SMTP_PROFILE_FRAGMENT: "govoplan/mail/configuration/smtp-profile.v1" }, exported_scopes=("system", "tenant"), ) def preflight( self, fragment: ConfigurationPackageFragment, context: ConfigurationPreflightContext, ) -> ConfigurationPreflightResult: if fragment.fragment_type != SMTP_PROFILE_FRAGMENT: return ConfigurationPreflightResult( diagnostics=(_unsupported(fragment),), plan=(_blocked_plan(fragment, "Fragment type is unsupported."),), ) with get_database().session() as session: return _preflight_smtp_profile(session, fragment, context) def apply( self, fragment: ConfigurationPackageFragment, supplied_data: Mapping[str, Any], context: ConfigurationPreflightContext, ) -> ConfigurationApplyResult: if fragment.fragment_type != SMTP_PROFILE_FRAGMENT: return ConfigurationApplyResult(diagnostics=(_unsupported(fragment),)) apply_context = replace(context, supplied_data=supplied_data, dry_run=False) with get_database().session() as session: preflight = _preflight_smtp_profile(session, fragment, apply_context) blockers = tuple( item for item in preflight.diagnostics if item.severity == "blocker" ) if blockers: return ConfigurationApplyResult(diagnostics=blockers) desired, diagnostics, _required_data = _desired_smtp_profile( fragment, apply_context, ) if desired is None: return ConfigurationApplyResult(diagnostics=tuple(diagnostics)) result = _apply_smtp_profile(session, desired) if not any(item.severity == "blocker" for item in result.diagnostics): session.commit() return result def export( self, selection: ConfigurationExportSelection, context: ConfigurationPreflightContext, ) -> ConfigurationExportResult: del context with get_database().session() as session: return _export_smtp_profiles(session, selection) def health( self, import_result: ConfigurationApplyResult, context: ConfigurationPreflightContext, ) -> tuple[ConfigurationDiagnostic, ...]: del context return tuple( item for item in import_result.diagnostics if item.severity == "blocker" ) def infrastructure_dependencies(self) -> tuple[InfrastructureDependency, ...]: with get_database().session() as session: return _smtp_infrastructure_dependencies(session) def _smtp_infrastructure_dependencies( session: Session, ) -> tuple[InfrastructureDependency, ...]: endpoints = tuple( session.execute( select(MailServerEndpoint) .where(MailServerEndpoint.protocol == "smtp") .order_by(MailServerEndpoint.id) ).scalars() ) binding_counts = { str(server_id): int(count) for server_id, count in session.execute( select( MailServerCredentialBinding.server_id, func.count(MailServerCredentialBinding.id), ).group_by(MailServerCredentialBinding.server_id) ) } dependencies: list[InfrastructureDependency] = [] endpoint_profile_ids: set[str] = set() for endpoint in endpoints: endpoint_profile_ids.add(endpoint.profile_id) dependencies.append( InfrastructureDependency( capability_id="mail.smtp", module_id="mail", dependency_type="smtp_endpoint", dependency_ref=mail_server_ref(endpoint.id) or f"mail:{endpoint.id}", state="active" if endpoint.is_active else "inactive", scope=str(endpoint.scope_type or "tenant"), summary=( "A persisted Mail SMTP endpoint is bound to the deployment relay." ), metrics={ "credential_binding_count": binding_counts.get(endpoint.id, 0), "default_endpoint": int(bool(endpoint.is_default)), }, required_action=( "Rebind, migrate, or explicitly retire this SMTP endpoint and its credential-envelope references before changing the relay capability." ), ) ) legacy_profiles = tuple( session.execute( select(MailServerProfile) .where(MailServerProfile.smtp_config.is_not(None)) .order_by(MailServerProfile.id) ).scalars() ) for profile in legacy_profiles: if profile.id in endpoint_profile_ids or not dict(profile.smtp_config or {}): continue dependencies.append( InfrastructureDependency( capability_id="mail.smtp", module_id="mail", dependency_type="legacy_smtp_profile", dependency_ref=f"mail-profile:{profile.id}", state="active" if profile.is_active else "inactive", scope=str(profile.scope_type or "tenant"), summary=( "A persisted legacy Mail profile still contains SMTP transport configuration." ), metrics={"credential_binding_count": 0}, required_action=( "Migrate or explicitly retire this legacy profile before changing the relay capability." ), ) ) return tuple(dependencies) def _preflight_smtp_profile( session: Session, fragment: ConfigurationPackageFragment, context: ConfigurationPreflightContext, ) -> ConfigurationPreflightResult: desired, diagnostics, required_data = _desired_smtp_profile(fragment, context) if desired is None: return ConfigurationPreflightResult( diagnostics=tuple(diagnostics), required_data=tuple(required_data), plan=(_blocked_plan(fragment, "SMTP profile inputs are incomplete."),), ) profile = _profile_by_identity(session, desired) if profile is None: credential_diagnostics = _credential_diagnostics( session, desired, server=None, ) diagnostics.extend(credential_diagnostics) action = "blocked" if _has_blockers(diagnostics) else "create" return ConfigurationPreflightResult( diagnostics=tuple(diagnostics), required_data=tuple(required_data), plan=( ConfigurationPlanItem( action=action, module_id="mail", fragment_type=SMTP_PROFILE_FRAGMENT, fragment_id=desired.fragment_ref, summary=( "Create a receipt-bound SMTP profile and optional credential binding." if action == "create" else "SMTP profile creation is blocked." ), ), ), ) server = _default_smtp_server(session, profile.id) credential_diagnostics = _credential_diagnostics( session, desired, server=server, ) diagnostics.extend(credential_diagnostics) changes = _profile_changes(session, profile, server, desired) if changes and desired.on_conflict == "preserve": diagnostics.append( ConfigurationDiagnostic( severity="blocker", code="mail_configuration_conflict", message=( f"Mail profile {desired.slug!r} already exists with different " f"configuration ({', '.join(changes)})." ), module_id="mail", object_ref=desired.fragment_ref, resolution=( "Keep the existing profile, choose another stable slug, or review " "the package with on_conflict set to update." ), ) ) if _has_blockers(diagnostics): action = "blocked" summary = ( "Existing SMTP profile is preserved because the reviewed change is blocked." ) elif changes: action = "update" summary = f"Update receipt-bound SMTP profile fields: {', '.join(changes)}." else: action = "skip" summary = "SMTP profile and credential binding already match the receipt." return ConfigurationPreflightResult( diagnostics=tuple(diagnostics), required_data=tuple(required_data), plan=( ConfigurationPlanItem( action=action, module_id="mail", fragment_type=SMTP_PROFILE_FRAGMENT, fragment_id=desired.fragment_ref, summary=summary, ), ), ) def _desired_smtp_profile( fragment: ConfigurationPackageFragment, context: ConfigurationPreflightContext, ) -> tuple[ _DesiredSmtpProfile | None, list[ConfigurationDiagnostic], list[ConfigurationRequiredData], ]: diagnostics: list[ConfigurationDiagnostic] = [] parsed = _parse_mail_fragment(fragment, diagnostics) if parsed is None: return None, diagnostics, [] receipt_state = _mail_receipt_capability( fragment, context, parsed.payload, diagnostics, ) if receipt_state is None: return None, diagnostics, [] receipt, capability = receipt_state resolved, required_data = _resolve_smtp_inputs( fragment, context, parsed, receipt, capability, diagnostics, ) if resolved is None: return None, diagnostics, required_data target = _resolve_profile_target( fragment, context, parsed, receipt, diagnostics, ) if target is None: return None, diagnostics, required_data fragment_ref = ( fragment.fragment_id or f"{receipt.installation_id}:mail.smtp:{target.slug}" ) return ( _DesiredSmtpProfile( fragment_ref=fragment_ref, tenant_id=target.tenant_id, scope_type=target.scope_type, scope_id=target.scope_id, name=target.name, slug=target.slug, description=target.description, inherit_to_lower_scopes=target.inherit_to_lower_scopes, smtp=resolved.smtp, credential_envelope_id=resolved.credential_envelope_id, credential_is_default=target.credential_is_default, on_conflict=target.on_conflict, capability_source=capability.source, installation_id=receipt.installation_id, operator_user_id=context.operator_user_id, ), diagnostics, required_data, ) def _parse_mail_fragment( fragment: ConfigurationPackageFragment, diagnostics: list[ConfigurationDiagnostic], ) -> _ParsedMailFragment | None: payload = fragment.payload if not isinstance(payload, Mapping): diagnostics.append( _invalid(fragment, "SMTP profile payload must be an object.") ) return None unknown_payload = sorted(set(payload) - _PAYLOAD_KEYS) if unknown_payload: diagnostics.append(_unknown_payload_diagnostic(fragment, unknown_payload)) profile = _mapping( payload.get("profile"), field_name="profile", fragment=fragment, diagnostics=diagnostics, ) smtp = _mapping( payload.get("smtp"), field_name="smtp", fragment=fragment, diagnostics=diagnostics, ) input_keys = _mapping( payload.get("input_keys"), field_name="input_keys", fragment=fragment, diagnostics=diagnostics, ) _unknown_fields(profile, _PROFILE_KEYS, "profile", fragment, diagnostics) unknown_smtp = sorted(set(smtp) - _SMTP_KEYS) if unknown_smtp: diagnostics.append(_unknown_smtp_diagnostic(fragment, unknown_smtp)) _unknown_fields(input_keys, _INPUT_KEY_FIELDS, "input_keys", fragment, diagnostics) if _has_blockers(diagnostics): return None return _ParsedMailFragment( payload=payload, profile=profile, smtp=smtp, input_keys=input_keys, ) def _mail_receipt_capability( fragment: ConfigurationPackageFragment, context: ConfigurationPreflightContext, payload: Mapping[str, Any], diagnostics: list[ConfigurationDiagnostic], ) -> tuple[InfrastructureCapabilityReceipt, InfrastructureCapability] | None: if context.infrastructure_receipt_error: diagnostics.append( ConfigurationDiagnostic( severity="blocker", code="infrastructure_receipt_invalid", message=f"The deployment capability receipt is invalid: {context.infrastructure_receipt_error}", module_id="mail", object_ref=fragment.fragment_id or SMTP_PROFILE_FRAGMENT, resolution="Repair or regenerate the deployment receipt before importing Mail configuration.", ) ) return None receipt = context.infrastructure_receipt if receipt is None: diagnostics.append( ConfigurationDiagnostic( severity="blocker", code="infrastructure_receipt_missing", message="Mail SMTP configuration requires the mounted deployment capability receipt.", module_id="mail", object_ref=fragment.fragment_id or SMTP_PROFILE_FRAGMENT, resolution="Mount the installer-generated capability receipt and rerun preflight.", ) ) return None capability_id = _text(payload.get("capability_id")) or "mail.smtp" if capability_id != "mail.smtp": diagnostics.append( _invalid( fragment, "Mail SMTP fragments must reference capability 'mail.smtp'.", ) ) return None capability = receipt.capability(capability_id) if capability is None: diagnostics.append( ConfigurationDiagnostic( severity="blocker", code="infrastructure_capability_missing", message="The deployment receipt does not declare Mail SMTP infrastructure.", module_id="mail", object_ref=capability_id, resolution="Regenerate the receipt from a deployment profile that declares Mail infrastructure.", ) ) return None if capability.state == "unavailable": diagnostics.append( ConfigurationDiagnostic( severity="blocker", code="infrastructure_capability_unavailable", message="The deployment receipt states that SMTP infrastructure is unavailable.", module_id="mail", object_ref=capability_id, resolution="Reconfigure the deployment with test mail or an external SMTP relay before importing this profile.", ) ) return None return receipt, capability def _resolve_smtp_inputs( fragment: ConfigurationPackageFragment, context: ConfigurationPreflightContext, parsed: _ParsedMailFragment, receipt: InfrastructureCapabilityReceipt, capability: InfrastructureCapability, diagnostics: list[ConfigurationDiagnostic], ) -> tuple[_ResolvedSmtpInputs | None, list[ConfigurationRequiredData]]: prefix = _input_prefix(fragment, receipt.installation_id) key_map = { field: _text(parsed.input_keys.get(field)) or f"{prefix}.{field}" for field in _INPUT_KEY_FIELDS } endpoint = capability.endpoint values = { field: _receipt_or_input_value( field=field, receipt_value=endpoint.get(field), payload=parsed.smtp, supplied_data=context.supplied_data, input_key=key_map[field], fragment=fragment, diagnostics=diagnostics, ) for field in ("host", "port", "security") } if values["security"] is None: values["security"] = _inferred_security( source=capability.source, scheme=_text(endpoint.get("scheme")), port=values["port"], ) required_data = _smtp_required_data(values, key_map) credential_envelope_id = _text( parsed.payload.get("credential_envelope_id") ) or _text(context.supplied_data.get(key_map["credential_envelope_id"])) credential_required = bool(capability.secret_refs) required_data.append( ConfigurationRequiredData( key=key_map["credential_envelope_id"], label="SMTP credential envelope id", data_type="reference:credential-envelope", required=credential_required and credential_envelope_id is None, secret=False, description=( "An existing credential envelope is required by the receipt; plaintext credentials are not accepted." if credential_required else "Optional existing credential envelope; plaintext credentials are not accepted." ), ) ) missing = [item for item in required_data if item.required] if missing: diagnostics.append( ConfigurationDiagnostic( severity="blocker", code="mail_configuration_input_required", message=( "Mail SMTP configuration still needs reviewed operator data: " + ", ".join(item.label for item in missing) + "." ), module_id="mail", object_ref=fragment.fragment_id or SMTP_PROFILE_FRAGMENT, resolution="Provide the listed non-secret values or credential-envelope reference and rerun preflight.", ) ) return None, required_data try: smtp = SmtpConfig.model_validate( { **values, "timeout_seconds": parsed.smtp.get("timeout_seconds", 30), } ) except Exception as exc: diagnostics.append( _invalid(fragment, f"SMTP server metadata is invalid: {exc}") ) return None, required_data return ( _ResolvedSmtpInputs( smtp=smtp, credential_envelope_id=credential_envelope_id, ), required_data, ) def _resolve_profile_target( fragment: ConfigurationPackageFragment, context: ConfigurationPreflightContext, parsed: _ParsedMailFragment, receipt: InfrastructureCapabilityReceipt, diagnostics: list[ConfigurationDiagnostic], ) -> _ProfileTarget | None: scope_type = (_text(parsed.profile.get("scope_type")) or "tenant").casefold() if scope_type not in {"system", "tenant"}: diagnostics.append( _invalid( fragment, "Deployment SMTP profiles may use only system or tenant scope.", ) ) return None if scope_type == "tenant" and not context.tenant_id: diagnostics.append( ConfigurationDiagnostic( severity="blocker", code="tenant_required", message="A tenant-scoped Mail profile requires a selected tenant.", module_id="mail", object_ref=fragment.fragment_id or SMTP_PROFILE_FRAGMENT, resolution="Select a tenant or review a system-scoped package with system authority.", ) ) return None if scope_type == "system" and not _SYSTEM_CONFIGURATION_SCOPES.intersection( context.operator_scopes ): diagnostics.append( ConfigurationDiagnostic( severity="blocker", code="system_configuration_authority_required", message="Creating a system-wide Mail profile requires system configuration authority.", module_id="mail", object_ref=fragment.fragment_id or SMTP_PROFILE_FRAGMENT, resolution="Use a tenant profile or ask a system administrator to apply the package.", ) ) return None on_conflict = (_text(parsed.payload.get("on_conflict")) or "preserve").casefold() if on_conflict not in {"preserve", "update"}: diagnostics.append( _invalid(fragment, "on_conflict must be 'preserve' or 'update'.") ) return None name = _text(parsed.profile.get("name")) or ( f"Deployment SMTP ({receipt.installation_id})" ) slug = slugify_profile_name( _text(parsed.profile.get("slug")) or f"deployment-{receipt.installation_id}-smtp" )[:100] return _ProfileTarget( tenant_id=None if scope_type == "system" else context.tenant_id, scope_type=scope_type, scope_id=None if scope_type == "system" else context.tenant_id, name=name[:255], slug=slug, description=( _text(parsed.profile.get("description")) or f"SMTP profile bound to deployment capability receipt {receipt.installation_id}." ), inherit_to_lower_scopes=_bool( parsed.profile.get("inherit_to_lower_scopes"), default=True, ), credential_is_default=_bool( parsed.payload.get("credential_is_default"), default=True, ), on_conflict=on_conflict, ) def _apply_smtp_profile( session: Session, desired: _DesiredSmtpProfile, ) -> ConfigurationApplyResult: profile = _profile_by_identity(session, desired) created: dict[str, str] = {} updated: dict[str, str] = {} if profile is None: profile = create_mail_server_profile( session, tenant_id=desired.tenant_id or "system", user_id=desired.operator_user_id, name=desired.name, slug=desired.slug, description=desired.description, smtp=desired.smtp, imap=None, is_active=True, inherit_to_lower_scopes=desired.inherit_to_lower_scopes, scope_type=desired.scope_type, scope_id=desired.scope_id, ) initialize_profile_hierarchy( session, profile=profile, smtp=desired.smtp, imap=None, user_id=desired.operator_user_id, ) created[desired.fragment_ref] = f"mail_profile:{profile.id}" else: server = _default_smtp_server(session, profile.id) changes = _profile_changes(session, profile, server, desired) if changes: update_mail_server_profile( session, profile, user_id=desired.operator_user_id, tenant_id=desired.tenant_id or "system", name=desired.name, description=desired.description, is_active=True if not profile.is_active else None, inherit_to_lower_scopes=desired.inherit_to_lower_scopes, smtp=desired.smtp, ) sync_default_profile_server( session, profile=profile, protocol="smtp", config=desired.smtp_payload, user_id=desired.operator_user_id, ) updated[desired.fragment_ref] = f"mail_profile:{profile.id}" server = _default_smtp_server(session, profile.id) if server is None: server = sync_default_profile_server( session, profile=profile, protocol="smtp", config=desired.smtp_payload, user_id=desired.operator_user_id, ) if desired.credential_envelope_id and server is not None: credential = session.get(CredentialEnvelope, desired.credential_envelope_id) if credential is None: return ConfigurationApplyResult( diagnostics=( ConfigurationDiagnostic( severity="blocker", code="mail_credential_envelope_missing", message="The reviewed SMTP credential envelope no longer exists.", module_id="mail", object_ref=desired.fragment_ref, resolution="Rerun preflight and select an available credential envelope.", ), ) ) binding = _credential_binding( session, server.id, desired.credential_envelope_id, ) was_default = bool(binding and binding.is_default) bind_mail_credential( session, server=server, credential=credential, user_id=desired.operator_user_id, is_default=desired.credential_is_default, ) if binding is None or (desired.credential_is_default and not was_default): updated.setdefault( desired.fragment_ref, f"mail_profile:{profile.id}", ) return ConfigurationApplyResult(created_refs=created, updated_refs=updated) def _export_smtp_profiles( session: Session, selection: ConfigurationExportSelection, ) -> ConfigurationExportResult: clauses = [] if selection.tenant_id: clauses.append( (MailServerProfile.scope_type == "tenant") & (MailServerProfile.scope_id == selection.tenant_id) ) if "system" in selection.scopes: clauses.append(MailServerProfile.scope_type == "system") if not clauses: return ConfigurationExportResult( diagnostics=( ConfigurationDiagnostic( severity="blocker", code="tenant_required", message="Mail configuration export requires a tenant or system scope.", module_id="mail", resolution="Select a tenant or explicitly include system scope.", ), ) ) statement = select(MailServerProfile) if len(clauses) == 1: statement = statement.where(clauses[0]) else: statement = statement.where(or_(*clauses)) requested = {item.removeprefix("mail_profile:") for item in selection.object_refs} if requested: statement = statement.where(MailServerProfile.id.in_(requested)) profiles = session.execute(statement.order_by(MailServerProfile.slug)).scalars() fragments: list[ConfigurationPackageFragment] = [] requirements: list[ConfigurationRequiredData] = [] for profile in profiles: server = _default_smtp_server(session, profile.id) smtp_payload = dict( server.config if server is not None else profile.smtp_config or {} ) for key in ("username", "password", "enabled"): smtp_payload.pop(key, None) fragment_id = f"mail-profile-{profile.slug}" credential_key = f"mail.smtp.{_safe_key(profile.slug)}.credential_envelope_id" fragments.append( ConfigurationPackageFragment( module_id="mail", fragment_type=SMTP_PROFILE_FRAGMENT, fragment_id=fragment_id, payload={ "capability_id": "mail.smtp", "profile": { "name": profile.name, "slug": profile.slug, "description": profile.description, "scope_type": profile.scope_type, "inherit_to_lower_scopes": bool( profile.inherit_to_lower_scopes ), }, "smtp": smtp_payload, "input_keys": {"credential_envelope_id": credential_key}, "on_conflict": "preserve", }, ) ) requirements.append( ConfigurationRequiredData( key=credential_key, label=f"Credential envelope for {profile.name}", data_type="reference:credential-envelope", required=False, description="Select a target-environment credential envelope; credentials are never exported.", ) ) return ConfigurationExportResult( fragments=tuple(fragments), data_requirements=tuple(requirements), ) def _profile_by_identity( session: Session, desired: _DesiredSmtpProfile, ) -> MailServerProfile | None: statement = select(MailServerProfile).where( MailServerProfile.scope_type == desired.scope_type, MailServerProfile.slug == desired.slug, ) if desired.scope_id is None: statement = statement.where(MailServerProfile.scope_id.is_(None)) else: statement = statement.where(MailServerProfile.scope_id == desired.scope_id) return session.execute(statement).scalar_one_or_none() def _default_smtp_server( session: Session, profile_id: str, ) -> MailServerEndpoint | None: return ( session.execute( select(MailServerEndpoint) .where( MailServerEndpoint.profile_id == profile_id, MailServerEndpoint.protocol == "smtp", ) .order_by( MailServerEndpoint.is_default.desc(), MailServerEndpoint.is_active.desc(), MailServerEndpoint.name, MailServerEndpoint.id, ) ) .scalars() .first() ) def _profile_changes( session: Session, profile: MailServerProfile, server: MailServerEndpoint | None, desired: _DesiredSmtpProfile, ) -> list[str]: changes = _profile_metadata_changes(profile, desired) changes.extend(_smtp_server_changes(profile, server, desired)) changes.extend(_credential_binding_changes(session, server, desired)) return list(dict.fromkeys(changes)) def _profile_metadata_changes( profile: MailServerProfile, desired: _DesiredSmtpProfile, ) -> list[str]: changes: list[str] = [] if profile.name != desired.name: changes.append("name") if (profile.description or None) != (desired.description or None): changes.append("description") if not profile.is_active: changes.append("active state") if bool(profile.inherit_to_lower_scopes) != desired.inherit_to_lower_scopes: changes.append("scope inheritance") return changes def _smtp_server_changes( profile: MailServerProfile, server: MailServerEndpoint | None, desired: _DesiredSmtpProfile, ) -> list[str]: changes: list[str] = [] current_payload = dict( server.config if server is not None else profile.smtp_config or {} ) for key in ("username", "password", "enabled"): current_payload.pop(key, None) try: current_payload = SmtpServerConfig.model_validate(current_payload).model_dump( mode="json" ) except Exception: changes.append("SMTP server metadata") else: if current_payload != desired.smtp_payload: changes.append("SMTP server metadata") if server is None: changes.append("SMTP server hierarchy") return changes def _credential_binding_changes( session: Session, server: MailServerEndpoint | None, desired: _DesiredSmtpProfile, ) -> list[str]: if not desired.credential_envelope_id or server is None: return [] binding = _credential_binding( session, server.id, desired.credential_envelope_id, ) if binding is None: return ["credential binding"] if desired.credential_is_default and not binding.is_default: return ["default credential binding"] return [] def _credential_diagnostics( session: Session, desired: _DesiredSmtpProfile, *, server: MailServerEndpoint | None, ) -> list[ConfigurationDiagnostic]: credential_id = desired.credential_envelope_id if not credential_id: return [] credential = session.get(CredentialEnvelope, credential_id) if ( credential is None or credential.deleted_at is not None or not credential.is_active ): return [ ConfigurationDiagnostic( severity="blocker", code="mail_credential_envelope_missing", message="The selected SMTP credential envelope is missing or inactive.", module_id="mail", object_ref=desired.fragment_ref, resolution="Select an active credential envelope visible to this profile scope.", ) ] server_ref = mail_server_ref(server.id) if server is not None else None context = CredentialAccessContext( tenant_id=desired.tenant_id, target_scope_type=desired.scope_type, target_scope_id=desired.scope_id, module_id="mail", server_ref=server_ref, ) if not credential_visible_to_context(credential, context): reason = ( "A server-restricted credential cannot be bound before the package-owned server exists." if server is None and credential.allowed_server_refs else "The selected credential is outside the profile scope or is not allowed for Mail and this server." ) return [ ConfigurationDiagnostic( severity="blocker", code="mail_credential_envelope_not_available", message=reason, module_id="mail", object_ref=desired.fragment_ref, resolution="Choose a compatible credential envelope or update its governed scope and allowed references first.", ) ] server_tenant_id = desired.tenant_id if credential.tenant_id not in {None, server_tenant_id}: return [ ConfigurationDiagnostic( severity="blocker", code="mail_credential_tenant_mismatch", message="The credential envelope and SMTP profile belong to different tenants.", module_id="mail", object_ref=desired.fragment_ref, resolution="Choose a system credential or a credential from the selected tenant.", ) ] return [] def _credential_binding( session: Session, server_id: str, credential_id: str, ) -> MailServerCredentialBinding | None: return session.execute( select(MailServerCredentialBinding).where( MailServerCredentialBinding.server_id == server_id, MailServerCredentialBinding.credential_id == credential_id, ) ).scalar_one_or_none() def _receipt_or_input_value( *, field: str, receipt_value: object, payload: Mapping[str, Any], supplied_data: Mapping[str, Any], input_key: str, fragment: ConfigurationPackageFragment, diagnostics: list[ConfigurationDiagnostic], ) -> object: explicit = payload.get(field) if receipt_value is not None: if explicit is not None and str(explicit) != str(receipt_value): diagnostics.append( ConfigurationDiagnostic( severity="blocker", code="infrastructure_endpoint_mismatch", message=f"SMTP {field} conflicts with the deployment capability receipt.", module_id="mail", object_ref=fragment.fragment_id or SMTP_PROFILE_FRAGMENT, resolution="Use the receipt endpoint or regenerate the deployment receipt before importing.", ) ) return receipt_value if explicit is not None: return explicit return supplied_data.get(input_key) def _inferred_security(*, source: str, scheme: str | None, port: object) -> str | None: if source == "installer-managed-test": return "plain" if scheme in {"smtps", "smtp+tls"} or str(port) == "465": return "tls" return None def _smtp_required_data( values: Mapping[str, object], key_map: Mapping[str, str], ) -> list[ConfigurationRequiredData]: requirements: list[ConfigurationRequiredData] = [] definitions = ( ("host", "SMTP host", "string", "Non-secret SMTP server hostname."), ("port", "SMTP port", "integer", "SMTP server port from 1 to 65535."), ( "security", "SMTP transport security", "enum:plain,starttls,tls", "Choose plain, STARTTLS, or implicit TLS.", ), ) for field, label, data_type, description in definitions: value = values.get(field) if value is None or (isinstance(value, str) and not value.strip()): requirements.append( ConfigurationRequiredData( key=key_map[field], label=label, data_type=data_type, description=description, ) ) return requirements def _unknown_payload_diagnostic( fragment: ConfigurationPackageFragment, unknown: list[str], ) -> ConfigurationDiagnostic: if _contains_secret_field(unknown): return ConfigurationDiagnostic( severity="blocker", code="mail_configuration_secret_forbidden", message="SMTP configuration packages accept credential-envelope references, never inline credentials.", module_id="mail", object_ref=fragment.fragment_id or SMTP_PROFILE_FRAGMENT, resolution="Remove inline credentials and select an existing credential envelope by id.", ) return _invalid( fragment, f"SMTP profile payload contains unsupported fields: {', '.join(unknown)}.", ) def _unknown_smtp_diagnostic( fragment: ConfigurationPackageFragment, unknown: list[str], ) -> ConfigurationDiagnostic: if _contains_secret_field(unknown): return ConfigurationDiagnostic( severity="blocker", code="mail_configuration_secret_forbidden", message=( "SMTP configuration packages accept non-secret server metadata " "and credential-envelope references only." ), module_id="mail", object_ref=fragment.fragment_id or SMTP_PROFILE_FRAGMENT, resolution="Remove inline credentials and select an existing credential envelope by id.", ) return _invalid( fragment, f"SMTP payload contains unsupported fields: {', '.join(unknown)}.", ) def _contains_secret_field(fields: list[str]) -> bool: return any( marker in field.casefold() for field in fields for marker in ("password", "secret", "token", "username") ) def _mapping( value: object, *, field_name: str, fragment: ConfigurationPackageFragment, diagnostics: list[ConfigurationDiagnostic], ) -> Mapping[str, Any]: if value is None: return {} if isinstance(value, Mapping): return value diagnostics.append(_invalid(fragment, f"{field_name} must be an object.")) return {} def _unknown_fields( value: Mapping[str, Any], allowed: frozenset[str], field_name: str, fragment: ConfigurationPackageFragment, diagnostics: list[ConfigurationDiagnostic], ) -> None: unknown = sorted(set(value) - allowed) if unknown: diagnostics.append( _invalid( fragment, f"{field_name} contains unsupported fields: {', '.join(unknown)}.", ) ) def _input_prefix(fragment: ConfigurationPackageFragment, installation_id: str) -> str: return f"mail.smtp.{_safe_key(fragment.fragment_id or installation_id)}" def _safe_key(value: str) -> str: return re.sub(r"[^A-Za-z0-9_.-]+", "-", value).strip("-.") or "deployment" def _text(value: object) -> str | None: if value is None: return None text = str(value).strip() return text or None def _bool(value: object, *, default: bool) -> bool: if value is None: return default if isinstance(value, bool): return value return str(value).strip().casefold() in {"1", "true", "yes", "on"} def _has_blockers(diagnostics: list[ConfigurationDiagnostic]) -> bool: return any(item.severity == "blocker" for item in diagnostics) def _invalid( fragment: ConfigurationPackageFragment, message: str, ) -> ConfigurationDiagnostic: return ConfigurationDiagnostic( severity="blocker", code="mail_configuration_payload_invalid", message=message, module_id="mail", object_ref=fragment.fragment_id or fragment.fragment_type, resolution="Review the Mail configuration-package fragment and rerun preflight.", ) def _unsupported(fragment: ConfigurationPackageFragment) -> ConfigurationDiagnostic: return ConfigurationDiagnostic( severity="blocker", code="fragment_type_unsupported", message=f"Mail configuration does not support fragment type {fragment.fragment_type!r}.", module_id="mail", object_ref=fragment.fragment_id or fragment.fragment_type, ) def _blocked_plan( fragment: ConfigurationPackageFragment, summary: str, ) -> ConfigurationPlanItem: return ConfigurationPlanItem( action="blocked", module_id="mail", fragment_type=fragment.fragment_type, fragment_id=fragment.fragment_id, summary=summary, ) __all__ = ["MAIL_CONFIGURATION_CAPABILITY", "SqlMailConfigurationProvider"]