Skip to main content

saml_rs/model/
subject.rs

1use crate::config::NameIdFormat;
2
3/// NameID value, format, and optional qualifier attributes.
4///
5/// [`Self::new`] leaves the qualifiers unset. Response generation copies the
6/// value and format only.
7#[derive(Debug, Clone, PartialEq, Eq)]
8pub struct NameId {
9    value: String,
10    format: Option<NameIdFormat>,
11    name_qualifier: Option<String>,
12    sp_name_qualifier: Option<String>,
13    sp_provided_id: Option<String>,
14}
15
16impl NameId {
17    /// Create a NameID value.
18    pub fn new(value: impl Into<String>, format: Option<NameIdFormat>) -> Self {
19        Self::with_qualifiers(value, format, None, None, None)
20    }
21
22    /// Create a NameID with qualifier attributes.
23    ///
24    /// `None` omits the attribute. `Some("")` is an empty value.
25    pub fn with_qualifiers(
26        value: impl Into<String>,
27        format: Option<NameIdFormat>,
28        name_qualifier: Option<String>,
29        sp_name_qualifier: Option<String>,
30        sp_provided_id: Option<String>,
31    ) -> Self {
32        Self {
33            value: value.into(),
34            format,
35            name_qualifier,
36            sp_name_qualifier,
37            sp_provided_id,
38        }
39    }
40
41    /// Borrow the NameID text.
42    pub fn value(&self) -> &str {
43        &self.value
44    }
45
46    /// NameID format, when extracted.
47    pub fn format(&self) -> Option<&NameIdFormat> {
48        self.format.as_ref()
49    }
50
51    /// `NameQualifier`, when present.
52    pub fn name_qualifier(&self) -> Option<&str> {
53        self.name_qualifier.as_deref()
54    }
55
56    /// `SPNameQualifier`, when present.
57    pub fn sp_name_qualifier(&self) -> Option<&str> {
58        self.sp_name_qualifier.as_deref()
59    }
60
61    /// `SPProvidedID`, when present.
62    pub fn sp_provided_id(&self) -> Option<&str> {
63        self.sp_provided_id.as_deref()
64    }
65}
66
67/// AuthnRequest NameIDPolicy.
68#[derive(Debug, Clone, PartialEq, Eq)]
69pub struct NameIdPolicy {
70    format: Option<NameIdFormat>,
71    creation_request: NameIdCreationRequest,
72}
73
74/// AuthnRequest NameID creation request.
75#[derive(Debug, Clone, Copy, PartialEq, Eq)]
76pub enum NameIdCreationRequest {
77    /// The request did not specify whether the IdP may create a new identifier.
78    Unspecified,
79    /// The IdP may create a new identifier.
80    AllowCreate,
81    /// The IdP must not create a new identifier.
82    DoNotAllowCreate,
83}
84
85impl NameIdPolicy {
86    /// Create a NameIDPolicy model.
87    pub fn new(format: Option<NameIdFormat>, creation_request: NameIdCreationRequest) -> Self {
88        Self {
89            format,
90            creation_request,
91        }
92    }
93
94    /// Create a NameIDPolicy without an AllowCreate preference.
95    pub fn unspecified(format: Option<NameIdFormat>) -> Self {
96        Self::new(format, NameIdCreationRequest::Unspecified)
97    }
98
99    /// Create a NameIDPolicy allowing the IdP to create a new identifier.
100    pub fn allow_creation(format: Option<NameIdFormat>) -> Self {
101        Self::new(format, NameIdCreationRequest::AllowCreate)
102    }
103
104    /// Create a NameIDPolicy forbidding the IdP from creating a new identifier.
105    pub fn disallow_creation(format: Option<NameIdFormat>) -> Self {
106        Self::new(format, NameIdCreationRequest::DoNotAllowCreate)
107    }
108
109    pub(crate) fn from_parsed(
110        format: Option<NameIdFormat>,
111        allow_create: Option<bool>,
112    ) -> Option<Self> {
113        if format.is_none() && allow_create.is_none() {
114            return None;
115        }
116        Some(match allow_create {
117            Some(true) => Self::allow_creation(format),
118            Some(false) => Self::disallow_creation(format),
119            None => Self::unspecified(format),
120        })
121    }
122
123    /// Requested NameID format.
124    pub fn format(&self) -> Option<&NameIdFormat> {
125        self.format.as_ref()
126    }
127
128    /// NameID creation request.
129    pub fn creation_request(&self) -> NameIdCreationRequest {
130        self.creation_request
131    }
132
133    /// Whether the IdP may create a new identifier.
134    pub fn allow_create(&self) -> Option<bool> {
135        match self.creation_request {
136            NameIdCreationRequest::Unspecified => None,
137            NameIdCreationRequest::AllowCreate => Some(true),
138            NameIdCreationRequest::DoNotAllowCreate => Some(false),
139        }
140    }
141}
142
143/// SubjectConfirmation captured from the validated flow result.
144#[derive(Debug, Clone, PartialEq, Eq)]
145pub struct SubjectConfirmation {
146    raw_xml: String,
147}
148
149impl SubjectConfirmation {
150    /// Create a subject confirmation from extractor context XML.
151    pub fn from_raw_xml(raw_xml: impl Into<String>) -> Self {
152        Self {
153            raw_xml: raw_xml.into(),
154        }
155    }
156
157    /// Borrow the raw confirmation XML captured by the extractor.
158    pub fn raw_xml(&self) -> &str {
159        &self.raw_xml
160    }
161}
162
163/// SAML subject.
164#[derive(Debug, Clone, PartialEq, Eq)]
165pub struct Subject {
166    name_id: NameId,
167    confirmations: Vec<SubjectConfirmation>,
168}
169
170impl Subject {
171    /// Create a subject.
172    pub fn new(name_id: NameId, confirmations: Vec<SubjectConfirmation>) -> Self {
173        Self {
174            name_id,
175            confirmations,
176        }
177    }
178
179    /// Subject NameID.
180    pub fn name_id(&self) -> &NameId {
181        &self.name_id
182    }
183
184    /// Subject confirmations.
185    pub fn confirmations(&self) -> &[SubjectConfirmation] {
186        &self.confirmations
187    }
188}
189
190/// Subject requested by an `<AuthnRequest>` (Core §3.4.1).
191///
192/// The identity provider decides whether an assertion subject strongly matches
193/// (Core §3.4.1.4, §3.3.4, Errata 05 E75). This library does not compare them.
194#[derive(Debug, Clone, PartialEq, Eq)]
195pub struct RequestedSubject {
196    identifier: RequestedSubjectIdentifier,
197    confirmations: Vec<SubjectConfirmation>,
198}
199
200/// Identifier inside a requested [`RequestedSubject`] (Core §3.4.1).
201#[derive(Debug, Clone, PartialEq, Eq)]
202pub enum RequestedSubjectIdentifier {
203    /// No `<BaseID>`, `<NameID>`, or `<EncryptedID>`. The presenter is the subject.
204    NoIdentifier,
205    /// `<NameID>` content and attributes.
206    NameId(NameId),
207    /// `<BaseID>`, not decoded.
208    BaseId,
209    /// `<EncryptedID>`, not decrypted.
210    EncryptedId,
211}
212
213impl RequestedSubject {
214    pub(crate) fn new(
215        identifier: RequestedSubjectIdentifier,
216        confirmations: Vec<SubjectConfirmation>,
217    ) -> Self {
218        Self {
219            identifier,
220            confirmations,
221        }
222    }
223
224    /// Requested identifier.
225    pub fn identifier(&self) -> &RequestedSubjectIdentifier {
226        &self.identifier
227    }
228
229    /// `<NameID>` when the identifier is [`RequestedSubjectIdentifier::NameId`].
230    pub fn name_id(&self) -> Option<&NameId> {
231        match &self.identifier {
232            RequestedSubjectIdentifier::NameId(name_id) => Some(name_id),
233            RequestedSubjectIdentifier::NoIdentifier
234            | RequestedSubjectIdentifier::BaseId
235            | RequestedSubjectIdentifier::EncryptedId => None,
236        }
237    }
238
239    /// `<SubjectConfirmation>` elements, in document order.
240    ///
241    /// An empty slice means the presenter is the only attesting entity
242    /// (Core §3.4.1).
243    pub fn confirmations(&self) -> &[SubjectConfirmation] {
244        &self.confirmations
245    }
246}