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    /// The returned fields are unchanged from earlier releases.
206    #[deprecated(note = "start from recommended() and add only the named hardenings you want")]
207    #[expect(
208        deprecated,
209        reason = "the deprecated strict preset keeps its current logout bundle"
210    )]
211    pub fn strict() -> Self {
212        Self {
213            assertions: AssertionSignaturePolicy::RequireSigned,
214            responses: ResponseSignaturePolicy::RequireForEncryptedCbc,
215            xml_signatures: XmlSignatureProfile::AllowProviderSupportedForCompatibility,
216            authn_requests: AuthnRequestSigningPolicy::Sign,
217            audience: AudienceValidationPolicy::Validate,
218            name_id_creation: NameIdCreationPolicy::DoNotAllowCreate,
219            logout: LogoutPolicy::strict(),
220        }
221    }
222
223    /// Legacy permissive preset: samlify-port behavior for a caller leaving
224    /// the raw API.
225    pub fn compatibility() -> Self {
226        Self {
227            assertions: AssertionSignaturePolicy::AllowUnsignedForCompatibility,
228            responses: ResponseSignaturePolicy::AllowUnsignedEncryptedCbc,
229            xml_signatures: XmlSignatureProfile::AllowProviderSupportedForCompatibility,
230            authn_requests: AuthnRequestSigningPolicy::DoNotSignForCompatibility,
231            audience: AudienceValidationPolicy::SkipForCompatibility,
232            name_id_creation: NameIdCreationPolicy::DoNotAllowCreate,
233            logout: LogoutPolicy::compatibility(),
234        }
235    }
236}
237
238impl Default for SpValidationPolicy {
239    fn default() -> Self {
240        Self::compatibility()
241    }
242}
243
244/// IdP-side validation policy.
245///
246/// [`Self::recommended`] is the preset for claimed Web Browser SSO and Single
247/// Logout features. [`Self::compatibility`] is the legacy permissive preset.
248/// `strict()` is deprecated and keeps its current bundle. Field classification
249/// is in `docs/conformance/`.
250#[derive(Debug, Clone, PartialEq, Eq)]
251pub struct IdpValidationPolicy {
252    /// Inbound AuthnRequest signature requirement.
253    pub authn_requests: AuthnRequestValidationPolicy,
254    /// Logout signature validation behavior.
255    pub logout: LogoutPolicy,
256}
257
258impl IdpValidationPolicy {
259    /// Validation preset for claimed Web Browser SSO and Single Logout features.
260    ///
261    /// An inbound `AuthnRequest` may be unsigned. A signature that is present
262    /// is verified. Logout requests and responses require a signature.
263    pub fn recommended() -> Self {
264        Self {
265            authn_requests: AuthnRequestValidationPolicy::AllowUnsignedVerifyIfPresent,
266            logout: LogoutPolicy::recommended(),
267        }
268    }
269
270    /// Deprecated bundle of IdP validation choices.
271    ///
272    /// Inbound `AuthnRequest` and logout messages must be signed. The returned
273    /// fields are unchanged from earlier releases.
274    #[deprecated(note = "start from recommended() and add only the named hardenings you want")]
275    #[expect(
276        deprecated,
277        reason = "the deprecated strict preset keeps its current logout bundle"
278    )]
279    pub fn strict() -> Self {
280        Self {
281            authn_requests: AuthnRequestValidationPolicy::RequireSigned,
282            logout: LogoutPolicy::strict(),
283        }
284    }
285
286    /// Legacy permissive preset.
287    ///
288    /// Samlify-port behavior for a caller leaving the raw API. A signature
289    /// present on an `AuthnRequest` is not verified.
290    pub fn compatibility() -> Self {
291        Self {
292            authn_requests: AuthnRequestValidationPolicy::AllowUnsignedForCompatibility,
293            logout: LogoutPolicy::compatibility(),
294        }
295    }
296}
297
298impl Default for IdpValidationPolicy {
299    fn default() -> Self {
300        Self::compatibility()
301    }
302}
303
304/// Logout request and response signature policy.
305///
306/// [`Self::recommended`] requires a signature on requests and responses.
307/// [`Self::compatibility`] is the legacy permissive preset. `strict()` is
308/// deprecated and unchanged.
309///
310/// `finish_slo` reads `responses`. `receive_slo` reads `requests`. With
311/// [`crate::LogoutSigning::FollowLocalPolicy`], `requests` also decides
312/// whether an outbound `LogoutRequest` is signed. See
313/// `docs/conformance/single-logout.md`.
314#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
315pub struct LogoutPolicy {
316    /// LogoutRequest signature behavior.
317    pub requests: LogoutSignaturePolicy,
318    /// LogoutResponse signature behavior.
319    pub responses: LogoutSignaturePolicy,
320}
321
322impl LogoutPolicy {
323    /// Require a signature on logout requests and responses.
324    pub fn recommended() -> Self {
325        Self {
326            requests: LogoutSignaturePolicy::RequireSigned,
327            responses: LogoutSignaturePolicy::RequireSigned,
328        }
329    }
330
331    /// Deprecated logout signature bundle.
332    ///
333    /// Logout requests and responses must still be signed.
334    #[deprecated(note = "start from recommended() and add only the named hardenings you want")]
335    pub fn strict() -> Self {
336        Self {
337            requests: LogoutSignaturePolicy::RequireSigned,
338            responses: LogoutSignaturePolicy::RequireSigned,
339        }
340    }
341
342    /// Legacy permissive preset.
343    ///
344    /// Unsigned logout requests and responses are accepted. This is the
345    /// samlify-port hatch.
346    pub fn compatibility() -> Self {
347        Self {
348            requests: LogoutSignaturePolicy::AllowUnsignedForCompatibility,
349            responses: LogoutSignaturePolicy::AllowUnsignedForCompatibility,
350        }
351    }
352}
353
354/// Whether assertions are encrypted in generated responses.
355#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
356pub enum AssertionEncryptionPolicy {
357    /// Do not encrypt assertions.
358    #[default]
359    PlaintextAssertions,
360    /// Encrypt assertions.
361    EncryptAssertions,
362}
363
364/// XML encryption policy.
365///
366/// # Examples
367///
368/// Use typed configuration to request encrypted assertions in generated
369/// responses. This only configures policy; actual encryption uses the crate's
370/// XML-Enc backend and deployment credentials.
371///
372/// ```
373/// use saml_rs::{EntityId, IdpConfig, SsoEndpoint, XmlEncryptionPolicy, XmlPolicy};
374///
375/// let xml = XmlPolicy {
376///     encryption: XmlEncryptionPolicy::encrypt_assertions(),
377///     ..XmlPolicy::default()
378/// };
379/// let idp_builder = IdpConfig::builder(EntityId::try_new("https://idp.example.com/metadata")?)
380///     .sso_endpoint(SsoEndpoint::post("https://idp.example.com/sso")?)
381///     .xml(xml);
382/// # let _ = idp_builder;
383/// # Ok::<(), saml_rs::SamlError>(())
384/// ```
385#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
386pub struct XmlEncryptionPolicy {
387    /// Assertion encryption behavior.
388    pub assertions: AssertionEncryptionPolicy,
389    allow_insecure_software_rsa_key_transport_decryption: bool,
390}
391
392impl XmlEncryptionPolicy {
393    /// Enable assertion encryption.
394    pub fn encrypt_assertions() -> Self {
395        Self {
396            assertions: AssertionEncryptionPolicy::EncryptAssertions,
397            ..Self::default()
398        }
399    }
400
401    /// Explicitly allow RustCrypto software RSA key-transport decryption despite
402    /// `RUSTSEC-2023-0071` timing-risk concerns in that backend.
403    pub fn allow_insecure_software_rsa_key_transport_decryption() -> Self {
404        Self {
405            allow_insecure_software_rsa_key_transport_decryption: true,
406            ..Self::default()
407        }
408    }
409
410    /// Return a copy with the software RSA key-transport risk explicitly allowed.
411    pub fn with_insecure_software_rsa_key_transport_decryption_allowed(mut self) -> Self {
412        self.allow_insecure_software_rsa_key_transport_decryption = true;
413        self
414    }
415
416    pub(super) fn allows_insecure_software_rsa_key_transport_decryption(self) -> bool {
417        self.allow_insecure_software_rsa_key_transport_decryption
418    }
419}
420
421/// XML parser, redirect decompression, clock, and XML encryption policy.
422///
423/// # Examples
424///
425/// Software RSA key-transport decryption is disabled by default on the
426/// RustCrypto provider because that backend, reached through `bergshamra` /
427/// `kryptering`, is affected by `RUSTSEC-2023-0071`. Enable it only as an
428/// explicit RustCrypto exception for a deployment that accepts
429/// that risk. AWS-LC and FIPS ignore this opt-in.
430///
431/// ```
432/// use saml_rs::{AcsEndpoint, EntityId, SpConfig, XmlEncryptionPolicy, XmlPolicy};
433///
434/// let xml = XmlPolicy {
435///     encryption: XmlEncryptionPolicy::default()
436///         .with_insecure_software_rsa_key_transport_decryption_allowed(),
437///     ..XmlPolicy::default()
438/// };
439/// let sp_builder = SpConfig::builder(EntityId::try_new("https://sp.example.com/metadata")?)
440///     .acs_endpoint(AcsEndpoint::post("https://sp.example.com/acs")?)
441///     .xml(xml);
442/// # let _ = sp_builder;
443/// # Ok::<(), saml_rs::SamlError>(())
444/// ```
445#[derive(Debug, Clone, Copy, PartialEq, Eq)]
446pub struct XmlPolicy {
447    /// Clock drift tolerance `(not_before_ms, not_on_or_after_ms)`.
448    pub clock_drifts: (i64, i64),
449    /// Maximum decoded compressed and inflated raw-DEFLATE bytes accepted for
450    /// HTTP-Redirect input.
451    pub redirect_inflate_max_bytes: usize,
452    /// XML parser resource limits.
453    pub limits: XmlLimits,
454    /// XML encryption behavior.
455    pub encryption: XmlEncryptionPolicy,
456}
457
458impl Default for XmlPolicy {
459    fn default() -> Self {
460        Self {
461            clock_drifts: (0, 0),
462            redirect_inflate_max_bytes: MAX_DEFLATE_RAW_DECODE_BYTES,
463            limits: XmlLimits::default(),
464            encryption: XmlEncryptionPolicy::default(),
465        }
466    }
467}
468
469/// Algorithm choices used by outgoing SAML messages.
470#[derive(Debug, Clone, PartialEq, Eq)]
471pub struct AlgorithmPolicy {
472    /// Signature algorithm URI.
473    pub signature: SignatureAlgorithm,
474    /// Data encryption algorithm URI.
475    pub data_encryption: DataEncryptionAlgorithm,
476    /// Key encryption algorithm URI.
477    pub key_encryption: KeyEncryptionAlgorithm,
478    /// Sign/encrypt operation order for messages that do both.
479    pub message_signing_order: MessageSignatureOrder,
480    /// XML-DSig reference transforms.
481    pub signed_reference_transforms: Vec<TransformAlgorithm>,
482}
483
484impl Default for AlgorithmPolicy {
485    fn default() -> Self {
486        Self {
487            signature: SignatureAlgorithm::default(),
488            data_encryption: DataEncryptionAlgorithm::default(),
489            key_encryption: KeyEncryptionAlgorithm::default(),
490            message_signing_order: MessageSignatureOrder::SignThenEncrypt,
491            signed_reference_transforms: vec![
492                TransformAlgorithm::EnvelopedSignature,
493                TransformAlgorithm::ExclusiveCanonicalization,
494            ],
495        }
496    }
497}
498
499/// Template and XML tag-prefix customization.
500#[derive(Debug, Clone)]
501pub struct TemplatePolicy {
502    /// Default RelayState.
503    pub relay_state: String,
504    /// IdP protocol tag prefix for generated messages.
505    pub tag_prefix_protocol: String,
506    /// IdP assertion tag prefix for generated messages.
507    pub tag_prefix_assertion: String,
508    /// IdP tag prefix for generated `<EncryptedAssertion>` elements.
509    pub tag_prefix_encrypted_assertion: String,
510    /// IdP login response template and attributes.
511    pub login_response_template: Option<LoginResponseTemplate>,
512    /// SP login request template.
513    pub login_request_template: Option<String>,
514    /// Logout request template.
515    ///
516    /// Typed Session Authority generation requires a complete unqualified
517    /// `NotOnOrAfter="{NotOnOrAfter}"` root attribute. Typed SP and raw
518    /// samlify-port generation do not synthesize the attribute.
519    pub logout_request_template: Option<String>,
520    /// Logout response template.
521    ///
522    /// After prefix and placeholder substitution, the final outbound XML must
523    /// satisfy the enforced LogoutResponse structure, issuer, destination, and
524    /// request-correlation requirements. A root `<ds:Signature>` is rejected
525    /// before signing so the library owns signature construction. When
526    /// `InResponseTo` is `None`, an attribute whose complete value is the
527    /// `{InResponseTo}` placeholder is omitted.
528    pub logout_response_template: Option<String>,
529    /// Embedded-signature placement and prefix.
530    pub signature_config: Option<SignatureConfig>,
531}
532
533impl Default for TemplatePolicy {
534    fn default() -> Self {
535        Self {
536            relay_state: String::new(),
537            tag_prefix_protocol: "samlp".to_string(),
538            tag_prefix_assertion: "saml".to_string(),
539            tag_prefix_encrypted_assertion: "saml".to_string(),
540            login_response_template: None,
541            login_request_template: None,
542            logout_request_template: None,
543            logout_response_template: None,
544            signature_config: None,
545        }
546    }
547}
548pub(super) fn authn_request_signing_enabled(policy: AuthnRequestSigningPolicy) -> bool {
549    matches!(policy, AuthnRequestSigningPolicy::Sign)
550}
551
552pub(super) fn authn_request_signature_required(policy: AuthnRequestValidationPolicy) -> bool {
553    matches!(policy, AuthnRequestValidationPolicy::RequireSigned)
554}
555
556pub(super) fn verify_present_authn_request_signature(policy: AuthnRequestValidationPolicy) -> bool {
557    matches!(
558        policy,
559        AuthnRequestValidationPolicy::AllowUnsignedVerifyIfPresent
560    )
561}
562
563pub(super) fn assertion_signature_required(policy: AssertionSignaturePolicy) -> bool {
564    matches!(policy, AssertionSignaturePolicy::RequireSigned)
565}
566
567pub(super) fn response_signature_required(policy: ResponseSignaturePolicy) -> bool {
568    matches!(policy, ResponseSignaturePolicy::RequireSigned)
569}
570
571pub(super) fn encrypted_cbc_response_signature_required(policy: ResponseSignaturePolicy) -> bool {
572    matches!(policy, ResponseSignaturePolicy::RequireForEncryptedCbc)
573}
574
575pub(super) fn logout_signature_required(policy: LogoutSignaturePolicy) -> Result<bool, SamlError> {
576    match policy {
577        LogoutSignaturePolicy::RequireSigned => Ok(true),
578        LogoutSignaturePolicy::AllowUnsignedForCompatibility => Ok(false),
579    }
580}
581pub(super) fn name_id_creation_allowed(policy: NameIdCreationPolicy) -> bool {
582    matches!(policy, NameIdCreationPolicy::AllowCreate)
583}
584
585pub(super) fn audience_validation_enabled(policy: AudienceValidationPolicy) -> bool {
586    matches!(
587        policy,
588        AudienceValidationPolicy::Validate | AudienceValidationPolicy::EvaluatePresentRestrictions
589    )
590}
591
592pub(super) fn audience_restriction_required(policy: AudienceValidationPolicy) -> bool {
593    matches!(policy, AudienceValidationPolicy::Validate)
594}