mas_storage/compat/
session.rs

1// Copyright 2024 New Vector Ltd.
2// Copyright 2023, 2024 The Matrix.org Foundation C.I.C.
3//
4// SPDX-License-Identifier: AGPL-3.0-only
5// Please see LICENSE in the repository root for full details.
6
7use std::net::IpAddr;
8
9use async_trait::async_trait;
10use chrono::{DateTime, Utc};
11use mas_data_model::{BrowserSession, CompatSession, CompatSsoLogin, Device, User, UserAgent};
12use rand_core::RngCore;
13use ulid::Ulid;
14
15use crate::{Clock, Page, Pagination, repository_impl};
16
17#[derive(Clone, Copy, Debug, PartialEq, Eq)]
18pub enum CompatSessionState {
19    Active,
20    Finished,
21}
22
23impl CompatSessionState {
24    /// Returns [`true`] if we're looking for active sessions
25    #[must_use]
26    pub fn is_active(self) -> bool {
27        matches!(self, Self::Active)
28    }
29
30    /// Returns [`true`] if we're looking for finished sessions
31    #[must_use]
32    pub fn is_finished(self) -> bool {
33        matches!(self, Self::Finished)
34    }
35}
36
37#[derive(Clone, Copy, Debug, PartialEq, Eq)]
38pub enum CompatSessionType {
39    SsoLogin,
40    Unknown,
41}
42
43impl CompatSessionType {
44    /// Returns [`true`] if we're looking for SSO logins
45    #[must_use]
46    pub fn is_sso_login(self) -> bool {
47        matches!(self, Self::SsoLogin)
48    }
49
50    /// Returns [`true`] if we're looking for unknown sessions
51    #[must_use]
52    pub fn is_unknown(self) -> bool {
53        matches!(self, Self::Unknown)
54    }
55}
56
57/// Filter parameters for listing compatibility sessions
58#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
59pub struct CompatSessionFilter<'a> {
60    user: Option<&'a User>,
61    browser_session: Option<&'a BrowserSession>,
62    state: Option<CompatSessionState>,
63    auth_type: Option<CompatSessionType>,
64    device: Option<&'a Device>,
65    last_active_before: Option<DateTime<Utc>>,
66    last_active_after: Option<DateTime<Utc>>,
67}
68
69impl<'a> CompatSessionFilter<'a> {
70    /// Create a new [`CompatSessionFilter`] with default values
71    #[must_use]
72    pub fn new() -> Self {
73        Self::default()
74    }
75
76    /// Set the user who owns the compatibility sessions
77    #[must_use]
78    pub fn for_user(mut self, user: &'a User) -> Self {
79        self.user = Some(user);
80        self
81    }
82
83    /// Get the user filter
84    #[must_use]
85    pub fn user(&self) -> Option<&'a User> {
86        self.user
87    }
88
89    /// Set the device filter
90    #[must_use]
91    pub fn for_device(mut self, device: &'a Device) -> Self {
92        self.device = Some(device);
93        self
94    }
95
96    /// Get the device filter
97    #[must_use]
98    pub fn device(&self) -> Option<&'a Device> {
99        self.device
100    }
101
102    /// Set the browser session filter
103    #[must_use]
104    pub fn for_browser_session(mut self, browser_session: &'a BrowserSession) -> Self {
105        self.browser_session = Some(browser_session);
106        self
107    }
108
109    /// Get the browser session filter
110    #[must_use]
111    pub fn browser_session(&self) -> Option<&'a BrowserSession> {
112        self.browser_session
113    }
114
115    /// Only return sessions with a last active time before the given time
116    #[must_use]
117    pub fn with_last_active_before(mut self, last_active_before: DateTime<Utc>) -> Self {
118        self.last_active_before = Some(last_active_before);
119        self
120    }
121
122    /// Only return sessions with a last active time after the given time
123    #[must_use]
124    pub fn with_last_active_after(mut self, last_active_after: DateTime<Utc>) -> Self {
125        self.last_active_after = Some(last_active_after);
126        self
127    }
128
129    /// Get the last active before filter
130    ///
131    /// Returns [`None`] if no client filter was set
132    #[must_use]
133    pub fn last_active_before(&self) -> Option<DateTime<Utc>> {
134        self.last_active_before
135    }
136
137    /// Get the last active after filter
138    ///
139    /// Returns [`None`] if no client filter was set
140    #[must_use]
141    pub fn last_active_after(&self) -> Option<DateTime<Utc>> {
142        self.last_active_after
143    }
144
145    /// Only return active compatibility sessions
146    #[must_use]
147    pub fn active_only(mut self) -> Self {
148        self.state = Some(CompatSessionState::Active);
149        self
150    }
151
152    /// Only return finished compatibility sessions
153    #[must_use]
154    pub fn finished_only(mut self) -> Self {
155        self.state = Some(CompatSessionState::Finished);
156        self
157    }
158
159    /// Get the state filter
160    #[must_use]
161    pub fn state(&self) -> Option<CompatSessionState> {
162        self.state
163    }
164
165    /// Only return SSO login compatibility sessions
166    #[must_use]
167    pub fn sso_login_only(mut self) -> Self {
168        self.auth_type = Some(CompatSessionType::SsoLogin);
169        self
170    }
171
172    /// Only return unknown compatibility sessions
173    #[must_use]
174    pub fn unknown_only(mut self) -> Self {
175        self.auth_type = Some(CompatSessionType::Unknown);
176        self
177    }
178
179    /// Get the auth type filter
180    #[must_use]
181    pub fn auth_type(&self) -> Option<CompatSessionType> {
182        self.auth_type
183    }
184}
185
186/// A [`CompatSessionRepository`] helps interacting with
187/// [`CompatSession`] saved in the storage backend
188#[async_trait]
189pub trait CompatSessionRepository: Send + Sync {
190    /// The error type returned by the repository
191    type Error;
192
193    /// Lookup a compat session by its ID
194    ///
195    /// Returns the compat session if it exists, `None` otherwise
196    ///
197    /// # Parameters
198    ///
199    /// * `id`: The ID of the compat session to lookup
200    ///
201    /// # Errors
202    ///
203    /// Returns [`Self::Error`] if the underlying repository fails
204    async fn lookup(&mut self, id: Ulid) -> Result<Option<CompatSession>, Self::Error>;
205
206    /// Start a new compat session
207    ///
208    /// Returns the newly created compat session
209    ///
210    /// # Parameters
211    ///
212    /// * `rng`: The random number generator to use
213    /// * `clock`: The clock used to generate timestamps
214    /// * `user`: The user to create the compat session for
215    /// * `device`: The device ID of this session
216    /// * `browser_session`: The browser session which created this session
217    /// * `is_synapse_admin`: Whether the session is a synapse admin session
218    ///
219    /// # Errors
220    ///
221    /// Returns [`Self::Error`] if the underlying repository fails
222    async fn add(
223        &mut self,
224        rng: &mut (dyn RngCore + Send),
225        clock: &dyn Clock,
226        user: &User,
227        device: Device,
228        browser_session: Option<&BrowserSession>,
229        is_synapse_admin: bool,
230    ) -> Result<CompatSession, Self::Error>;
231
232    /// End a compat session
233    ///
234    /// Returns the ended compat session
235    ///
236    /// # Parameters
237    ///
238    /// * `clock`: The clock used to generate timestamps
239    /// * `compat_session`: The compat session to end
240    ///
241    /// # Errors
242    ///
243    /// Returns [`Self::Error`] if the underlying repository fails
244    async fn finish(
245        &mut self,
246        clock: &dyn Clock,
247        compat_session: CompatSession,
248    ) -> Result<CompatSession, Self::Error>;
249
250    /// Mark all the [`CompatSession`] matching the given filter as finished
251    ///
252    /// Returns the number of sessions affected
253    ///
254    /// # Parameters
255    ///
256    /// * `clock`: The clock used to generate timestamps
257    /// * `filter`: The filter to apply
258    ///
259    /// # Errors
260    ///
261    /// Returns [`Self::Error`] if the underlying repository fails
262    async fn finish_bulk(
263        &mut self,
264        clock: &dyn Clock,
265        filter: CompatSessionFilter<'_>,
266    ) -> Result<usize, Self::Error>;
267
268    /// List [`CompatSession`] with the given filter and pagination
269    ///
270    /// Returns a page of compat sessions, with the associated SSO logins if any
271    ///
272    /// # Parameters
273    ///
274    /// * `filter`: The filter to apply
275    /// * `pagination`: The pagination parameters
276    ///
277    /// # Errors
278    ///
279    /// Returns [`Self::Error`] if the underlying repository fails
280    async fn list(
281        &mut self,
282        filter: CompatSessionFilter<'_>,
283        pagination: Pagination,
284    ) -> Result<Page<(CompatSession, Option<CompatSsoLogin>)>, Self::Error>;
285
286    /// Count the number of [`CompatSession`] with the given filter
287    ///
288    /// # Parameters
289    ///
290    /// * `filter`: The filter to apply
291    ///
292    /// # Errors
293    ///
294    /// Returns [`Self::Error`] if the underlying repository fails
295    async fn count(&mut self, filter: CompatSessionFilter<'_>) -> Result<usize, Self::Error>;
296
297    /// Record a batch of [`CompatSession`] activity
298    ///
299    /// # Parameters
300    ///
301    /// * `activity`: A list of tuples containing the session ID, the last
302    ///   activity timestamp and the IP address of the client
303    ///
304    /// # Errors
305    ///
306    /// Returns [`Self::Error`] if the underlying repository fails
307    async fn record_batch_activity(
308        &mut self,
309        activity: Vec<(Ulid, DateTime<Utc>, Option<IpAddr>)>,
310    ) -> Result<(), Self::Error>;
311
312    /// Record the user agent of a compat session
313    ///
314    /// # Parameters
315    ///
316    /// * `compat_session`: The compat session to record the user agent for
317    /// * `user_agent`: The user agent to record
318    ///
319    /// # Errors
320    ///
321    /// Returns [`Self::Error`] if the underlying repository fails
322    async fn record_user_agent(
323        &mut self,
324        compat_session: CompatSession,
325        user_agent: UserAgent,
326    ) -> Result<CompatSession, Self::Error>;
327}
328
329repository_impl!(CompatSessionRepository:
330    async fn lookup(&mut self, id: Ulid) -> Result<Option<CompatSession>, Self::Error>;
331
332    async fn add(
333        &mut self,
334        rng: &mut (dyn RngCore + Send),
335        clock: &dyn Clock,
336        user: &User,
337        device: Device,
338        browser_session: Option<&BrowserSession>,
339        is_synapse_admin: bool,
340    ) -> Result<CompatSession, Self::Error>;
341
342    async fn finish(
343        &mut self,
344        clock: &dyn Clock,
345        compat_session: CompatSession,
346    ) -> Result<CompatSession, Self::Error>;
347
348    async fn finish_bulk(
349        &mut self,
350        clock: &dyn Clock,
351        filter: CompatSessionFilter<'_>,
352    ) -> Result<usize, Self::Error>;
353
354    async fn list(
355        &mut self,
356        filter: CompatSessionFilter<'_>,
357        pagination: Pagination,
358    ) -> Result<Page<(CompatSession, Option<CompatSsoLogin>)>, Self::Error>;
359
360    async fn count(&mut self, filter: CompatSessionFilter<'_>) -> Result<usize, Self::Error>;
361
362    async fn record_batch_activity(
363        &mut self,
364        activity: Vec<(Ulid, DateTime<Utc>, Option<IpAddr>)>,
365    ) -> Result<(), Self::Error>;
366
367    async fn record_user_agent(
368        &mut self,
369        compat_session: CompatSession,
370        user_agent: UserAgent,
371    ) -> Result<CompatSession, Self::Error>;
372);