Skip to main content

saml_rs/model/
logout.rs

1use super::extract::{
2    optional_endpoint, optional_request_id, required_str, session_indexes_from_value,
3};
4use super::identifiers::{MessageId, SessionIndex};
5use super::subject::NameId;
6use super::EndpointUrl;
7use crate::config::EntityId;
8use crate::constants::status_code;
9use crate::error::SamlError;
10use crate::raw::FlowResult;
11
12/// Subject data used to issue a front-channel Single Logout request.
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct LogoutSubject {
15    name_id: NameId,
16    session_indexes: Vec<SessionIndex>,
17}
18
19impl LogoutSubject {
20    /// Create logout subject data from a NameID and SessionIndex values.
21    pub fn new(name_id: NameId, session_indexes: Vec<SessionIndex>) -> Self {
22        Self {
23            name_id,
24            session_indexes,
25        }
26    }
27
28    /// Create logout subject data with no SessionIndex values.
29    pub fn from_name_id(name_id: NameId) -> Self {
30        Self::new(name_id, Vec::new())
31    }
32
33    /// Create logout subject data with one SessionIndex.
34    pub fn with_session_index(name_id: NameId, session_index: SessionIndex) -> Self {
35        Self::new(name_id, vec![session_index])
36    }
37
38    /// Subject NameID.
39    pub fn name_id(&self) -> &NameId {
40        &self.name_id
41    }
42
43    /// First SessionIndex to include in the logout request, when present.
44    pub fn session_index(&self) -> Option<&SessionIndex> {
45        self.session_indexes.first()
46    }
47
48    /// SessionIndex values to include in the logout request.
49    pub fn session_indexes(&self) -> &[SessionIndex] {
50        &self.session_indexes
51    }
52}
53
54/// Parsed LogoutRequest result.
55#[derive(Debug, Clone)]
56pub struct LogoutRequest {
57    id: MessageId,
58    issuer: EntityId,
59    name_id: Option<NameId>,
60    session_indexes: Vec<SessionIndex>,
61    destination: Option<EndpointUrl>,
62    raw_flow: FlowResult,
63}
64
65impl LogoutRequest {
66    /// LogoutRequest ID.
67    pub fn id(&self) -> &MessageId {
68        &self.id
69    }
70
71    /// LogoutRequest issuer.
72    pub fn issuer(&self) -> &EntityId {
73        &self.issuer
74    }
75
76    /// NameID, when present.
77    pub fn name_id(&self) -> Option<&NameId> {
78        self.name_id.as_ref()
79    }
80
81    /// Session indexes.
82    pub fn session_indexes(&self) -> &[SessionIndex] {
83        &self.session_indexes
84    }
85
86    /// Destination endpoint, when present.
87    pub fn destination(&self) -> Option<&EndpointUrl> {
88        self.destination.as_ref()
89    }
90
91    /// Raw validated flow result.
92    pub fn raw_flow(&self) -> &FlowResult {
93        &self.raw_flow
94    }
95}
96
97impl TryFrom<FlowResult> for LogoutRequest {
98    type Error = SamlError;
99
100    fn try_from(raw_flow: FlowResult) -> Result<Self, Self::Error> {
101        let id = MessageId::try_new(required_str(&raw_flow.extract, "request.id")?)?;
102        let issuer = EntityId::try_new(required_str(&raw_flow.extract, "issuer")?)?;
103        let name_id = raw_flow
104            .extract
105            .get_str("nameID")
106            .map(|value| NameId::new(value, None));
107        let session_indexes = session_indexes_from_value(raw_flow.extract.get("sessionIndex"))?;
108        let destination = optional_endpoint(&raw_flow.extract, "request.destination")?;
109        Ok(Self {
110            id,
111            issuer,
112            name_id,
113            session_indexes,
114            destination,
115            raw_flow,
116        })
117    }
118}
119
120/// Parsed LogoutResponse result.
121#[derive(Debug, Clone)]
122pub struct LogoutResponse {
123    id: MessageId,
124    issuer: EntityId,
125    in_response_to: Option<MessageId>,
126    destination: Option<EndpointUrl>,
127    raw_flow: FlowResult,
128}
129
130impl LogoutResponse {
131    /// LogoutResponse ID.
132    pub fn id(&self) -> &MessageId {
133        &self.id
134    }
135
136    /// LogoutResponse issuer.
137    pub fn issuer(&self) -> &EntityId {
138        &self.issuer
139    }
140
141    /// InResponseTo, when present.
142    pub fn in_response_to(&self) -> Option<&MessageId> {
143        self.in_response_to.as_ref()
144    }
145
146    /// Destination endpoint, when present.
147    pub fn destination(&self) -> Option<&EndpointUrl> {
148        self.destination.as_ref()
149    }
150
151    /// Raw validated flow result.
152    pub fn raw_flow(&self) -> &FlowResult {
153        &self.raw_flow
154    }
155}
156
157impl TryFrom<FlowResult> for LogoutResponse {
158    type Error = SamlError;
159
160    fn try_from(raw_flow: FlowResult) -> Result<Self, Self::Error> {
161        let id = MessageId::try_new(required_str(&raw_flow.extract, "response.id")?)?;
162        let issuer = EntityId::try_new(required_str(&raw_flow.extract, "issuer")?)?;
163        let in_response_to = optional_request_id(&raw_flow.extract, "response.inResponseTo")?;
164        let destination = optional_endpoint(&raw_flow.extract, "response.destination")?;
165        Ok(Self {
166            id,
167            issuer,
168            in_response_to,
169            destination,
170            raw_flow,
171        })
172    }
173}
174
175/// Marker result for completed logout flows.
176#[derive(Debug, Clone)]
177pub struct LogoutCompleted {
178    peer_entity_id: EntityId,
179    response: Option<LogoutResponse>,
180}
181
182impl PartialEq for LogoutCompleted {
183    fn eq(&self, other: &Self) -> bool {
184        self.peer_entity_id == other.peer_entity_id
185    }
186}
187
188impl Eq for LogoutCompleted {}
189
190impl LogoutCompleted {
191    /// Create a completed logout marker.
192    pub fn new(peer_entity_id: EntityId) -> Self {
193        Self {
194            peer_entity_id,
195            response: None,
196        }
197    }
198
199    /// Create a completed logout marker from a validated LogoutResponse.
200    pub fn from_response(peer_entity_id: EntityId, response: LogoutResponse) -> Self {
201        Self {
202            peer_entity_id,
203            response: Some(response),
204        }
205    }
206
207    /// Peer entity ID involved in the completed logout.
208    pub fn peer_entity_id(&self) -> &EntityId {
209        &self.peer_entity_id
210    }
211
212    /// Validated LogoutResponse, when this completion came from a front-channel response.
213    pub fn response(&self) -> Option<&LogoutResponse> {
214        self.response.as_ref()
215    }
216
217    /// Successful SAML status for the completed logout response.
218    pub fn status(&self) -> Option<&str> {
219        self.response.as_ref().map(|_| status_code::SUCCESS)
220    }
221
222    /// Raw validated flow result for the LogoutResponse.
223    pub fn raw_flow(&self) -> Option<&FlowResult> {
224        self.response.as_ref().map(LogoutResponse::raw_flow)
225    }
226}