Skip to content

Commit

Permalink
Improve ToXml trait
Browse files Browse the repository at this point in the history
  • Loading branch information
njaremko committed Jul 3, 2024
1 parent d9443b1 commit e7a9b1a
Show file tree
Hide file tree
Showing 13 changed files with 99 additions and 57 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

### 0.0.17

- Expose the `ToXml` trait
- Expose the `ToXml` trait, and make it consistent with libraries like `serde_json`

### 0.0.16

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
.slo_url("http://localhost:8080/saml/slo".to_string())
.build()?;

let metadata = sp.metadata()?.to_xml()?;
let metadata = sp.metadata()?.to_string()?;

let metadata_route = warp::get()
.and(warp::path("metadata"))
Expand Down
2 changes: 1 addition & 1 deletion src/idp/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ impl IdentityProvider {
attributes,
);

let response_xml_unsigned = response.to_xml()?;
let response_xml_unsigned = response.to_string()?;
let signed_xml = crypto::sign_xml(
response_xml_unsigned.as_str(),
self.export_private_key_der()?.as_slice(),
Expand Down
2 changes: 1 addition & 1 deletion src/idp/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ fn test_signed_response() {
.expect("failed to created and sign response");

let out_xml = out_response
.to_xml()
.to_string()
.expect("failed to serialize response xml");

verify_signed_xml(out_xml.as_bytes(), idp_cert.as_slice(), Some("ID"))
Expand Down
2 changes: 1 addition & 1 deletion src/idp/verified_request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ mod test {
.try_verify_self_signed()
.expect("failed to verify self signed signature");
let verified_request_xml = expected_verified
.to_xml()
.to_string()
.expect("Failed to serialize verified authn request");
let reparsed_unverified =
UnverifiedAuthnRequest::from_xml(&verified_request_xml).expect("failed to parse");
Expand Down
10 changes: 5 additions & 5 deletions src/metadata/entity_descriptor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ mod test {
.parse()
.expect("Failed to parse sp_metadata.xml into an EntityDescriptor");
let output_xml = entity_descriptor
.to_xml()
.to_string()
.expect("Failed to convert EntityDescriptor to xml");
let reparsed_entity_descriptor: EntityDescriptor = output_xml
.parse()
Expand All @@ -344,7 +344,7 @@ mod test {
.parse()
.expect("Failed to parse idp_metadata.xml into an EntityDescriptor");
let output_xml = entity_descriptor
.to_xml()
.to_string()
.expect("Failed to convert EntityDescriptor to xml");
let reparsed_entity_descriptor: EntityDescriptor = output_xml
.parse()
Expand All @@ -363,7 +363,7 @@ mod test {
.parse()
.expect("Failed to parse idp_metadata_nested.xml into an EntitiesDescriptor");
let output_xml = entities_descriptor
.to_xml()
.to_string()
.expect("Failed to convert EntitiesDescriptor to xml");
let reparsed_entities_descriptor: EntitiesDescriptor = output_xml
.parse()
Expand All @@ -383,7 +383,7 @@ mod test {
.parse()
.expect("Failed to parse idp_metadata.xml into an EntityDescriptorType");
let output_xml = entity_descriptor_type
.to_xml()
.to_string()
.expect("Failed to convert EntityDescriptorType to xml");
let reparsed_entity_descriptor_type: EntityDescriptorType = output_xml
.parse()
Expand Down Expand Up @@ -412,7 +412,7 @@ mod test {
.parse()
.expect("Failed to parse idp_metadata_nested.xml into an EntityDescriptorType");
let output_xml = entity_descriptor_type
.to_xml()
.to_string()
.expect("Failed to convert EntityDescriptorType to xml");
let reparsed_entity_descriptor_type: EntityDescriptorType = output_xml
.parse()
Expand Down
2 changes: 1 addition & 1 deletion src/schema/authn_request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ impl AuthnRequest {
) -> Result<String, Box<dyn std::error::Error>> {
use crate::traits::ToXml;

crypto::sign_xml(self.to_xml()?, private_key_der)
crypto::sign_xml(self.to_string()?, private_key_der)
.map_err(|crypto_error| Box::new(crypto_error) as Box<dyn std::error::Error>)
}
}
Expand Down
75 changes: 50 additions & 25 deletions src/schema/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,55 +112,67 @@ const LOGOUT_REQUEST_NAME: &str = "saml2p:LogoutRequest";
const SESSION_INDEX_NAME: &str = "saml2p:SessionIndex";
const PROTOCOL_SCHEMA: (&str, &str) = ("xmlns:saml2p", "urn:oasis:names:tc:SAML:2.0:protocol");

impl LogoutRequest {
pub fn to_xml(&self) -> Result<String, Box<dyn std::error::Error>> {
impl TryFrom<LogoutRequest> for Event<'_> {
type Error = Box<dyn std::error::Error>;

fn try_from(value: LogoutRequest) -> Result<Self, Self::Error> {
(&value).try_into()
}
}

impl TryFrom<&LogoutRequest> for Event<'_> {
type Error = Box<dyn std::error::Error>;

fn try_from(value: &LogoutRequest) -> Result<Self, Self::Error> {
let mut write_buf = Vec::new();
let mut writer = Writer::new(Cursor::new(&mut write_buf));
writer.write_event(Event::Decl(BytesDecl::new("1.0", Some("UTF-8"), None)))?;

let mut root = BytesStart::new(LOGOUT_REQUEST_NAME);
root.push_attribute(PROTOCOL_SCHEMA);
if let Some(id) = &self.id {
if let Some(id) = &value.id {
root.push_attribute(("ID", id.as_ref()));
}
if let Some(version) = &self.version {
if let Some(version) = &value.version {
root.push_attribute(("Version", version.as_ref()));
}
if let Some(issue_instant) = &self.issue_instant {
if let Some(issue_instant) = &value.issue_instant {
root.push_attribute((
"IssueInstant",
issue_instant
.to_rfc3339_opts(SecondsFormat::Millis, true)
.as_ref(),
));
}
if let Some(destination) = &self.destination {
if let Some(destination) = &value.destination {
root.push_attribute(("Destination", destination.as_ref()));
}

writer.write_event(Event::Start(root))?;

if let Some(issuer) = &self.issuer {
if let Some(issuer) = &value.issuer {
let event: Event<'_> = issuer.try_into()?;
writer.write_event(event)?;
}
if let Some(signature) = &self.signature {
if let Some(signature) = &value.signature {
let event: Event<'_> = signature.try_into()?;
writer.write_event(event)?;
}

if let Some(session) = &self.session_index {
if let Some(session) = &value.session_index {
writer.write_event(Event::Start(BytesStart::new(SESSION_INDEX_NAME)))?;
writer.write_event(Event::Text(BytesText::new(session)))?;
writer.write_event(Event::End(BytesEnd::new(SESSION_INDEX_NAME)))?;
}
if let Some(name_id) = &self.name_id {
if let Some(name_id) = &value.name_id {
let event: Event<'_> = name_id.try_into()?;
writer.write_event(event)?;
}

writer.write_event(Event::End(BytesEnd::new(LOGOUT_REQUEST_NAME)))?;
Ok(String::from_utf8(write_buf)?)
Ok(Event::Text(BytesText::from_escaped(String::from_utf8(
write_buf,
)?)))
}
}

Expand Down Expand Up @@ -668,62 +680,75 @@ impl FromStr for LogoutResponse {

const LOGOUT_RESPONSE_NAME: &str = "saml2p:LogoutResponse";

impl LogoutResponse {
pub fn to_xml(&self) -> Result<String, Box<dyn std::error::Error>> {
impl TryFrom<LogoutResponse> for Event<'_> {
type Error = Box<dyn std::error::Error>;

fn try_from(value: LogoutResponse) -> Result<Self, Self::Error> {
(&value).try_into()
}
}

impl TryFrom<&LogoutResponse> for Event<'_> {
type Error = Box<dyn std::error::Error>;

fn try_from(value: &LogoutResponse) -> Result<Self, Self::Error> {
let mut write_buf = Vec::new();
let mut writer = Writer::new(Cursor::new(&mut write_buf));
writer.write_event(Event::Decl(BytesDecl::new("1.0", Some("UTF-8"), None)))?;

let mut root = BytesStart::new(LOGOUT_RESPONSE_NAME);
root.push_attribute(PROTOCOL_SCHEMA);
if let Some(id) = &self.id {
if let Some(id) = &value.id {
root.push_attribute(("ID", id.as_ref()));
}
if let Some(resp_to) = &self.in_response_to {
if let Some(resp_to) = &value.in_response_to {
root.push_attribute(("InResponseTo", resp_to.as_ref()));
}
if let Some(version) = &self.version {
if let Some(version) = &value.version {
root.push_attribute(("Version", version.as_ref()));
}
if let Some(issue_instant) = &self.issue_instant {
if let Some(issue_instant) = &value.issue_instant {
root.push_attribute((
"IssueInstant",
issue_instant
.to_rfc3339_opts(SecondsFormat::Millis, true)
.as_ref(),
));
}
if let Some(destination) = &self.destination {
if let Some(destination) = &value.destination {
root.push_attribute(("Destination", destination.as_ref()));
}
if let Some(consent) = &self.consent {
if let Some(consent) = &value.consent {
root.push_attribute(("Consent", consent.as_ref()));
}

writer.write_event(Event::Start(root))?;

if let Some(issuer) = &self.issuer {
if let Some(issuer) = &value.issuer {
let event: Event<'_> = issuer.try_into()?;
writer.write_event(event)?;
}
if let Some(signature) = &self.signature {
if let Some(signature) = &value.signature {
let event: Event<'_> = signature.try_into()?;
writer.write_event(event)?;
}

if let Some(status) = &self.status {
if let Some(status) = &value.status {
let event: Event<'_> = status.try_into()?;
writer.write_event(event)?;
}

writer.write_event(Event::End(BytesEnd::new(LOGOUT_RESPONSE_NAME)))?;
Ok(String::from_utf8(write_buf)?)
Ok(Event::Text(BytesText::from_escaped(String::from_utf8(
write_buf,
)?)))
}
}

#[cfg(test)]
mod test {
use super::{LogoutRequest, LogoutResponse};
use crate::traits::ToXml;

#[test]
fn test_deserialize_serialize_logout_request() {
Expand All @@ -735,7 +760,7 @@ mod test {
.parse()
.expect("failed to parse logout_request.xml");
let serialized_request = expected_request
.to_xml()
.to_string()
.expect("failed to convert request to xml");
let actual_request: LogoutRequest = serialized_request
.parse()
Expand All @@ -754,7 +779,7 @@ mod test {
.parse()
.expect("failed to parse logout_response.xml");
let serialized_response = expected_response
.to_xml()
.to_string()
.expect("failed to convert Response to xml");
let actual_response: LogoutResponse = serialized_response
.parse()
Expand Down
2 changes: 1 addition & 1 deletion src/schema/requested_authn_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ mod test {
let expected_context: RequestedAuthnContext =
quick_xml::de::from_str(xml_context).expect("failed to parse RequestedAuthnContext");
let serialized_context = expected_context
.to_xml()
.to_string()
.expect("failed to convert RequestedAuthnContext to xml");
let actual_context: RequestedAuthnContext = quick_xml::de::from_str(&serialized_context)
.expect("failed to re-parse RequestedAuthnContext");
Expand Down
8 changes: 4 additions & 4 deletions src/schema/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ impl TryFrom<&Response> for Event<'_> {

// TODO: encrypted assertion
// if let Some(assertion) = &self.encrypted_assertion {
// writer.write(assertion.to_xml()?.as_bytes())?;
// writer.write(assertion.to_string()?.as_bytes())?;
// }

writer.write_event(Event::End(BytesEnd::new(NAME)))?;
Expand All @@ -137,7 +137,7 @@ mod test {
let expected_response: Response =
response_xml.parse().expect("failed to parse response.xml");
let serialized_response = expected_response
.to_xml()
.to_string()
.expect("failed to convert response to xml");
let actual_response: Response = serialized_response
.parse()
Expand All @@ -156,7 +156,7 @@ mod test {
.parse()
.expect("failed to parse response_signed_assertion.xml");
let serialized_response = expected_response
.to_xml()
.to_string()
.expect("failed to convert response to xml");
let actual_response: Response = serialized_response
.parse()
Expand All @@ -175,7 +175,7 @@ mod test {
.parse()
.expect("failed to parse response_signed.xml");
let serialized_response = expected_response
.to_xml()
.to_string()
.expect("failed to convert response to xml");
let actual_response: Response = serialized_response
.parse()
Expand Down
4 changes: 2 additions & 2 deletions src/service_provider/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -511,7 +511,7 @@ fn parse_certificates(key_descriptor: &KeyDescriptor) -> Result<Vec<x509::X509>,

impl AuthnRequest {
pub fn post(&self, relay_state: &str) -> Result<Option<String>, Box<dyn std::error::Error>> {
let encoded = general_purpose::STANDARD.encode(self.to_xml()?.as_bytes());
let encoded = general_purpose::STANDARD.encode(self.to_string()?.as_bytes());
if let Some(dest) = &self.destination {
Ok(Some(format!(
r#"
Expand All @@ -536,7 +536,7 @@ impl AuthnRequest {
let mut compressed_buf = vec![];
{
let mut encoder = DeflateEncoder::new(&mut compressed_buf, Compression::default());
encoder.write_all(self.to_xml()?.as_bytes())?;
encoder.write_all(self.to_string()?.as_bytes())?;
}
let encoded = general_purpose::STANDARD.encode(&compressed_buf);

Expand Down
Loading

0 comments on commit e7a9b1a

Please sign in to comment.