Skip to main content

flp_saml2/sp/
metadata.rs

1// This library provides SAML2 implementation in Rust
2// Copyright (C) 2026  Hakukaze Shikano
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program.  If not, see <https://www.gnu.org/licenses/>.
16
17use chrono::{Duration, SecondsFormat, Utc};
18use quick_xml::se::to_string as to_xml_string;
19use serde::Serialize;
20
21use crate::{get_cert_data, sp::ServiceProvider};
22
23#[derive(Serialize)]
24pub struct AssertionConsumerService {
25    #[serde(rename = "Binding")]
26    binding: String,
27    #[serde(rename = "Location")]
28    location: String,
29    index: String,
30}
31
32#[derive(Serialize)]
33pub struct SingleLogoutService {
34    #[serde(rename = "Binding")]
35    binding: String,
36    #[serde(rename = "Location")]
37    location: String,
38}
39
40#[derive(Serialize)]
41pub struct X509Certificate {
42    #[serde(rename = "$value")]
43    value: String,
44}
45
46#[derive(Serialize)]
47pub struct X509Data {
48    #[serde(rename = "ds:X509Certificate")]
49    x509_certificate: X509Certificate,
50}
51
52#[derive(Serialize)]
53pub struct KeyInfo {
54    #[serde(rename = "xmlns:ds")]
55    ds: String,
56    #[serde(rename = "ds:X509Data")]
57    x509_data: X509Data,
58}
59
60#[derive(Serialize)]
61pub struct KeyDescriptor {
62    #[serde(rename = "use")]
63    us: String,
64    #[serde(rename = "ds:KeyInfo")]
65    key_info: KeyInfo,
66}
67
68#[derive(Serialize)]
69pub struct SPSSODescriptor {
70    #[serde(rename = "protocolSupportEnumeration")]
71    protocol_support_enumeration: String,
72    #[serde(rename = "md:KeyDescriptor")]
73    key_descriptor: Vec<KeyDescriptor>,
74    #[serde(rename = "md:SingleLogoutService")]
75    single_logout_service: SingleLogoutService,
76    #[serde(rename = "md:AssertionConsumerService")]
77    assertion_consumer_service: AssertionConsumerService,
78}
79
80#[derive(Serialize)]
81#[serde(rename = "md:EntityDescriptor")]
82pub struct EntityDescriptor {
83    #[serde(rename = "xmlns:md")]
84    md: String,
85    #[serde(rename = "xmlns:ds")]
86    ds: String,
87    #[serde(rename = "entityID")]
88    entity_id: String,
89    #[serde(rename = "validUntil")]
90    valid_util: String,
91    #[serde(rename = "md:SPSSODescriptor")]
92    sp_sso_descriptor: SPSSODescriptor,
93}
94
95impl ServiceProvider {
96    pub fn metadata(&self) -> String {
97        let now = Utc::now();
98        let tomorrow = now + Duration::days(1);
99
100        let cert_data = get_cert_data(&self.certificate);
101
102        to_xml_string(&EntityDescriptor {
103            md: "urn:oasis:names:tc:SAML:2.0:metadata".into(),
104            ds: "http://www.w3.org/2000/09/xmldsig#".into(),
105            entity_id: self.entity_id.to_string(),
106            valid_util: tomorrow.to_rfc3339_opts(SecondsFormat::Millis, true),
107            sp_sso_descriptor: SPSSODescriptor {
108                protocol_support_enumeration:
109                    "urn:oasis:names:tc:SAML:1.1:protocol urn:oasis:names:tc:SAML:2.0:protocol"
110                        .into(),
111                key_descriptor: vec![
112                    KeyDescriptor {
113                        us: "signing".into(),
114                        key_info: KeyInfo {
115                            ds: "http://www.w3.org/2000/09/xmldsig#".into(),
116                            x509_data: X509Data {
117                                x509_certificate: X509Certificate {
118                                    value: cert_data.clone(),
119                                },
120                            },
121                        },
122                    },
123                    KeyDescriptor {
124                        us: "encryption".into(),
125                        key_info: KeyInfo {
126                            ds: "http://www.w3.org/2000/09/xmldsig#".into(),
127                            x509_data: X509Data {
128                                x509_certificate: X509Certificate { value: cert_data },
129                            },
130                        },
131                    },
132                ],
133                single_logout_service: SingleLogoutService {
134                    binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect".into(),
135                    location: self.assert_logout.to_string(),
136                },
137                assertion_consumer_service: AssertionConsumerService {
138                    binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST".into(),
139                    location: self.assert_login.to_string(),
140                    index: "0".into(),
141                },
142            },
143        })
144        .unwrap()
145    }
146}