intermittent commit

This commit is contained in:
2026-07-14 13:22:11 +02:00
parent 4865de5007
commit b0337b2d26
28 changed files with 2264 additions and 490 deletions

View File

@@ -496,15 +496,7 @@ def _merge_policy(policy: EffectiveMailProfilePolicy, data: dict[str, Any], *, s
normalized = normalize_mail_profile_policy(data)
policy.source_policies.append(_mail_policy_source_step(source, source_id, label or source.capitalize(), normalized, baseline=baseline))
lower_limits = dict(policy.allow_lower_level_limits)
allowed_ids = _meaningful_allow_patterns(normalized.get("allowed_profile_ids") or [])
if allowed_ids and lower_limits.get("allowed_profile_ids", True):
policy.allowed_profile_id_sets.append(set(allowed_ids))
if normalized.get("allow_user_profiles") is False and lower_limits.get("allow_user_profiles", True):
policy.allow_user_profiles = False
if normalized.get("allow_group_profiles") is False and lower_limits.get("allow_group_profiles", True):
policy.allow_group_profiles = False
if normalized.get("allow_campaign_profiles") is False and lower_limits.get("allow_campaign_profiles", True):
policy.allow_campaign_profiles = False
_merge_profile_definition_policy(policy, normalized, lower_limits)
_merge_credential_policy(
policy.smtp_credentials,
normalized.get("smtp_credentials") or {},
@@ -519,19 +511,43 @@ def _merge_policy(policy: EffectiveMailProfilePolicy, data: dict[str, Any], *, s
lower_limits=lower_limits,
inherit_limit_key="imap_credentials.inherit",
)
_merge_pattern_policy(policy, normalized, lower_limits)
_merge_lower_level_limits(policy, normalized, lower_limits)
def _policy_limit_enabled(lower_limits: dict[str, bool], key: str) -> bool:
return lower_limits.get(key, True)
def _merge_profile_definition_policy(policy: EffectiveMailProfilePolicy, normalized: dict[str, Any], lower_limits: dict[str, bool]) -> None:
allowed_ids = _meaningful_allow_patterns(normalized.get("allowed_profile_ids") or [])
if allowed_ids and _policy_limit_enabled(lower_limits, "allowed_profile_ids"):
policy.allowed_profile_id_sets.append(set(allowed_ids))
if normalized.get("allow_user_profiles") is False and _policy_limit_enabled(lower_limits, "allow_user_profiles"):
policy.allow_user_profiles = False
if normalized.get("allow_group_profiles") is False and _policy_limit_enabled(lower_limits, "allow_group_profiles"):
policy.allow_group_profiles = False
if normalized.get("allow_campaign_profiles") is False and _policy_limit_enabled(lower_limits, "allow_campaign_profiles"):
policy.allow_campaign_profiles = False
def _merge_pattern_policy(policy: EffectiveMailProfilePolicy, normalized: dict[str, Any], lower_limits: dict[str, bool]) -> None:
whitelist = normalized.get("whitelist") or {}
blacklist = normalized.get("blacklist") or {}
for key in PROFILE_PATTERN_KEYS:
allow_patterns = _meaningful_allow_patterns(whitelist.get(key, []))
if allow_patterns and lower_limits.get(f"whitelist.{key}", True):
if allow_patterns and _policy_limit_enabled(lower_limits, f"whitelist.{key}"):
policy.whitelist_groups.setdefault(key, []).append(allow_patterns)
deny_patterns = _clean_string_list(blacklist.get(key, []))
if deny_patterns and lower_limits.get(f"blacklist.{key}", True):
if deny_patterns and _policy_limit_enabled(lower_limits, f"blacklist.{key}"):
policy.blacklist_patterns.setdefault(key, []).extend(deny_patterns)
def _merge_lower_level_limits(policy: EffectiveMailProfilePolicy, normalized: dict[str, Any], lower_limits: dict[str, bool]) -> None:
local_lower_limits = normalized.get("allow_lower_level_limits") or {}
if local_lower_limits:
policy.allow_lower_level_limits = {
key: lower_limits.get(key, True) and local_lower_limits.get(key, lower_limits.get(key, True))
key: _policy_limit_enabled(lower_limits, key) and local_lower_limits.get(key, _policy_limit_enabled(lower_limits, key))
for key in MAIL_POLICY_LIMIT_KEYS
}
@@ -1187,49 +1203,81 @@ def update_mail_server_profile(
if is_active is not None:
profile.is_active = is_active
next_smtp = smtp or SmtpConfig.model_validate({**(profile.smtp_config or {}), "username": _profile_username(profile, "smtp"), "password": decrypt_secret(profile.smtp_password_encrypted)})
if clear_imap:
next_imap = None
elif imap is not None:
next_imap = imap
elif profile.imap_config:
next_imap = ImapConfig.model_validate({**(profile.imap_config or {}), "username": _profile_username(profile, "imap"), "password": decrypt_secret(profile.imap_password_encrypted)})
else:
next_imap = None
next_smtp, next_imap = _next_profile_transport_state(profile, smtp=smtp, imap=imap, clear_imap=clear_imap)
_assert_profile_transport_allowed(session, tenant_id=tenant_id, profile=profile, smtp=next_smtp, imap=next_imap)
_apply_profile_transport_update(profile, smtp=smtp, imap=imap, clear_imap=clear_imap)
profile.updated_by_user_id = user_id
session.add(profile)
session.flush()
return profile
def _next_profile_transport_state(
profile: MailServerProfile,
*,
smtp: SmtpConfig | None,
imap: ImapConfig | None,
clear_imap: bool,
) -> tuple[SmtpConfig, ImapConfig | None]:
next_smtp = smtp or smtp_config_from_profile(profile)
if clear_imap:
return next_smtp, None
if imap is not None:
return next_smtp, imap
return next_smtp, imap_config_from_profile(profile)
def _assert_profile_transport_allowed(
session: Session,
*,
tenant_id: str,
profile: MailServerProfile,
smtp: SmtpConfig,
imap: ImapConfig | None,
) -> None:
scope_type = _profile_scope_type(profile)
scope_id = _profile_scope_id(profile)
if scope_type == "campaign":
assert_mail_policy_allows_transport(session, tenant_id=tenant_id, smtp=next_smtp, imap=next_imap, campaign_id=scope_id)
assert_mail_policy_allows_transport(session, tenant_id=tenant_id, smtp=smtp, imap=imap, campaign_id=scope_id)
elif scope_type == "user":
assert_mail_policy_allows_transport(session, tenant_id=tenant_id, smtp=next_smtp, imap=next_imap, owner_user_id=scope_id)
assert_mail_policy_allows_transport(session, tenant_id=tenant_id, smtp=smtp, imap=imap, owner_user_id=scope_id)
elif scope_type == "group":
assert_mail_policy_allows_transport(session, tenant_id=tenant_id, smtp=next_smtp, imap=next_imap, owner_group_id=scope_id)
assert_mail_policy_allows_transport(session, tenant_id=tenant_id, smtp=smtp, imap=imap, owner_group_id=scope_id)
elif scope_type == "tenant":
assert_mail_policy_allows_transport(session, tenant_id=tenant_id, smtp=next_smtp, imap=next_imap)
assert_mail_policy_allows_transport(session, tenant_id=tenant_id, smtp=smtp, imap=imap)
def _apply_profile_transport_update(
profile: MailServerProfile,
*,
smtp: SmtpConfig | None,
imap: ImapConfig | None,
clear_imap: bool,
) -> None:
if smtp is not None:
smtp_payload, smtp_username, smtp_password, username_supplied, password_supplied = _transport_payload(smtp)
profile.smtp_config = smtp_payload
if username_supplied:
profile.smtp_username = smtp_username
if password_supplied:
profile.smtp_password_encrypted = encrypt_secret(smtp_password)
_apply_profile_transport_payload(profile, "smtp", smtp)
if clear_imap:
profile.imap_config = None
profile.imap_username = None
profile.imap_password_encrypted = None
elif imap is not None:
imap_payload, imap_username, imap_password, username_supplied, password_supplied = _transport_payload(imap)
profile.imap_config = imap_payload
_apply_profile_transport_payload(profile, "imap", imap)
def _apply_profile_transport_payload(profile: MailServerProfile, protocol: str, config: SmtpConfig | ImapConfig) -> None:
payload, username, password, username_supplied, password_supplied = _transport_payload(config)
if protocol == "smtp":
profile.smtp_config = payload
if username_supplied:
profile.imap_username = imap_username
profile.smtp_username = username
if password_supplied:
profile.imap_password_encrypted = encrypt_secret(imap_password)
profile.updated_by_user_id = user_id
session.add(profile)
session.flush()
return profile
profile.smtp_password_encrypted = encrypt_secret(password)
return
profile.imap_config = payload
if username_supplied:
profile.imap_username = username
if password_supplied:
profile.imap_password_encrypted = encrypt_secret(password)
def _profile_username(profile: MailServerProfile, protocol: str) -> str | None:
@@ -1346,29 +1394,50 @@ def get_mail_profile_policy(
def _validate_policy_against_parent(parent: EffectiveMailProfilePolicy, normalized: dict[str, Any]) -> None:
parent_limits = parent.allow_lower_level_limits
violations = _policy_parent_lock_violations(parent.allow_lower_level_limits, normalized)
if violations:
raise MailProfileError(_policy_parent_lock_message(violations[0]))
def _policy_parent_lock_violations(parent_limits: dict[str, bool], normalized: dict[str, Any]) -> list[str]:
violations: list[str] = []
allowed_ids = _meaningful_allow_patterns(normalized.get("allowed_profile_ids") or [])
if allowed_ids and not parent_limits.get("allowed_profile_ids", True):
raise MailProfileError("Mail profile allow-list is locked by an ancestor policy")
if allowed_ids and not _policy_limit_enabled(parent_limits, "allowed_profile_ids"):
violations.append("allowed_profile_ids")
for key in ("allow_user_profiles", "allow_group_profiles", "allow_campaign_profiles"):
if isinstance(normalized.get(key), bool) and not parent_limits.get(key, True):
raise MailProfileError(f"{key} is locked by an ancestor policy")
if isinstance(normalized.get(key), bool) and not _policy_limit_enabled(parent_limits, key):
violations.append(key)
for kind in ("whitelist", "blacklist"):
rules = normalized.get(kind) or {}
if isinstance(rules, dict):
for key in PROFILE_PATTERN_KEYS:
if _clean_string_list(rules.get(key, [])) and not parent_limits.get(f"{kind}.{key}", True):
raise MailProfileError(f"{kind}.{key} is locked by an ancestor policy")
limit_key = f"{kind}.{key}"
if _clean_string_list(rules.get(key, [])) and not _policy_limit_enabled(parent_limits, limit_key):
violations.append(limit_key)
for protocol in ("smtp", "imap"):
local = normalized.get(f"{protocol}_credentials") or {}
local_inherit = local.get("inherit")
if isinstance(local_inherit, bool) and not parent_limits.get(f"{protocol}_credentials.inherit", True):
raise MailProfileError(f"{protocol.upper()} credential inheritance is locked by an ancestor policy")
limit_key = f"{protocol}_credentials.inherit"
if isinstance(local_inherit, bool) and not _policy_limit_enabled(parent_limits, limit_key):
violations.append(limit_key)
local_lower_limits = normalized.get("allow_lower_level_limits") or {}
if isinstance(local_lower_limits, dict):
for key, allowed in local_lower_limits.items():
if allowed is True and not parent_limits.get(key, True):
raise MailProfileError(f"{key} lower-level limiting cannot be re-enabled below a locked ancestor policy")
if allowed is True and not _policy_limit_enabled(parent_limits, key):
violations.append(f"allow_lower_level_limits.{key}")
return violations
def _policy_parent_lock_message(field: str) -> str:
if field == "allowed_profile_ids":
return "Mail profile allow-list is locked by an ancestor policy"
if field.startswith("allow_lower_level_limits."):
key = field.removeprefix("allow_lower_level_limits.")
return f"{key} lower-level limiting cannot be re-enabled below a locked ancestor policy"
if field.endswith("_credentials.inherit"):
protocol = field.split("_", 1)[0].upper()
return f"{protocol} credential inheritance is locked by an ancestor policy"
return f"{field} is locked by an ancestor policy"
def set_mail_profile_policy(