From f3faa4dff92cbdb721a59a85bcf94c86f3d914ad Mon Sep 17 00:00:00 2001 From: Albrecht Degering Date: Tue, 21 Jul 2026 12:52:01 +0200 Subject: [PATCH] Refactor mail profile lock validation --- src/govoplan_mail/backend/mail_profiles.py | 91 +++++++++++++++++----- 1 file changed, 70 insertions(+), 21 deletions(-) diff --git a/src/govoplan_mail/backend/mail_profiles.py b/src/govoplan_mail/backend/mail_profiles.py index 43930d7..61b6be9 100644 --- a/src/govoplan_mail/backend/mail_profiles.py +++ b/src/govoplan_mail/backend/mail_profiles.py @@ -1399,32 +1399,81 @@ def _validate_policy_against_parent(parent: EffectiveMailProfilePolicy, normaliz raise MailProfileError(_policy_parent_lock_message(violations[0])) +def _locked_boolean_policy_fields( + parent_limits: dict[str, bool], + normalized: dict[str, Any], +) -> list[str]: + return [ + key + for key in ( + "allow_user_profiles", + "allow_group_profiles", + "allow_campaign_profiles", + ) + if isinstance(normalized.get(key), bool) + and not _policy_limit_enabled(parent_limits, key) + ] + + +def _locked_pattern_policy_fields( + parent_limits: dict[str, bool], + normalized: dict[str, Any], +) -> list[str]: + violations: list[str] = [] + for kind in ("whitelist", "blacklist"): + rules = normalized.get(kind) or {} + if not isinstance(rules, dict): + continue + for key in PROFILE_PATTERN_KEYS: + 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) + return violations + + +def _locked_credential_policy_fields( + parent_limits: dict[str, bool], + normalized: dict[str, Any], +) -> list[str]: + violations: list[str] = [] + for protocol in ("smtp", "imap"): + local = normalized.get(f"{protocol}_credentials") or {} + local_inherit = local.get("inherit") + limit_key = f"{protocol}_credentials.inherit" + if isinstance(local_inherit, bool) and not _policy_limit_enabled( + parent_limits, + limit_key, + ): + violations.append(limit_key) + return violations + + +def _locked_lower_limit_policy_fields( + parent_limits: dict[str, bool], + normalized: dict[str, Any], +) -> list[str]: + local_lower_limits = normalized.get("allow_lower_level_limits") or {} + if not isinstance(local_lower_limits, dict): + return [] + return [ + f"allow_lower_level_limits.{key}" + for key, allowed in local_lower_limits.items() + if allowed is True and not _policy_limit_enabled(parent_limits, key) + ] + + 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 _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 _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: - 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") - 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 _policy_limit_enabled(parent_limits, key): - violations.append(f"allow_lower_level_limits.{key}") + violations.extend(_locked_boolean_policy_fields(parent_limits, normalized)) + violations.extend(_locked_pattern_policy_fields(parent_limits, normalized)) + violations.extend(_locked_credential_policy_fields(parent_limits, normalized)) + violations.extend(_locked_lower_limit_policy_fields(parent_limits, normalized)) return violations