Skip to main content

wae_authentication/saml/
metadata.rs

1//! SAML 元数据定义
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5
6use super::config::{NameIdFormat, SamlBinding};
7
8/// SAML 实体描述符
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct EntityDescriptor {
11    /// 实体 ID
12    #[serde(rename = "@entityID")]
13    pub entity_id: String,
14
15    /// 有效期
16    #[serde(rename = "@validUntil")]
17    pub valid_until: Option<DateTime<Utc>>,
18
19    /// 缓存时长
20    #[serde(rename = "@cacheDuration")]
21    pub cache_duration: Option<String>,
22
23    /// SP 描述符
24    #[serde(rename = "md:SPSSODescriptor")]
25    pub sp_sso_descriptor: Option<SPSSODescriptor>,
26
27    /// IdP 描述符
28    #[serde(rename = "md:IDPSSODescriptor")]
29    pub idp_sso_descriptor: Option<IDPSSODescriptor>,
30}
31
32/// SP SSO 描述符
33#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct SPSSODescriptor {
35    /// 是否需要签名断言
36    #[serde(rename = "@WantAssertionsSigned")]
37    pub want_assertions_signed: Option<bool>,
38
39    /// 是否需要签名响应
40    #[serde(rename = "@AuthnRequestsSigned")]
41    pub authn_requests_signed: Option<bool>,
42
43    /// 协议支持枚举
44    #[serde(rename = "@protocolSupportEnumeration")]
45    pub protocol_support_enumeration: String,
46
47    /// 名称 ID 格式
48    #[serde(rename = "md:NameIDFormat")]
49    pub name_id_format: Vec<String>,
50
51    /// 断言消费者服务
52    #[serde(rename = "md:AssertionConsumerService")]
53    pub assertion_consumer_service: Vec<AssertionConsumerService>,
54
55    /// 单点登出服务
56    #[serde(rename = "md:SingleLogoutService")]
57    pub single_logout_service: Option<Vec<SingleLogoutService>>,
58
59    /// 签名密钥描述符
60    #[serde(rename = "md:KeyDescriptor")]
61    pub key_descriptor: Option<Vec<KeyDescriptor>>,
62}
63
64/// IdP SSO 描述符
65#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct IDPSSODescriptor {
67    /// 是否需要签名请求
68    #[serde(rename = "@WantAuthnRequestsSigned")]
69    pub want_authn_requests_signed: Option<bool>,
70
71    /// 协议支持枚举
72    #[serde(rename = "@protocolSupportEnumeration")]
73    pub protocol_support_enumeration: String,
74
75    /// 名称 ID 格式
76    #[serde(rename = "md:NameIDFormat")]
77    pub name_id_format: Vec<String>,
78
79    /// 单点登录服务
80    #[serde(rename = "md:SingleSignOnService")]
81    pub single_sign_on_service: Vec<SingleSignOnService>,
82
83    /// 单点登出服务
84    #[serde(rename = "md:SingleLogoutService")]
85    pub single_logout_service: Option<Vec<SingleLogoutService>>,
86
87    /// 签名密钥描述符
88    #[serde(rename = "md:KeyDescriptor")]
89    pub key_descriptor: Option<Vec<KeyDescriptor>>,
90}
91
92/// 断言消费者服务
93#[derive(Debug, Clone, Serialize, Deserialize)]
94pub struct AssertionConsumerService {
95    /// 索引
96    #[serde(rename = "@index")]
97    pub index: u32,
98
99    /// 是否默认
100    #[serde(rename = "@isDefault")]
101    pub is_default: Option<bool>,
102
103    /// 绑定
104    #[serde(rename = "@Binding")]
105    pub binding: String,
106
107    /// 位置
108    #[serde(rename = "@Location")]
109    pub location: String,
110}
111
112/// 单点登录服务
113#[derive(Debug, Clone, Serialize, Deserialize)]
114pub struct SingleSignOnService {
115    /// 绑定
116    #[serde(rename = "@Binding")]
117    pub binding: String,
118
119    /// 位置
120    #[serde(rename = "@Location")]
121    pub location: String,
122}
123
124/// 单点登出服务
125#[derive(Debug, Clone, Serialize, Deserialize)]
126pub struct SingleLogoutService {
127    /// 绑定
128    #[serde(rename = "@Binding")]
129    pub binding: String,
130
131    /// 位置
132    #[serde(rename = "@Location")]
133    pub location: String,
134
135    /// 响应位置
136    #[serde(rename = "@ResponseLocation")]
137    pub response_location: Option<String>,
138}
139
140/// 密钥描述符
141#[derive(Debug, Clone, Serialize, Deserialize)]
142pub struct KeyDescriptor {
143    /// 用途
144    #[serde(rename = "@use")]
145    pub use_: Option<String>,
146
147    /// 证书
148    #[serde(rename = "ds:KeyInfo")]
149    pub key_info: KeyInfo,
150}
151
152/// 密钥信息
153#[derive(Debug, Clone, Serialize, Deserialize)]
154pub struct KeyInfo {
155    /// X509 数据
156    #[serde(rename = "ds:X509Data")]
157    pub x509_data: X509Data,
158}
159
160/// X509 数据
161#[derive(Debug, Clone, Serialize, Deserialize)]
162pub struct X509Data {
163    /// 证书
164    #[serde(rename = "ds:X509Certificate")]
165    pub certificate: String,
166}
167
168/// SP 元数据构建器
169#[derive(Debug, Clone)]
170pub struct SpMetadataBuilder {
171    entity_id: String,
172    acs_url: String,
173    slo_url: Option<String>,
174    name_id_formats: Vec<NameIdFormat>,
175    want_assertions_signed: bool,
176    authn_requests_signed: bool,
177    certificate: Option<String>,
178}
179
180impl SpMetadataBuilder {
181    /// 创建新的 SP 元数据构建器
182    pub fn new(entity_id: impl Into<String>, acs_url: impl Into<String>) -> Self {
183        Self {
184            entity_id: entity_id.into(),
185            acs_url: acs_url.into(),
186            slo_url: None,
187            name_id_formats: vec![NameIdFormat::EmailAddress],
188            want_assertions_signed: true,
189            authn_requests_signed: false,
190            certificate: None,
191        }
192    }
193
194    /// 设置 SLO URL
195    pub fn with_slo_url(mut self, url: impl Into<String>) -> Self {
196        self.slo_url = Some(url.into());
197        self
198    }
199
200    /// 设置名称 ID 格式
201    pub fn with_name_id_formats(mut self, formats: Vec<NameIdFormat>) -> Self {
202        self.name_id_formats = formats;
203        self
204    }
205
206    /// 设置是否需要签名断言
207    pub fn with_want_assertions_signed(mut self, want: bool) -> Self {
208        self.want_assertions_signed = want;
209        self
210    }
211
212    /// 设置是否签名认证请求
213    pub fn with_authn_requests_signed(mut self, signed: bool) -> Self {
214        self.authn_requests_signed = signed;
215        self
216    }
217
218    /// 设置证书
219    pub fn with_certificate(mut self, cert: impl Into<String>) -> Self {
220        self.certificate = Some(cert.into());
221        self
222    }
223
224    /// 构建元数据
225    pub fn build(self) -> EntityDescriptor {
226        let acs = AssertionConsumerService {
227            index: 0,
228            is_default: Some(true),
229            binding: SamlBinding::HttpPost.uri().to_string(),
230            location: self.acs_url,
231        };
232
233        let slo_services = self.slo_url.map(|url| {
234            vec![SingleLogoutService {
235                binding: SamlBinding::HttpRedirect.uri().to_string(),
236                location: url,
237                response_location: None,
238            }]
239        });
240
241        let key_descriptors = self.certificate.map(|cert| {
242            vec![KeyDescriptor {
243                use_: Some("signing".to_string()),
244                key_info: KeyInfo { x509_data: X509Data { certificate: cert } },
245            }]
246        });
247
248        EntityDescriptor {
249            entity_id: self.entity_id,
250            valid_until: None,
251            cache_duration: None,
252            sp_sso_descriptor: Some(SPSSODescriptor {
253                want_assertions_signed: Some(self.want_assertions_signed),
254                authn_requests_signed: Some(self.authn_requests_signed),
255                protocol_support_enumeration: "urn:oasis:names:tc:SAML:2.0:protocol".to_string(),
256                name_id_format: self.name_id_formats.iter().map(|f| f.uri().to_string()).collect(),
257                assertion_consumer_service: vec![acs],
258                single_logout_service: slo_services,
259                key_descriptor: key_descriptors,
260            }),
261            idp_sso_descriptor: None,
262        }
263    }
264}