diff --git a/src/govoplan_mail/backend/mail_profiles.py b/src/govoplan_mail/backend/mail_profiles.py index 1f275d3..af09797 100644 --- a/src/govoplan_mail/backend/mail_profiles.py +++ b/src/govoplan_mail/backend/mail_profiles.py @@ -285,11 +285,15 @@ def _legacy_tenant_settings(session: Session, tenant_id: str) -> dict[str, Any] return _json_object(row[0]) if row else None -def _legacy_subject_policy(session: Session, *, table_name: str, tenant_id: str, scope_id: str) -> dict[str, Any] | None: - if table_name not in {"access_users", "access_groups"}: +def _legacy_subject_policy(session: Session, *, subject_type: str, tenant_id: str, scope_id: str) -> dict[str, Any] | None: + if subject_type == "user": + statement = text("select mail_profile_policy from access_users where id = :scope_id and tenant_id = :tenant_id") + elif subject_type == "group": + statement = text("select mail_profile_policy from access_groups where id = :scope_id and tenant_id = :tenant_id") + else: raise MailProfileError("Unsupported mail policy subject") row = session.execute( - text(f"select mail_profile_policy from {table_name} where id = :scope_id and tenant_id = :tenant_id"), + statement, {"scope_id": scope_id, "tenant_id": tenant_id}, ).first() return _json_object(row[0]) if row else None @@ -304,7 +308,7 @@ def _user_exists(session: Session, *, tenant_id: str, user_id: str) -> bool: if directory is not None: user = directory.get_user(user_id) return bool(user and user.tenant_id == tenant_id and user.status == "active") - return _legacy_subject_policy(session, table_name="access_users", tenant_id=tenant_id, scope_id=user_id) is not None + return _legacy_subject_policy(session, subject_type="user", tenant_id=tenant_id, scope_id=user_id) is not None def _group_exists(session: Session, *, tenant_id: str, group_id: str) -> bool: @@ -312,7 +316,7 @@ def _group_exists(session: Session, *, tenant_id: str, group_id: str) -> bool: if directory is not None: group = directory.get_group(group_id) return bool(group and group.tenant_id == tenant_id and group.status == "active") - return _legacy_subject_policy(session, table_name="access_groups", tenant_id=tenant_id, scope_id=group_id) is not None + return _legacy_subject_policy(session, subject_type="group", tenant_id=tenant_id, scope_id=group_id) is not None def _policy_scope_key(*, tenant_id: str, scope_type: str, scope_id: str | None) -> tuple[str | None, str, str | None]: @@ -356,10 +360,10 @@ def _legacy_mail_profile_policy( if not scope_id: return None if clean_scope == "user": - legacy = _legacy_subject_policy(session, table_name="access_users", tenant_id=tenant_id, scope_id=scope_id) + legacy = _legacy_subject_policy(session, subject_type="user", tenant_id=tenant_id, scope_id=scope_id) return _policy_from_attr(legacy) if legacy is not None else None if clean_scope == "group": - legacy = _legacy_subject_policy(session, table_name="access_groups", tenant_id=tenant_id, scope_id=scope_id) + legacy = _legacy_subject_policy(session, subject_type="group", tenant_id=tenant_id, scope_id=scope_id) return _policy_from_attr(legacy) if legacy is not None else None try: context = _campaign_policy_context(session, tenant_id=tenant_id, campaign_id=scope_id) diff --git a/webui/src/features/mail/mailPolicyValidation.ts b/webui/src/features/mail/mailPolicyValidation.ts index 2ee6c72..185e0b3 100644 --- a/webui/src/features/mail/mailPolicyValidation.ts +++ b/webui/src/features/mail/mailPolicyValidation.ts @@ -88,10 +88,39 @@ export function wildcardPatternMatches(pattern: string, rawValue: string): boole const normalizedPattern = pattern.trim(); const value = rawValue.trim(); if (!normalizedPattern || !value) return false; - if (normalizedPattern === "*") return true; - const escaped = normalizedPattern.replace(/[.+^${}()|[\]\\]/g, "\\$&"); - const regex = `^${escaped.replace(/\*/g, ".*").replace(/\?/g, ".")}$`; - return new RegExp(regex, "i").test(value); + return wildcardMatch(normalizedPattern.toLowerCase(), value.toLowerCase()); +} + +function wildcardMatch(pattern: string, value: string): boolean { + let patternIndex = 0; + let valueIndex = 0; + let starIndex = -1; + let valueRetryIndex = 0; + + while (valueIndex < value.length) { + const token = pattern[patternIndex]; + if (token === "?" || token === value[valueIndex]) { + patternIndex += 1; + valueIndex += 1; + continue; + } + if (token === "*") { + starIndex = patternIndex; + valueRetryIndex = valueIndex; + patternIndex += 1; + continue; + } + if (starIndex !== -1) { + patternIndex = starIndex + 1; + valueRetryIndex += 1; + valueIndex = valueRetryIndex; + continue; + } + return false; + } + + while (pattern[patternIndex] === "*") patternIndex += 1; + return patternIndex === pattern.length; } function patternsFor( @@ -117,4 +146,4 @@ function normalizeDomain(value: string | null | undefined): string { if (!trimmed) return ""; const emailDomain = trimmed.includes("@") ? trimmed.split("@").pop() ?? "" : trimmed; return emailDomain.replace(/^@+/, ""); -} \ No newline at end of file +}