Skip to main content

saml_rs/config/
builders.rs

1use std::time::Duration;
2
3use crate::browser::{AcsEndpoint, SloEndpoint, SsoEndpoint};
4use crate::entity::EntitySetting;
5use crate::error::SamlError;
6
7use super::algorithms::{name_id_format_uris, transform_algorithm_uris, NameIdFormat};
8use super::credentials::Credentials;
9use super::descriptors::{validate_entity_id, EntityId, IdpMetadataConfig, SpMetadataConfig};
10use super::policies::{
11    assertion_signature_required, audience_validation_enabled, authn_request_signature_required,
12    authn_request_signing_enabled, encrypted_cbc_response_signature_required,
13    logout_signature_required, name_id_creation_allowed, response_signature_required,
14    AlgorithmPolicy, AssertionEncryptionPolicy, AuthnRequestSigningPolicy, IdpValidationPolicy,
15    SpValidationPolicy, TemplatePolicy, XmlPolicy,
16};
17#[cfg(not(any(
18    feature = "crypto-rustcrypto",
19    feature = "crypto-aws-lc",
20    feature = "crypto-fips"
21)))]
22use super::policies::{
23    AssertionSignaturePolicy, AuthnRequestValidationPolicy, LogoutPolicy, LogoutSignaturePolicy,
24    ResponseSignaturePolicy,
25};
26
27/// Typed Service Provider configuration.
28///
29/// # Examples
30///
31/// ```
32/// use saml_rs::{AcsEndpoint, EntityId, SpConfig, SpMetadataConfig};
33///
34/// let acs = AcsEndpoint::post("https://sp.example.com/acs")?;
35/// let config = SpConfig::try_new(
36///     EntityId::try_new("https://sp.example.com/metadata")?,
37///     SpMetadataConfig::new(vec![acs]),
38/// )?;
39///
40/// assert_eq!(config.entity_id.as_str(), "https://sp.example.com/metadata");
41/// # Ok::<(), saml_rs::SamlError>(())
42/// ```
43#[derive(Debug, Clone)]
44pub struct SpConfig {
45    /// Local SP entity ID.
46    pub entity_id: EntityId,
47    /// Local SP metadata inputs.
48    pub metadata: SpMetadataConfig,
49    /// Local credentials.
50    pub credentials: Credentials,
51    /// Validation and outbound signing policy.
52    pub validation: SpValidationPolicy,
53    /// Algorithm policy.
54    pub algorithms: AlgorithmPolicy,
55    /// XML parser, redirect, clock, and encryption policy.
56    pub xml: XmlPolicy,
57    /// Template and prefix policy.
58    pub templates: TemplatePolicy,
59}
60
61impl SpConfig {
62    /// Create SP configuration with required identity and metadata inputs.
63    ///
64    /// This convenience constructor accepts already-validated typed inputs but
65    /// does not validate the final config. Use [`Self::try_new`] or
66    /// [`Self::builder`] for caller-provided setup.
67    pub fn new(entity_id: EntityId, metadata: SpMetadataConfig) -> Self {
68        Self {
69            entity_id,
70            metadata,
71            credentials: Credentials::default(),
72            validation: SpValidationPolicy::default(),
73            algorithms: AlgorithmPolicy::default(),
74            xml: XmlPolicy::default(),
75            templates: TemplatePolicy::default(),
76        }
77    }
78
79    /// Validate and create SP configuration with compatibility defaults.
80    ///
81    /// # Errors
82    ///
83    /// Returns [`SamlError`] when the entity ID is empty or required SP
84    /// metadata endpoints are missing.
85    pub fn try_new(entity_id: EntityId, metadata: SpMetadataConfig) -> Result<Self, SamlError> {
86        let config = Self::new(entity_id, metadata);
87        config.validate()?;
88        Ok(config)
89    }
90
91    /// Start a dependency-free SP config builder with strict typed defaults.
92    pub fn builder(entity_id: EntityId) -> SpConfigBuilder {
93        SpConfigBuilder::new(entity_id)
94    }
95
96    /// Validate required SP config fields.
97    ///
98    /// # Errors
99    ///
100    /// Returns [`SamlError`] when the entity ID is empty or required SP
101    /// metadata endpoints are missing.
102    pub fn validate(&self) -> Result<(), SamlError> {
103        validate_entity_id(&self.entity_id)?;
104        self.metadata.validate()?;
105        validate_sp_policy(self)
106    }
107}
108
109/// Dependency-free builder for [`SpConfig`].
110#[derive(Debug, Clone)]
111pub struct SpConfigBuilder {
112    entity_id: EntityId,
113    metadata: SpMetadataConfig,
114    credentials: Credentials,
115    validation: SpValidationPolicy,
116    algorithms: AlgorithmPolicy,
117    xml: XmlPolicy,
118    templates: TemplatePolicy,
119}
120
121impl SpConfigBuilder {
122    fn new(entity_id: EntityId) -> Self {
123        Self {
124            entity_id,
125            metadata: SpMetadataConfig::new(Vec::new()),
126            credentials: Credentials::default(),
127            validation: SpValidationPolicy::strict(),
128            algorithms: AlgorithmPolicy::default(),
129            xml: XmlPolicy::default(),
130            templates: TemplatePolicy::default(),
131        }
132    }
133
134    /// Add an ACS endpoint.
135    pub fn acs_endpoint(mut self, endpoint: AcsEndpoint) -> Self {
136        self.metadata.assertion_consumer_service.push(endpoint);
137        self
138    }
139
140    /// Add an SLO endpoint.
141    pub fn slo_endpoint(mut self, endpoint: SloEndpoint) -> Self {
142        self.metadata.single_logout_service.push(endpoint);
143        self
144    }
145
146    /// Add an advertised NameID format.
147    pub fn name_id_format(mut self, format: NameIdFormat) -> Self {
148        self.metadata.name_id_format.push(format);
149        self
150    }
151
152    /// Set generated metadata element ordering.
153    pub fn elements_order(mut self, elements_order: Vec<String>) -> Self {
154        self.metadata.elements_order = Some(elements_order);
155        self
156    }
157
158    /// Set local credentials.
159    pub fn credentials(mut self, credentials: Credentials) -> Self {
160        self.credentials = credentials;
161        self
162    }
163
164    /// Set SP validation and outbound signing policy.
165    pub fn validation(mut self, validation: SpValidationPolicy) -> Self {
166        self.validation = validation;
167        self
168    }
169
170    /// Set algorithm policy.
171    pub fn algorithms(mut self, algorithms: AlgorithmPolicy) -> Self {
172        self.algorithms = algorithms;
173        self
174    }
175
176    /// Set XML policy.
177    pub fn xml(mut self, xml: XmlPolicy) -> Self {
178        self.xml = xml;
179        self
180    }
181
182    /// Set template policy.
183    pub fn templates(mut self, templates: TemplatePolicy) -> Self {
184        self.templates = templates;
185        self
186    }
187
188    /// Build and validate the SP config.
189    ///
190    /// # Errors
191    ///
192    /// Returns [`SamlError`] when required fields are missing, selected policy
193    /// needs unavailable credentials, or selected policy requires crypto in a
194    /// no-default-features build.
195    pub fn build(self) -> Result<SpConfig, SamlError> {
196        let config = SpConfig {
197            entity_id: self.entity_id,
198            metadata: self.metadata,
199            credentials: self.credentials,
200            validation: self.validation,
201            algorithms: self.algorithms,
202            xml: self.xml,
203            templates: self.templates,
204        };
205        config.validate()?;
206        Ok(config)
207    }
208}
209
210/// Typed Identity Provider configuration.
211///
212/// # Examples
213///
214/// The builder starts with strict validation defaults. Use compatibility
215/// policy explicitly when compiling or testing without the default crypto
216/// feature.
217///
218/// ```
219/// use saml_rs::{EntityId, IdpConfig, IdpValidationPolicy, SsoEndpoint};
220/// use std::time::Duration;
221///
222/// let config = IdpConfig::builder(EntityId::try_new("https://idp.example.com/metadata")?)
223///     .sso_endpoint(SsoEndpoint::post("https://idp.example.com/sso")?)
224///     .issuance_lifetime(Duration::from_secs(10 * 60))
225///     .validation(IdpValidationPolicy::compatibility())
226///     .build()?;
227///
228/// assert_eq!(config.entity_id.as_str(), "https://idp.example.com/metadata");
229/// # Ok::<(), saml_rs::SamlError>(())
230/// ```
231#[derive(Debug, Clone)]
232pub struct IdpConfig {
233    /// Local IdP entity ID.
234    pub entity_id: EntityId,
235    /// Local IdP metadata inputs.
236    pub metadata: IdpMetadataConfig,
237    /// Local credentials.
238    pub credentials: Credentials,
239    /// Lifetime applied to assertions issued by this IdP and to
240    /// Session Authority LogoutRequests.
241    ///
242    /// The default is exactly five minutes. That value is saml-rs policy, not
243    /// an OASIS-mandated duration.
244    pub issuance_lifetime: Duration,
245    /// Validation policy.
246    pub validation: IdpValidationPolicy,
247    /// Algorithm policy.
248    pub algorithms: AlgorithmPolicy,
249    /// XML parser, redirect, clock, and encryption policy.
250    pub xml: XmlPolicy,
251    /// Template and prefix policy.
252    pub templates: TemplatePolicy,
253}
254
255impl IdpConfig {
256    /// Create IdP configuration with required identity and metadata inputs.
257    ///
258    /// This convenience constructor accepts already-validated typed inputs but
259    /// does not validate the final config. The issuance lifetime defaults to
260    /// exactly five minutes. Use [`Self::try_new`] or [`Self::builder`] for
261    /// caller-provided setup.
262    pub fn new(entity_id: EntityId, metadata: IdpMetadataConfig) -> Self {
263        Self {
264            entity_id,
265            metadata,
266            credentials: Credentials::default(),
267            issuance_lifetime: Duration::from_secs(300),
268            validation: IdpValidationPolicy::default(),
269            algorithms: AlgorithmPolicy::default(),
270            xml: XmlPolicy::default(),
271            templates: TemplatePolicy::default(),
272        }
273    }
274
275    /// Validate and create IdP configuration with compatibility defaults.
276    ///
277    /// # Errors
278    ///
279    /// Returns [`SamlError`] when the entity ID is empty, required IdP
280    /// metadata endpoints are missing, or the issuance lifetime is zero or
281    /// outside the range supported by the internal time representation.
282    pub fn try_new(entity_id: EntityId, metadata: IdpMetadataConfig) -> Result<Self, SamlError> {
283        let config = Self::new(entity_id, metadata);
284        config.validate()?;
285        Ok(config)
286    }
287
288    /// Start a dependency-free IdP config builder with strict typed defaults
289    /// and a five-minute issuance lifetime.
290    pub fn builder(entity_id: EntityId) -> IdpConfigBuilder {
291        IdpConfigBuilder::new(entity_id)
292    }
293
294    /// Validate required IdP config fields.
295    ///
296    /// # Errors
297    ///
298    /// Returns [`SamlError`] when the entity ID is empty, required IdP
299    /// metadata endpoints are missing, or the issuance lifetime is zero or
300    /// outside the range supported by the internal time representation.
301    pub fn validate(&self) -> Result<(), SamlError> {
302        validate_entity_id(&self.entity_id)?;
303        self.metadata.validate()?;
304        validate_idp_policy(self)
305    }
306}
307
308/// Dependency-free builder for [`IdpConfig`].
309#[derive(Debug, Clone)]
310pub struct IdpConfigBuilder {
311    entity_id: EntityId,
312    metadata: IdpMetadataConfig,
313    credentials: Credentials,
314    issuance_lifetime: Duration,
315    validation: IdpValidationPolicy,
316    algorithms: AlgorithmPolicy,
317    xml: XmlPolicy,
318    templates: TemplatePolicy,
319}
320
321impl IdpConfigBuilder {
322    fn new(entity_id: EntityId) -> Self {
323        Self {
324            entity_id,
325            metadata: IdpMetadataConfig::new(Vec::new()),
326            credentials: Credentials::default(),
327            issuance_lifetime: Duration::from_secs(300),
328            validation: IdpValidationPolicy::strict(),
329            algorithms: AlgorithmPolicy::default(),
330            xml: XmlPolicy::default(),
331            templates: TemplatePolicy::default(),
332        }
333    }
334
335    /// Add an SSO endpoint.
336    pub fn sso_endpoint(mut self, endpoint: SsoEndpoint) -> Self {
337        self.metadata.single_sign_on_service.push(endpoint);
338        self
339    }
340
341    /// Add an SLO endpoint.
342    pub fn slo_endpoint(mut self, endpoint: SloEndpoint) -> Self {
343        self.metadata.single_logout_service.push(endpoint);
344        self
345    }
346
347    /// Add an advertised NameID format.
348    pub fn name_id_format(mut self, format: NameIdFormat) -> Self {
349        self.metadata.name_id_format.push(format);
350        self
351    }
352
353    /// Set generated metadata element ordering.
354    pub fn elements_order(mut self, elements_order: Vec<String>) -> Self {
355        self.metadata.elements_order = Some(elements_order);
356        self
357    }
358
359    /// Set local credentials.
360    pub fn credentials(mut self, credentials: Credentials) -> Self {
361        self.credentials = credentials;
362        self
363    }
364
365    /// Set the lifetime for issued assertions and Session Authority
366    /// LogoutRequests.
367    ///
368    /// The value is validated by [`Self::build`].
369    pub fn issuance_lifetime(mut self, issuance_lifetime: Duration) -> Self {
370        self.issuance_lifetime = issuance_lifetime;
371        self
372    }
373
374    /// Set IdP validation policy.
375    pub fn validation(mut self, validation: IdpValidationPolicy) -> Self {
376        self.validation = validation;
377        self
378    }
379
380    /// Set algorithm policy.
381    pub fn algorithms(mut self, algorithms: AlgorithmPolicy) -> Self {
382        self.algorithms = algorithms;
383        self
384    }
385
386    /// Set XML policy.
387    pub fn xml(mut self, xml: XmlPolicy) -> Self {
388        self.xml = xml;
389        self
390    }
391
392    /// Set template policy.
393    pub fn templates(mut self, templates: TemplatePolicy) -> Self {
394        self.templates = templates;
395        self
396    }
397
398    /// Build and validate the IdP config.
399    ///
400    /// # Errors
401    ///
402    /// Returns [`SamlError`] when required fields are missing, the issuance
403    /// lifetime is zero or outside the internal supported range, or selected
404    /// policy requires crypto in a no-default-features build.
405    pub fn build(self) -> Result<IdpConfig, SamlError> {
406        let config = IdpConfig {
407            entity_id: self.entity_id,
408            metadata: self.metadata,
409            credentials: self.credentials,
410            issuance_lifetime: self.issuance_lifetime,
411            validation: self.validation,
412            algorithms: self.algorithms,
413            xml: self.xml,
414            templates: self.templates,
415        };
416        config.validate()?;
417        Ok(config)
418    }
419}
420
421fn validate_common_credentials(credentials: &Credentials) -> Result<(), SamlError> {
422    if credentials.signing_key_passphrase.is_some() && credentials.signing_key.is_none() {
423        return Err(SamlError::MissingKey("signing_key".into()));
424    }
425    if credentials.decryption_key_passphrase.is_some() && credentials.decryption_key.is_none() {
426        return Err(SamlError::MissingKey("decryption_key".into()));
427    }
428    Ok(())
429}
430
431fn validate_sp_policy(config: &SpConfig) -> Result<(), SamlError> {
432    validate_sp_crypto_support(config)?;
433    validate_common_credentials(&config.credentials)?;
434    if matches!(
435        config.validation.authn_requests,
436        AuthnRequestSigningPolicy::Sign
437    ) {
438        validate_signing_credentials(&config.credentials)?;
439    }
440    if matches!(
441        config.xml.encryption.assertions,
442        AssertionEncryptionPolicy::EncryptAssertions
443    ) && config.credentials.decryption_key.is_none()
444    {
445        return Err(SamlError::MissingKey("decryption_key".into()));
446    }
447    Ok(())
448}
449
450fn validate_idp_policy(config: &IdpConfig) -> Result<(), SamlError> {
451    validated_idp_issuance_lifetime(config.issuance_lifetime)?;
452    validate_idp_crypto_support(config)?;
453    validate_common_credentials(&config.credentials)
454}
455
456pub(crate) fn validated_idp_issuance_lifetime(
457    issuance_lifetime: Duration,
458) -> Result<time::Duration, SamlError> {
459    if issuance_lifetime.is_zero() {
460        return Err(SamlError::Invalid(
461            "IdP issuance lifetime must be greater than zero".into(),
462        ));
463    }
464    time::Duration::try_from(issuance_lifetime).map_err(|_| {
465        SamlError::Invalid(
466            "IdP issuance lifetime must fit in time::Duration (at most i64::MAX seconds plus 999,999,999 nanoseconds)".into(),
467        )
468    })
469}
470
471fn validate_signing_credentials(credentials: &Credentials) -> Result<(), SamlError> {
472    if credentials.signing_key.is_none() {
473        return Err(SamlError::MissingKey("signing_key".into()));
474    }
475    if credentials.signing_certificate.is_none() {
476        return Err(SamlError::MissingKey("signing_certificate".into()));
477    }
478    Ok(())
479}
480
481#[cfg(any(
482    feature = "crypto-rustcrypto",
483    feature = "crypto-aws-lc",
484    feature = "crypto-fips"
485))]
486fn validate_sp_crypto_support(_config: &SpConfig) -> Result<(), SamlError> {
487    Ok(())
488}
489
490#[cfg(not(any(
491    feature = "crypto-rustcrypto",
492    feature = "crypto-aws-lc",
493    feature = "crypto-fips"
494)))]
495fn validate_sp_crypto_support(config: &SpConfig) -> Result<(), SamlError> {
496    if sp_config_requires_crypto(config) {
497        return Err(SamlError::Unsupported(
498            "selected SP config policy requires a crypto provider feature".into(),
499        ));
500    }
501    Ok(())
502}
503
504#[cfg(any(
505    feature = "crypto-rustcrypto",
506    feature = "crypto-aws-lc",
507    feature = "crypto-fips"
508))]
509fn validate_idp_crypto_support(_config: &IdpConfig) -> Result<(), SamlError> {
510    Ok(())
511}
512
513#[cfg(not(any(
514    feature = "crypto-rustcrypto",
515    feature = "crypto-aws-lc",
516    feature = "crypto-fips"
517)))]
518fn validate_idp_crypto_support(config: &IdpConfig) -> Result<(), SamlError> {
519    if idp_config_requires_crypto(config) {
520        return Err(SamlError::Unsupported(
521            "selected IdP config policy requires a crypto provider feature".into(),
522        ));
523    }
524    Ok(())
525}
526
527#[cfg(not(any(
528    feature = "crypto-rustcrypto",
529    feature = "crypto-aws-lc",
530    feature = "crypto-fips"
531)))]
532fn sp_config_requires_crypto(config: &SpConfig) -> bool {
533    matches!(
534        config.validation.assertions,
535        AssertionSignaturePolicy::RequireSigned
536    ) || matches!(
537        config.validation.responses,
538        ResponseSignaturePolicy::RequireSigned | ResponseSignaturePolicy::RequireForEncryptedCbc
539    ) || matches!(
540        config.validation.authn_requests,
541        AuthnRequestSigningPolicy::Sign
542    ) || logout_policy_requires_crypto(config.validation.logout)
543        || matches!(
544            config.xml.encryption.assertions,
545            AssertionEncryptionPolicy::EncryptAssertions
546        )
547}
548
549#[cfg(not(any(
550    feature = "crypto-rustcrypto",
551    feature = "crypto-aws-lc",
552    feature = "crypto-fips"
553)))]
554fn idp_config_requires_crypto(config: &IdpConfig) -> bool {
555    matches!(
556        config.validation.authn_requests,
557        AuthnRequestValidationPolicy::RequireSigned
558    ) || logout_policy_requires_crypto(config.validation.logout)
559        || matches!(
560            config.xml.encryption.assertions,
561            AssertionEncryptionPolicy::EncryptAssertions
562        )
563}
564
565#[cfg(not(any(
566    feature = "crypto-rustcrypto",
567    feature = "crypto-aws-lc",
568    feature = "crypto-fips"
569)))]
570fn logout_policy_requires_crypto(policy: LogoutPolicy) -> bool {
571    logout_signature_policy_requires_crypto(policy.requests)
572        || logout_signature_policy_requires_crypto(policy.responses)
573}
574
575#[cfg(not(any(
576    feature = "crypto-rustcrypto",
577    feature = "crypto-aws-lc",
578    feature = "crypto-fips"
579)))]
580fn logout_signature_policy_requires_crypto(policy: LogoutSignaturePolicy) -> bool {
581    matches!(policy, LogoutSignaturePolicy::RequireSigned)
582}
583
584fn apply_common_settings(
585    entity_id: &EntityId,
586    name_id_format: &[NameIdFormat],
587    credentials: &Credentials,
588    algorithms: &AlgorithmPolicy,
589    xml: &XmlPolicy,
590    templates: &TemplatePolicy,
591    setting: &mut EntitySetting,
592) {
593    setting.entity_id = Some(entity_id.as_str().to_string());
594    setting.request_signature_algorithm = algorithms.signature.as_uri().to_string();
595    setting.data_encryption_algorithm = algorithms.data_encryption.as_uri().to_string();
596    setting.key_encryption_algorithm = algorithms.key_encryption.as_uri().to_string();
597    setting.message_signing_order = algorithms.message_signing_order;
598    setting.is_assertion_encrypted = matches!(
599        xml.encryption.assertions,
600        AssertionEncryptionPolicy::EncryptAssertions
601    );
602    setting.allow_insecure_software_rsa_key_transport_decryption = xml
603        .encryption
604        .allows_insecure_software_rsa_key_transport_decryption();
605    setting.relay_state = templates.relay_state.clone();
606    setting.name_id_format = name_id_format_uris(name_id_format);
607    setting.private_key = credentials
608        .signing_key
609        .as_ref()
610        .map(|key| key.as_str().to_string());
611    setting.private_key_pass = credentials
612        .signing_key_passphrase
613        .as_ref()
614        .map(|passphrase| passphrase.as_str().to_string());
615    setting.signing_cert = credentials
616        .signing_certificate
617        .as_ref()
618        .map(|certificate| certificate.as_str().to_string());
619    setting.encrypt_cert = credentials
620        .encryption_certificate
621        .as_ref()
622        .map(|certificate| certificate.as_str().to_string());
623    setting.enc_private_key = credentials
624        .decryption_key
625        .as_ref()
626        .map(|key| key.as_str().to_string());
627    setting.enc_private_key_pass = credentials
628        .decryption_key_passphrase
629        .as_ref()
630        .map(|passphrase| passphrase.as_str().to_string());
631    setting.clock_drifts = xml.clock_drifts;
632    setting.redirect_inflate_max_bytes = xml.redirect_inflate_max_bytes;
633    setting.xml_limits = xml.limits;
634    setting.tag_prefix_protocol = templates.tag_prefix_protocol.clone();
635    setting.tag_prefix_assertion = templates.tag_prefix_assertion.clone();
636    setting.tag_prefix_encrypted_assertion = templates.tag_prefix_encrypted_assertion.clone();
637    setting.login_response_template = templates.login_response_template.clone();
638    setting.login_request_template = templates.login_request_template.clone();
639    setting.logout_request_template = templates.logout_request_template.clone();
640    setting.logout_response_template = templates.logout_response_template.clone();
641    setting.signature_config = templates.signature_config.clone();
642    setting.transformation_algorithms =
643        transform_algorithm_uris(&algorithms.signed_reference_transforms);
644}
645
646impl TryFrom<&SpConfig> for EntitySetting {
647    type Error = SamlError;
648
649    fn try_from(config: &SpConfig) -> Result<Self, Self::Error> {
650        config.validate()?;
651        let mut setting = Self::default();
652        apply_common_settings(
653            &config.entity_id,
654            &config.metadata.name_id_format,
655            &config.credentials,
656            &config.algorithms,
657            &config.xml,
658            &config.templates,
659            &mut setting,
660        );
661        setting.allow_create = name_id_creation_allowed(config.validation.name_id_creation);
662        setting.authn_requests_signed =
663            authn_request_signing_enabled(config.validation.authn_requests);
664        setting.want_assertions_signed = assertion_signature_required(config.validation.assertions);
665        setting.validate_audience = audience_validation_enabled(config.validation.audience);
666        setting.want_message_signed = response_signature_required(config.validation.responses);
667        setting.want_encrypted_cbc_response_signed =
668            encrypted_cbc_response_signature_required(config.validation.responses);
669        setting.want_logout_request_signed =
670            logout_signature_required(config.validation.logout.requests)?;
671        setting.want_logout_response_signed =
672            logout_signature_required(config.validation.logout.responses)?;
673        Ok(setting)
674    }
675}
676
677impl TryFrom<&IdpConfig> for EntitySetting {
678    type Error = SamlError;
679
680    fn try_from(config: &IdpConfig) -> Result<Self, Self::Error> {
681        config.validate()?;
682        let mut setting = Self::default();
683        apply_common_settings(
684            &config.entity_id,
685            &config.metadata.name_id_format,
686            &config.credentials,
687            &config.algorithms,
688            &config.xml,
689            &config.templates,
690            &mut setting,
691        );
692        setting.want_authn_requests_signed =
693            authn_request_signature_required(config.validation.authn_requests);
694        setting.want_logout_request_signed =
695            logout_signature_required(config.validation.logout.requests)?;
696        setting.want_logout_response_signed =
697            logout_signature_required(config.validation.logout.responses)?;
698        Ok(setting)
699    }
700}