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}