use super::{as_object_list, Metadata};
use crate::constants::Binding;
use crate::error::SamlError;
use crate::xml::{ExtractorField, XmlLimits};
use std::ops::Deref;
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct AcsMetadataEndpoint {
pub(crate) binding: Binding,
pub(crate) location: String,
pub(crate) index: Option<u16>,
pub(crate) is_default: bool,
}
#[derive(Debug, Clone)]
pub struct SpMetadata {
inner: Metadata,
}
impl SpMetadata {
pub fn from_xml(xml: &str) -> Result<Self, SamlError> {
Self::from_xml_with_limits(xml, XmlLimits::default())
}
pub fn from_xml_with_limits(xml: &str, limits: XmlLimits) -> Result<Self, SamlError> {
let extra = vec![
ExtractorField::new("spSSODescriptor", &["EntityDescriptor", "SPSSODescriptor"])
.attrs(&["WantAssertionsSigned", "AuthnRequestsSigned"]),
ExtractorField::new(
"assertionConsumerService",
&[
"EntityDescriptor",
"SPSSODescriptor",
"AssertionConsumerService",
],
)
.attrs(&["Binding", "Location", "isDefault", "index"]),
];
Ok(Self {
inner: Metadata::parse_with_limits(xml, extra, limits)?,
})
}
pub fn is_want_assertions_signed(&self) -> bool {
self.inner
.meta
.get_str("spSSODescriptor.wantAssertionsSigned")
== Some("true")
}
pub fn is_authn_request_signed(&self) -> bool {
self.inner
.meta
.get_str("spSSODescriptor.authnRequestsSigned")
== Some("true")
}
pub fn get_assertion_consumer_service(&self, binding: Binding) -> Option<String> {
self.get_assertion_consumer_service_endpoint(binding)
.map(|endpoint| endpoint.location)
}
pub(crate) fn get_assertion_consumer_service_endpoint(
&self,
binding: Binding,
) -> Option<AcsMetadataEndpoint> {
self.assertion_consumer_service_endpoints()
.into_iter()
.find(|endpoint| endpoint.binding == binding)
}
pub(crate) fn get_assertion_consumer_service_by_index(
&self,
index: u16,
) -> Result<Option<AcsMetadataEndpoint>, SamlError> {
let mut matches = self
.assertion_consumer_service_endpoints()
.into_iter()
.filter(|endpoint| endpoint.index == Some(index));
let first = matches.next();
if matches.next().is_some() {
return Err(SamlError::Invalid(format!(
"duplicate AssertionConsumerService index {index}"
)));
}
Ok(first)
}
pub(crate) fn has_assertion_consumer_service(&self, binding: Binding, location: &str) -> bool {
self.assertion_consumer_service_endpoints()
.iter()
.any(|endpoint| endpoint.binding == binding && endpoint.location == location)
}
fn assertion_consumer_service_endpoints(&self) -> Vec<AcsMetadataEndpoint> {
let Some(acs) = self.inner.meta.get("assertionConsumerService") else {
return Vec::new();
};
as_object_list(acs)
.into_iter()
.filter_map(acs_metadata_endpoint_from_value)
.collect()
}
}
fn acs_metadata_endpoint_from_value(value: &crate::util::Value) -> Option<AcsMetadataEndpoint> {
let binding = Binding::from_urn(value.get_str("binding")?)?;
let location = value.get_str("location")?.to_string();
let index = value
.get_str("index")
.and_then(|index| index.parse::<u16>().ok());
let is_default = value.get_str("isDefault") == Some("true");
Some(AcsMetadataEndpoint {
binding,
location,
index,
is_default,
})
}
impl Deref for SpMetadata {
type Target = Metadata;
fn deref(&self) -> &Metadata {
&self.inner
}
}