use super::error::Error;
use crate::crypto;
use crate::crypto::CertificateDer;
use crate::metadata::EntityDescriptor;
pub struct SPMetadataExtractor(EntityDescriptor);
pub struct RequiredAttribute {
pub name: String,
pub format: Option<String>,
}
pub struct Acs {
pub bind_type: BindType,
pub url: String,
}
pub enum BindType {
Post,
}
impl SPMetadataExtractor {
pub fn try_from_xml(xml: &str) -> Result<Self, Box<dyn std::error::Error>> {
Ok(Self(xml.parse()?))
}
pub fn issuer(&self) -> Result<String, Error> {
self.0.entity_id.clone().ok_or(Error::MissingAudience)
}
pub fn acs(&self) -> Result<Acs, Error> {
let (binding, location) = self
.0
.sp_sso_descriptors
.as_ref()
.and_then(|d| d.first())
.and_then(|sd| sd.assertion_consumer_services.first())
.map(|acs| (acs.binding.as_str(), acs.location.as_str()))
.ok_or(Error::MissingAcsUrl)?;
if binding != "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" {
return Err(Error::NonHttpPostBindingUnsupported);
}
Ok(Acs {
bind_type: BindType::Post,
url: location.to_string(),
})
}
pub fn required_attributes(&self) -> Vec<RequiredAttribute> {
self.0
.sp_sso_descriptors
.as_ref()
.and_then(|d| d.first())
.and_then(|sd| sd.attribute_consuming_services.as_ref())
.and_then(|s| s.first())
.map(|acs| {
acs.request_attributes
.iter()
.filter(|ra| ra.is_required == Some(true))
.map(|ra| RequiredAttribute {
name: ra.name.clone(),
format: ra.name_format.clone(),
})
.collect()
})
.unwrap_or_default()
}
pub fn verification_cert(&self) -> Result<CertificateDer, Box<dyn std::error::Error>> {
let sp_descriptors = self
.0
.sp_sso_descriptors
.as_ref()
.ok_or(Error::NoSPSsoDescriptors)?;
for sp_descriptor in sp_descriptors {
match sp_descriptor.key_descriptors.as_ref() {
Some(kd) => {
let data = kd
.iter()
.filter(|d| d.is_signing())
.flat_map(|d| {
d.key_info
.x509_data
.iter()
.flat_map(|d| d.certificates.iter())
})
.next()
.ok_or(Error::NoCertificate)?;
return Ok(crypto::decode_x509_cert(data.as_str())?);
}
None => continue,
};
}
Err(Error::NoCertificate.into())
}
}