flp_saml2/sp/
authn_redirect.rs1use base64::prelude::*;
18use chrono::{SecondsFormat, Utc};
19use flate2::{Compression, read::DeflateEncoder};
20use openssl::{hash::MessageDigest, sign::Signer};
21use quick_xml::se::to_string as to_xml_string;
22use rand::{Rng, rng};
23use serde::Serialize;
24use std::io::Read;
25use url::Url;
26
27use crate::{error::Result, idp::IdentityProvider, sp::ServiceProvider};
28
29#[derive(Clone, Debug, Serialize)]
30pub struct AuthnContextClassRef {
31 #[serde(rename = "$value")]
32 value: String,
33}
34
35#[derive(Clone, Debug, Serialize)]
36pub struct RequestedAuthnContext {
37 #[serde(rename = "Comparison")]
38 comparison: String,
39 #[serde(rename = "saml:AuthnContextClassRef")]
40 authn_context_class_ref: AuthnContextClassRef,
41}
42
43#[derive(Serialize)]
44pub struct NameIDPolicy {
45 #[serde(rename = "Format")]
46 format: String,
47 #[serde(rename = "AllowCreate")]
48 allow_create: String,
49}
50
51#[derive(Serialize)]
52pub struct Issuer {
53 #[serde(rename = "$value")]
54 value: String,
55}
56
57#[derive(Serialize)]
58#[serde(rename = "samlp:AuthnRequest")]
59pub struct AuthnRequest {
60 #[serde(rename = "xmlns:samlp")]
61 samlp: String,
62 #[serde(rename = "xmlns:saml")]
63 saml: String,
64 #[serde(rename = "ID")]
65 id: String,
66 #[serde(rename = "Version")]
67 version: String,
68 #[serde(rename = "IssueInstant")]
69 issue_instant: String,
70 #[serde(rename = "Destination")]
71 destination: String,
72 #[serde(rename = "ProtocolBinding")]
73 protocol_binding: String,
74 #[serde(rename = "AssertionConsumerServiceURL")]
75 assertion_consumer_service_url: String,
76 #[serde(rename = "saml:Issuer")]
77 issuer: Issuer,
78 #[serde(rename = "samlp:NameIDPolicy")]
79 name_id_policy: NameIDPolicy,
80 #[serde(
81 rename = "samlp:RequestedAuthnContext",
82 skip_serializing_if = "Option::is_none"
83 )]
84 requested_authn_context: Option<RequestedAuthnContext>,
85}
86
87impl ServiceProvider {
88 pub fn authn_redirect(&self, idp: &IdentityProvider) -> Result<Url> {
89 let random_bytes = rng().random::<[u8; 21]>();
90 let now = Utc::now();
91
92 let authn = to_xml_string(&AuthnRequest {
93 samlp: "urn:oasis:names:tc:SAML:2.0:protocol".into(),
94 saml: "urn:oasis:names:tc:SAML:2.0:assertion".into(),
95 id: format!("_{}", hex::encode(random_bytes)),
96 version: "2.0".into(),
97 issue_instant: now.to_rfc3339_opts(SecondsFormat::Millis, true),
98 destination: idp.login.to_string(),
99 protocol_binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST".into(),
100 assertion_consumer_service_url: self.assert_login.to_string(),
101 issuer: Issuer {
102 value: self.entity_id.to_string(),
103 },
104 name_id_policy: NameIDPolicy {
105 format: self.name_id_format.clone().unwrap_or_else(|| {
106 "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified".into()
107 }),
108 allow_create: "true".into(),
109 },
110 requested_authn_context: self.authn_context.clone(),
111 })
112 .unwrap();
113
114 let mut deflater = DeflateEncoder::new(authn.as_bytes(), Compression::fast());
115 let mut deflated = Vec::new();
116 deflater.read_to_end(&mut deflated)?;
117 let saml_request = BASE64_STANDARD.encode(deflated);
118 let mut url = idp.login.clone();
119 url.query_pairs_mut()
120 .clear()
121 .append_pair("SAMLRequest", &saml_request);
122 if let Some(relay_state) = self.relay_state.as_ref() {
123 url.query_pairs_mut().append_pair("RelayState", relay_state);
124 }
125 url.query_pairs_mut().append_pair(
126 "SigAlg",
127 "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
128 );
129 let query_all = url.query().unwrap();
130 let mut signer = Signer::new(MessageDigest::sha256(), &self.private_key)?;
131 signer.update(query_all.as_bytes())?;
132 url.query_pairs_mut()
133 .append_pair("Signature", &BASE64_STANDARD.encode(signer.sign_to_vec()?));
134 Ok(url)
135 }
136}