Skip to main content

saml_rs/config/
policies.rs

1use crate::binding::MAX_DEFLATE_RAW_DECODE_BYTES;
2use crate::constants::MessageSignatureOrder;
3use crate::entity::SignatureConfig;
4use crate::error::SamlError;
5use crate::template::LoginResponseTemplate;
6use crate::xml::XmlLimits;
7
8use super::algorithms::{
9    DataEncryptionAlgorithm, KeyEncryptionAlgorithm, SignatureAlgorithm, TransformAlgorithm,
10};
11
12/// Whether an accepting service provider requires a signature on the Assertion
13/// itself.
14///
15/// [`Self::RequireSigned`] is library hardening and stays off unless the caller
16/// sets it. A response signed only on the `<Response>` remains acceptable
17/// until then. The accept combination is classified in
18/// `docs/conformance/web-browser-sso-acceptance.md`.
19#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
20pub enum AssertionSignaturePolicy {
21    /// Reject an assertion that is not directly signed.
22    ///
23    /// Library hardening for the accepting service provider.
24    RequireSigned,
25    /// Do not require a signature directly on the Assertion.
26    ///
27    /// HTTP POST still requires the assertion to be protected by an Assertion
28    /// signature or a Response signature.
29    #[default]
30    AllowUnsignedForCompatibility,
31}
32
33/// Whether an accepting service provider requires a signature on the
34/// `<Response>`.
35///
36/// [`Self::RequireForEncryptedCbc`] follows the recommendation for a
37/// CBC-encrypted assertion. [`Self::AllowUnsignedEncryptedCbc`] relaxes that
38/// recommendation alone and is not the legacy permissive preset.
39/// [`Self::RequireSigned`] requires a signature on every response and is
40/// library hardening. The accept combination is classified in
41/// `docs/conformance/web-browser-sso-acceptance.md`.
42#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
43pub enum ResponseSignaturePolicy {
44    /// Accept a CBC `EncryptedAssertion` without a Response signature.
45    #[default]
46    AllowUnsignedEncryptedCbc,
47    /// Require a signed Response when an `<EncryptedAssertion>` uses CBC.
48    ///
49    /// Plaintext responses are unchanged.
50    RequireForEncryptedCbc,
51    /// Require a Response signature on every response.
52    ///
53    /// Library hardening. Also rejects a response signed only on the Assertion.
54    RequireSigned,
55}
56
57/// Inbound embedded XML-signature algorithm profile for accepted SSO and
58/// Single Logout messages.
59///
60/// [`Self::StrictRsaSha2`] is library hardening. Selecting it does not require
61/// a signature directly on the Assertion. The accept combination is classified
62/// in `docs/conformance/web-browser-sso-acceptance.md`.
63#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
64pub enum XmlSignatureProfile {
65    /// Require one local `#id` reference, RSA-SHA256/384/512, a SHA-256/384/512
66    /// digest, and exclusive canonicalization. Each transform element, when
67    /// present, must belong to the XML-DSig namespace and use enveloped-signature
68    /// or exclusive canonicalization.
69    StrictRsaSha2,
70    /// Accept every algorithm and same-document reference shape supported by
71    /// the selected cryptographic provider.
72    #[default]
73    AllowProviderSupportedForCompatibility,
74}
75
76/// Whether an SP signs outgoing AuthnRequests.
77#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
78pub enum AuthnRequestSigningPolicy {
79    /// Sign outgoing AuthnRequests.
80    Sign,
81    /// Send unsigned AuthnRequests, preserving the samlify port.
82    #[default]
83    DoNotSignForCompatibility,
84}
85
86/// Whether an identity provider requires a signed inbound `<AuthnRequest>`.
87///
88/// [`Self::RequireSigned`] is optional and stays off.
89/// [`Self::AllowUnsignedVerifyIfPresent`] verifies a signature that is present
90/// and still accepts an unsigned request.
91/// [`Self::AllowUnsignedForCompatibility`] does not verify a present signature.
92/// The accept combination is classified in
93/// `docs/conformance/web-browser-sso-acceptance.md`.
94#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
95pub enum AuthnRequestValidationPolicy {
96    /// Reject unsigned AuthnRequests.
97    RequireSigned,
98    /// Accept an unsigned AuthnRequest and verify a signature that is present.
99    AllowUnsignedVerifyIfPresent,
100    /// Accept unsigned AuthnRequests without verifying a signature that is present.
101    #[default]
102    AllowUnsignedForCompatibility,
103}
104
105/// Whether logout messages require signatures.
106#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
107pub enum LogoutSignaturePolicy {
108    /// Reject unsigned logout messages.
109    #[default]
110    RequireSigned,
111    /// Accept unsigned logout messages, preserving the samlify port.
112    AllowUnsignedForCompatibility,
113}
114
115/// Whether an accepting service provider evaluates `<AudienceRestriction>`.
116///
117/// [`Self::EvaluatePresentRestrictions`] checks restrictions that are present.
118/// [`Self::Validate`] also rejects a bearer assertion that omits one.
119/// [`Self::SkipForCompatibility`] skips the check. The accept combination is
120/// classified in `docs/conformance/web-browser-sso-acceptance.md`.
121#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
122pub enum AudienceValidationPolicy {
123    /// Evaluate audience restrictions and reject a bearer assertion that omits one.
124    ///
125    /// Rejecting a missing restriction is library hardening. `strict()` keeps
126    /// this variant.
127    #[default]
128    Validate,
129    /// Evaluate `<AudienceRestriction>` elements that are present.
130    ///
131    /// A bearer assertion that omits the element is not rejected.
132    EvaluatePresentRestrictions,
133    /// Skip audience evaluation, preserving the samlify port.
134    SkipForCompatibility,
135}
136
137/// Whether SP AuthnRequests allow IdPs to create a new identifier.
138#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
139pub enum NameIdCreationPolicy {
140    /// Set `AllowCreate="true"` in AuthnRequests.
141    AllowCreate,
142    /// Set `AllowCreate="false"` in AuthnRequests.
143    #[default]
144    DoNotAllowCreate,
145}
146
147/// SP-side validation and outbound signing policy.
148///
149/// [`Self::recommended`] is the preset for claimed Web Browser SSO and Single
150/// Logout features. [`Self::compatibility`] is the legacy permissive preset.
151/// `strict()` is deprecated and keeps its current bundle. Field classification
152/// is in `docs/conformance/`.
153#[derive(Debug, Clone, PartialEq, Eq)]
154pub struct SpValidationPolicy {
155    /// Assertion signature requirement.
156    pub assertions: AssertionSignaturePolicy,
157    /// Top-level SAML Response signature requirement.
158    pub responses: ResponseSignaturePolicy,
159    /// Embedded XML-signature algorithm and reference requirements.
160    pub xml_signatures: XmlSignatureProfile,
161    /// Outbound AuthnRequest signing behavior.
162    pub authn_requests: AuthnRequestSigningPolicy,
163    /// Audience validation behavior.
164    pub audience: AudienceValidationPolicy,
165    /// NameID creation behavior for AuthnRequests.
166    pub name_id_creation: NameIdCreationPolicy,
167    /// Logout signature validation behavior.
168    pub logout: LogoutPolicy,
169}
170
171impl SpValidationPolicy {
172    /// Validation preset for claimed Web Browser SSO and Single Logout features.
173    ///
174    /// Recommendations start enabled. Optional capabilities and library
175    /// hardening stay off until a caller replaces one field.
176    ///
177    /// # Examples
178    ///
179    /// ```
180    /// use saml_rs::{AssertionSignaturePolicy, SpValidationPolicy, XmlSignatureProfile};
181    ///
182    /// let with_assertion_signature = SpValidationPolicy {
183    ///     assertions: AssertionSignaturePolicy::RequireSigned,
184    ///     ..SpValidationPolicy::recommended()
185    /// };
186    /// assert_eq!(
187    ///     with_assertion_signature.xml_signatures,
188    ///     XmlSignatureProfile::AllowProviderSupportedForCompatibility
189    /// );
190    /// ```
191    pub fn recommended() -> Self {
192        Self {
193            assertions: AssertionSignaturePolicy::AllowUnsignedForCompatibility,
194            responses: ResponseSignaturePolicy::RequireForEncryptedCbc,
195            xml_signatures: XmlSignatureProfile::AllowProviderSupportedForCompatibility,
196            authn_requests: AuthnRequestSigningPolicy::DoNotSignForCompatibility,
197            audience: AudienceValidationPolicy::EvaluatePresentRestrictions,
198            name_id_creation: NameIdCreationPolicy::DoNotAllowCreate,
199            logout: LogoutPolicy::recommended(),
200        }
201    }
202
203    /// Deprecated bundle of SP validation choices.
204    ///
205    /// Removed in the following release. Start from [`Self::recommended`] and
206    /// add only the named hardenings you want. The returned fields are
207    /// unchanged from earlier releases.
208    #[deprecated(
209        note = "removed in the following release; start from recommended() and add only the named hardenings you want"
210    )]
211    #[expect(
212        deprecated,
213        reason = "the deprecated strict preset keeps its current logout bundle"
214    )]
215    pub fn strict() -> Self {
216        Self {
217            assertions: AssertionSignaturePolicy::RequireSigned,
218            responses: ResponseSignaturePolicy::RequireForEncryptedCbc,
219            xml_signatures: XmlSignatureProfile::AllowProviderSupportedForCompatibility,
220            authn_requests: AuthnRequestSigningPolicy::Sign,
221            audience: AudienceValidationPolicy::Validate,
222            name_id_creation: NameIdCreationPolicy::DoNotAllowCreate,
223            logout: LogoutPolicy::strict(),
224        }
225    }
226
227    /// Legacy permissive preset: samlify-port behavior for a caller leaving
228    /// the raw API.
229    pub fn compatibility() -> Self {
230        Self {
231            assertions: AssertionSignaturePolicy::AllowUnsignedForCompatibility,
232            responses: ResponseSignaturePolicy::AllowUnsignedEncryptedCbc,
233            xml_signatures: XmlSignatureProfile::AllowProviderSupportedForCompatibility,
234            authn_requests: AuthnRequestSigningPolicy::DoNotSignForCompatibility,
235            audience: AudienceValidationPolicy::SkipForCompatibility,
236            name_id_creation: NameIdCreationPolicy::DoNotAllowCreate,
237            logout: LogoutPolicy::compatibility(),
238        }
239    }
240}
241
242impl Default for SpValidationPolicy {
243    fn default() -> Self {
244        Self::compatibility()
245    }
246}
247
248/// IdP-side validation policy.
249///
250/// [`Self::recommended`] is the preset for claimed Web Browser SSO and Single
251/// Logout features. [`Self::compatibility`] is the legacy permissive preset.
252/// `strict()` is deprecated and keeps its current bundle. Field classification
253/// is in `docs/conformance/`.
254#[derive(Debug, Clone, PartialEq, Eq)]
255pub struct IdpValidationPolicy {
256    /// Inbound AuthnRequest signature requirement.
257    pub authn_requests: AuthnRequestValidationPolicy,
258    /// Logout signature validation behavior.
259    pub logout: LogoutPolicy,
260}
261
262impl IdpValidationPolicy {
263    /// Validation preset for claimed Web Browser SSO and Single Logout features.
264    ///
265    /// An inbound `AuthnRequest` may be unsigned. A signature that is present
266    /// is verified. Logout requests and responses require a signature.
267    pub fn recommended() -> Self {
268        Self {
269            authn_requests: AuthnRequestValidationPolicy::AllowUnsignedVerifyIfPresent,
270            logout: LogoutPolicy::recommended(),
271        }
272    }
273
274    /// Deprecated bundle of IdP validation choices.
275    ///
276    /// Removed in the following release. Start from [`Self::recommended`] and
277    /// add only the named hardenings you want. Inbound `AuthnRequest` and
278    /// logout messages must be signed. The returned fields are unchanged from
279    /// earlier releases.
280    #[deprecated(
281        note = "removed in the following release; start from recommended() and add only the named hardenings you want"
282    )]
283    #[expect(
284        deprecated,
285        reason = "the deprecated strict preset keeps its current logout bundle"
286    )]
287    pub fn strict() -> Self {
288        Self {
289            authn_requests: AuthnRequestValidationPolicy::RequireSigned,
290            logout: LogoutPolicy::strict(),
291        }
292    }
293
294    /// Legacy permissive preset.
295    ///
296    /// Samlify-port behavior for a caller leaving the raw API. A signature
297    /// present on an `AuthnRequest` is not verified.
298    pub fn compatibility() -> Self {
299        Self {
300            authn_requests: AuthnRequestValidationPolicy::AllowUnsignedForCompatibility,
301            logout: LogoutPolicy::compatibility(),
302        }
303    }
304}
305
306impl Default for IdpValidationPolicy {
307    fn default() -> Self {
308        Self::compatibility()
309    }
310}
311
312/// Logout request and response signature policy.
313///
314/// [`Self::recommended`] requires a signature on requests and responses.
315/// [`Self::compatibility`] is the legacy permissive preset. `strict()` is
316/// deprecated and unchanged.
317///
318/// `finish_slo` reads `responses`. `receive_slo` reads `requests`. With
319/// [`crate::LogoutSigning::FollowLocalPolicy`], `requests` also decides
320/// whether an outbound `LogoutRequest` is signed. See
321/// `docs/conformance/single-logout.md`.
322#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
323pub struct LogoutPolicy {
324    /// LogoutRequest signature behavior.
325    pub requests: LogoutSignaturePolicy,
326    /// LogoutResponse signature behavior.
327    pub responses: LogoutSignaturePolicy,
328}
329
330impl LogoutPolicy {
331    /// Require a signature on logout requests and responses.
332    pub fn recommended() -> Self {
333        Self {
334            requests: LogoutSignaturePolicy::RequireSigned,
335            responses: LogoutSignaturePolicy::RequireSigned,
336        }
337    }
338
339    /// Deprecated logout signature bundle.
340    ///
341    /// Removed in the following release. Start from [`Self::recommended`] and
342    /// add only the named hardenings you want. Logout requests and responses
343    /// must still be signed.
344    #[deprecated(
345        note = "removed in the following release; start from recommended() and add only the named hardenings you want"
346    )]
347    pub fn strict() -> Self {
348        Self {
349            requests: LogoutSignaturePolicy::RequireSigned,
350            responses: LogoutSignaturePolicy::RequireSigned,
351        }
352    }
353
354    /// Legacy permissive preset.
355    ///
356    /// Unsigned logout requests and responses are accepted. This is the
357    /// samlify-port hatch.
358    pub fn compatibility() -> Self {
359        Self {
360            requests: LogoutSignaturePolicy::AllowUnsignedForCompatibility,
361            responses: LogoutSignaturePolicy::AllowUnsignedForCompatibility,
362        }
363    }
364}
365
366/// Whether assertions are encrypted in generated responses.
367#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
368pub enum AssertionEncryptionPolicy {
369    /// Do not encrypt assertions.
370    #[default]
371    PlaintextAssertions,
372    /// Encrypt assertions.
373    EncryptAssertions,
374}
375
376/// XML encryption policy.
377///
378/// # Examples
379///
380/// Use typed configuration to request encrypted assertions in generated
381/// responses. This only configures policy; actual encryption uses the crate's
382/// XML-Enc backend and deployment credentials.
383///
384/// ```
385/// use saml_rs::{EntityId, IdpConfig, SsoEndpoint, XmlEncryptionPolicy, XmlPolicy};
386///
387/// let xml = XmlPolicy {
388///     encryption: XmlEncryptionPolicy::encrypt_assertions(),
389///     ..XmlPolicy::default()
390/// };
391/// let idp_builder = IdpConfig::builder(EntityId::try_new("https://idp.example.com/metadata")?)
392///     .sso_endpoint(SsoEndpoint::post("https://idp.example.com/sso")?)
393///     .xml(xml);
394/// # let _ = idp_builder;
395/// # Ok::<(), saml_rs::SamlError>(())
396/// ```
397#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
398pub struct XmlEncryptionPolicy {
399    /// Assertion encryption behavior.
400    pub assertions: AssertionEncryptionPolicy,
401    allow_insecure_software_rsa_key_transport_decryption: bool,
402}
403
404impl XmlEncryptionPolicy {
405    /// Enable assertion encryption.
406    pub fn encrypt_assertions() -> Self {
407        Self {
408            assertions: AssertionEncryptionPolicy::EncryptAssertions,
409            ..Self::default()
410        }
411    }
412
413    /// Explicitly allow RustCrypto software RSA key-transport decryption despite
414    /// `RUSTSEC-2023-0071` timing-risk concerns in that backend.
415    pub fn allow_insecure_software_rsa_key_transport_decryption() -> Self {
416        Self {
417            allow_insecure_software_rsa_key_transport_decryption: true,
418            ..Self::default()
419        }
420    }
421
422    /// Return a copy with the software RSA key-transport risk explicitly allowed.
423    pub fn with_insecure_software_rsa_key_transport_decryption_allowed(mut self) -> Self {
424        self.allow_insecure_software_rsa_key_transport_decryption = true;
425        self
426    }
427
428    pub(super) fn allows_insecure_software_rsa_key_transport_decryption(self) -> bool {
429        self.allow_insecure_software_rsa_key_transport_decryption
430    }
431}
432
433/// XML parser, redirect decompression, clock, and XML encryption policy.
434///
435/// # Examples
436///
437/// Software RSA key-transport decryption is disabled by default on the
438/// RustCrypto provider because that backend, reached through `bergshamra` /
439/// `kryptering`, is affected by `RUSTSEC-2023-0071`. Enable it only as an
440/// explicit RustCrypto exception for a deployment that accepts
441/// that risk. AWS-LC and FIPS ignore this opt-in.
442///
443/// ```
444/// use saml_rs::{AcsEndpoint, EntityId, SpConfig, XmlEncryptionPolicy, XmlPolicy};
445///
446/// let xml = XmlPolicy {
447///     encryption: XmlEncryptionPolicy::default()
448///         .with_insecure_software_rsa_key_transport_decryption_allowed(),
449///     ..XmlPolicy::default()
450/// };
451/// let sp_builder = SpConfig::builder(EntityId::try_new("https://sp.example.com/metadata")?)
452///     .acs_endpoint(AcsEndpoint::post("https://sp.example.com/acs")?)
453///     .xml(xml);
454/// # let _ = sp_builder;
455/// # Ok::<(), saml_rs::SamlError>(())
456/// ```
457#[derive(Debug, Clone, Copy, PartialEq, Eq)]
458pub struct XmlPolicy {
459    /// Clock drift tolerance `(not_before_ms, not_on_or_after_ms)`.
460    pub clock_drifts: (i64, i64),
461    /// Maximum decoded compressed and inflated raw-DEFLATE bytes accepted for
462    /// HTTP-Redirect input.
463    pub redirect_inflate_max_bytes: usize,
464    /// XML parser resource limits.
465    pub limits: XmlLimits,
466    /// XML encryption behavior.
467    pub encryption: XmlEncryptionPolicy,
468}
469
470impl Default for XmlPolicy {
471    fn default() -> Self {
472        Self {
473            clock_drifts: (0, 0),
474            redirect_inflate_max_bytes: MAX_DEFLATE_RAW_DECODE_BYTES,
475            limits: XmlLimits::default(),
476            encryption: XmlEncryptionPolicy::default(),
477        }
478    }
479}
480
481/// Algorithm choices used by outgoing SAML messages.
482#[derive(Debug, Clone, PartialEq, Eq)]
483pub struct AlgorithmPolicy {
484    /// Signature algorithm URI.
485    pub signature: SignatureAlgorithm,
486    /// Data encryption algorithm URI.
487    pub data_encryption: DataEncryptionAlgorithm,
488    /// Key encryption algorithm URI.
489    pub key_encryption: KeyEncryptionAlgorithm,
490    /// Sign/encrypt operation order for messages that do both.
491    pub message_signing_order: MessageSignatureOrder,
492    /// XML-DSig reference transforms.
493    pub signed_reference_transforms: Vec<TransformAlgorithm>,
494}
495
496impl Default for AlgorithmPolicy {
497    fn default() -> Self {
498        Self {
499            signature: SignatureAlgorithm::default(),
500            data_encryption: DataEncryptionAlgorithm::default(),
501            key_encryption: KeyEncryptionAlgorithm::default(),
502            message_signing_order: MessageSignatureOrder::SignThenEncrypt,
503            signed_reference_transforms: vec![
504                TransformAlgorithm::EnvelopedSignature,
505                TransformAlgorithm::ExclusiveCanonicalization,
506            ],
507        }
508    }
509}
510
511/// Template and XML tag-prefix customization.
512#[derive(Debug, Clone)]
513pub struct TemplatePolicy {
514    /// Default RelayState.
515    pub relay_state: String,
516    /// IdP protocol tag prefix for generated messages.
517    pub tag_prefix_protocol: String,
518    /// IdP assertion tag prefix for generated messages.
519    pub tag_prefix_assertion: String,
520    /// IdP tag prefix for generated `<EncryptedAssertion>` elements.
521    pub tag_prefix_encrypted_assertion: String,
522    /// IdP login response template and attributes.
523    pub login_response_template: Option<LoginResponseTemplate>,
524    /// SP login request template.
525    pub login_request_template: Option<String>,
526    /// Logout request template.
527    ///
528    /// Typed Session Authority generation requires a complete unqualified
529    /// `NotOnOrAfter="{NotOnOrAfter}"` root attribute. Typed SP and raw
530    /// samlify-port generation do not synthesize the attribute.
531    pub logout_request_template: Option<String>,
532    /// Logout response template.
533    ///
534    /// After prefix and placeholder substitution, the final outbound XML must
535    /// satisfy the enforced LogoutResponse structure, issuer, destination, and
536    /// request-correlation requirements. A root `<ds:Signature>` is rejected
537    /// before signing so the library owns signature construction. When
538    /// `InResponseTo` is `None`, an attribute whose complete value is the
539    /// `{InResponseTo}` placeholder is omitted.
540    pub logout_response_template: Option<String>,
541    /// Embedded-signature placement and prefix.
542    pub signature_config: Option<SignatureConfig>,
543}
544
545impl Default for TemplatePolicy {
546    fn default() -> Self {
547        Self {
548            relay_state: String::new(),
549            tag_prefix_protocol: "samlp".to_string(),
550            tag_prefix_assertion: "saml".to_string(),
551            tag_prefix_encrypted_assertion: "saml".to_string(),
552            login_response_template: None,
553            login_request_template: None,
554            logout_request_template: None,
555            logout_response_template: None,
556            signature_config: None,
557        }
558    }
559}
560pub(super) fn authn_request_signing_enabled(policy: AuthnRequestSigningPolicy) -> bool {
561    matches!(policy, AuthnRequestSigningPolicy::Sign)
562}
563
564pub(super) fn authn_request_signature_required(policy: AuthnRequestValidationPolicy) -> bool {
565    matches!(policy, AuthnRequestValidationPolicy::RequireSigned)
566}
567
568pub(super) fn verify_present_authn_request_signature(policy: AuthnRequestValidationPolicy) -> bool {
569    matches!(
570        policy,
571        AuthnRequestValidationPolicy::AllowUnsignedVerifyIfPresent
572    )
573}
574
575pub(super) fn assertion_signature_required(policy: AssertionSignaturePolicy) -> bool {
576    matches!(policy, AssertionSignaturePolicy::RequireSigned)
577}
578
579pub(super) fn response_signature_required(policy: ResponseSignaturePolicy) -> bool {
580    matches!(policy, ResponseSignaturePolicy::RequireSigned)
581}
582
583pub(super) fn encrypted_cbc_response_signature_required(policy: ResponseSignaturePolicy) -> bool {
584    matches!(policy, ResponseSignaturePolicy::RequireForEncryptedCbc)
585}
586
587pub(super) fn logout_signature_required(policy: LogoutSignaturePolicy) -> Result<bool, SamlError> {
588    match policy {
589        LogoutSignaturePolicy::RequireSigned => Ok(true),
590        LogoutSignaturePolicy::AllowUnsignedForCompatibility => Ok(false),
591    }
592}
593pub(super) fn name_id_creation_allowed(policy: NameIdCreationPolicy) -> bool {
594    matches!(policy, NameIdCreationPolicy::AllowCreate)
595}
596
597pub(super) fn audience_validation_enabled(policy: AudienceValidationPolicy) -> bool {
598    matches!(
599        policy,
600        AudienceValidationPolicy::Validate | AudienceValidationPolicy::EvaluatePresentRestrictions
601    )
602}
603
604pub(super) fn audience_restriction_required(policy: AudienceValidationPolicy) -> bool {
605    matches!(policy, AudienceValidationPolicy::Validate)
606}