1use super::{as_object_list, Metadata};
4use crate::constants::Binding;
5use crate::error::SamlError;
6use crate::xml::{ExtractorField, XmlLimits};
7use std::ops::Deref;
8
9#[derive(Debug, Clone, PartialEq, Eq)]
11pub(crate) struct AcsMetadataEndpoint {
12 pub(crate) binding: Binding,
14 pub(crate) location: String,
16 pub(crate) index: Option<u16>,
18 pub(crate) is_default: bool,
20}
21
22#[derive(Debug, Clone)]
24pub struct SpMetadata {
25 inner: Metadata,
26}
27
28impl SpMetadata {
29 pub fn from_xml(xml: &str) -> Result<Self, SamlError> {
36 Self::from_xml_with_limits(xml, XmlLimits::default())
37 }
38
39 pub fn from_xml_with_limits(xml: &str, limits: XmlLimits) -> Result<Self, SamlError> {
46 let extra = vec![
47 ExtractorField::new("spSSODescriptor", &["EntityDescriptor", "SPSSODescriptor"])
48 .attrs(&["WantAssertionsSigned", "AuthnRequestsSigned"]),
49 ExtractorField::new(
50 "assertionConsumerService",
51 &[
52 "EntityDescriptor",
53 "SPSSODescriptor",
54 "AssertionConsumerService",
55 ],
56 )
57 .attrs(&["Binding", "Location", "isDefault", "index"]),
58 ];
59 Ok(Self {
60 inner: Metadata::parse_with_limits(xml, extra, limits)?,
61 })
62 }
63
64 pub fn is_want_assertions_signed(&self) -> bool {
66 self.inner
67 .meta
68 .get_str("spSSODescriptor.wantAssertionsSigned")
69 == Some("true")
70 }
71
72 pub fn is_authn_request_signed(&self) -> bool {
74 self.inner
75 .meta
76 .get_str("spSSODescriptor.authnRequestsSigned")
77 == Some("true")
78 }
79
80 pub fn get_assertion_consumer_service(&self, binding: Binding) -> Option<String> {
82 self.get_assertion_consumer_service_endpoint(binding)
83 .map(|endpoint| endpoint.location)
84 }
85
86 pub(crate) fn get_assertion_consumer_service_endpoint(
88 &self,
89 binding: Binding,
90 ) -> Option<AcsMetadataEndpoint> {
91 self.assertion_consumer_service_endpoints()
92 .into_iter()
93 .find(|endpoint| endpoint.binding == binding)
94 }
95
96 pub(crate) fn get_assertion_consumer_service_by_index(
98 &self,
99 index: u16,
100 ) -> Result<Option<AcsMetadataEndpoint>, SamlError> {
101 let mut matches = self
102 .assertion_consumer_service_endpoints()
103 .into_iter()
104 .filter(|endpoint| endpoint.index == Some(index));
105 let first = matches.next();
106 if matches.next().is_some() {
107 return Err(SamlError::Invalid(format!(
108 "duplicate AssertionConsumerService index {index}"
109 )));
110 }
111 Ok(first)
112 }
113
114 pub(crate) fn has_assertion_consumer_service(&self, binding: Binding, location: &str) -> bool {
116 self.assertion_consumer_service_endpoints()
117 .iter()
118 .any(|endpoint| endpoint.binding == binding && endpoint.location == location)
119 }
120
121 fn assertion_consumer_service_endpoints(&self) -> Vec<AcsMetadataEndpoint> {
122 let Some(acs) = self.inner.meta.get("assertionConsumerService") else {
123 return Vec::new();
124 };
125 as_object_list(acs)
126 .into_iter()
127 .filter_map(acs_metadata_endpoint_from_value)
128 .collect()
129 }
130}
131
132fn acs_metadata_endpoint_from_value(value: &crate::util::Value) -> Option<AcsMetadataEndpoint> {
133 let binding = Binding::from_urn(value.get_str("binding")?)?;
134 let location = value.get_str("location")?.to_string();
135 let index = value
136 .get_str("index")
137 .and_then(|index| index.parse::<u16>().ok());
138 let is_default = value.get_str("isDefault") == Some("true");
139 Some(AcsMetadataEndpoint {
140 binding,
141 location,
142 index,
143 is_default,
144 })
145}
146
147impl Deref for SpMetadata {
148 type Target = Metadata;
149 fn deref(&self) -> &Metadata {
150 &self.inner
151 }
152}