mas_handlers/admin/
model.rs

1// Copyright 2024 New Vector Ltd.
2// Copyright 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 chrono::{DateTime, Utc};
10use mas_data_model::Device;
11use schemars::JsonSchema;
12use serde::Serialize;
13use ulid::Ulid;
14use url::Url;
15
16/// A resource, with a type and an ID
17pub trait Resource {
18    /// The type of the resource
19    const KIND: &'static str;
20
21    /// The canonical path prefix for this kind of resource
22    const PATH: &'static str;
23
24    /// The ID of the resource
25    fn id(&self) -> Ulid;
26
27    /// The canonical path for this resource
28    ///
29    /// This is the concatenation of the canonical path prefix and the ID
30    fn path(&self) -> String {
31        format!("{}/{}", Self::PATH, self.id())
32    }
33}
34
35/// A user
36#[derive(Serialize, JsonSchema)]
37pub struct User {
38    #[serde(skip)]
39    id: Ulid,
40
41    /// The username (localpart) of the user
42    username: String,
43
44    /// When the user was created
45    created_at: DateTime<Utc>,
46
47    /// When the user was locked. If null, the user is not locked.
48    locked_at: Option<DateTime<Utc>>,
49
50    /// Whether the user can request admin privileges.
51    admin: bool,
52}
53
54impl User {
55    /// Samples of users with different properties for examples in the schema
56    pub fn samples() -> [Self; 3] {
57        [
58            Self {
59                id: Ulid::from_bytes([0x01; 16]),
60                username: "alice".to_owned(),
61                created_at: DateTime::default(),
62                locked_at: None,
63                admin: false,
64            },
65            Self {
66                id: Ulid::from_bytes([0x02; 16]),
67                username: "bob".to_owned(),
68                created_at: DateTime::default(),
69                locked_at: None,
70                admin: true,
71            },
72            Self {
73                id: Ulid::from_bytes([0x03; 16]),
74                username: "charlie".to_owned(),
75                created_at: DateTime::default(),
76                locked_at: Some(DateTime::default()),
77                admin: false,
78            },
79        ]
80    }
81}
82
83impl From<mas_data_model::User> for User {
84    fn from(user: mas_data_model::User) -> Self {
85        Self {
86            id: user.id,
87            username: user.username,
88            created_at: user.created_at,
89            locked_at: user.locked_at,
90            admin: user.can_request_admin,
91        }
92    }
93}
94
95impl Resource for User {
96    const KIND: &'static str = "user";
97    const PATH: &'static str = "/api/admin/v1/users";
98
99    fn id(&self) -> Ulid {
100        self.id
101    }
102}
103
104/// An email address for a user
105#[derive(Serialize, JsonSchema)]
106pub struct UserEmail {
107    #[serde(skip)]
108    id: Ulid,
109
110    /// When the object was created
111    created_at: DateTime<Utc>,
112
113    /// The ID of the user who owns this email address
114    #[schemars(with = "super::schema::Ulid")]
115    user_id: Ulid,
116
117    /// The email address
118    email: String,
119}
120
121impl Resource for UserEmail {
122    const KIND: &'static str = "user-email";
123    const PATH: &'static str = "/api/admin/v1/user-emails";
124
125    fn id(&self) -> Ulid {
126        self.id
127    }
128}
129
130impl From<mas_data_model::UserEmail> for UserEmail {
131    fn from(value: mas_data_model::UserEmail) -> Self {
132        Self {
133            id: value.id,
134            created_at: value.created_at,
135            user_id: value.user_id,
136            email: value.email,
137        }
138    }
139}
140
141impl UserEmail {
142    pub fn samples() -> [Self; 1] {
143        [Self {
144            id: Ulid::from_bytes([0x01; 16]),
145            created_at: DateTime::default(),
146            user_id: Ulid::from_bytes([0x02; 16]),
147            email: "alice@example.com".to_owned(),
148        }]
149    }
150}
151
152/// A compatibility session for legacy clients
153#[derive(Serialize, JsonSchema)]
154pub struct CompatSession {
155    #[serde(skip)]
156    pub id: Ulid,
157
158    /// The ID of the user that owns this session
159    #[schemars(with = "super::schema::Ulid")]
160    pub user_id: Ulid,
161
162    /// The Matrix device ID of this session
163    #[schemars(with = "super::schema::Device")]
164    pub device_id: Option<Device>,
165
166    /// The ID of the user session that started this session, if any
167    #[schemars(with = "super::schema::Ulid")]
168    pub user_session_id: Option<Ulid>,
169
170    /// The redirect URI used to login in the client, if it was an SSO login
171    pub redirect_uri: Option<Url>,
172
173    /// The time this session was created
174    pub created_at: DateTime<Utc>,
175
176    /// The user agent string that started this session, if any
177    pub user_agent: Option<String>,
178
179    /// The time this session was last active
180    pub last_active_at: Option<DateTime<Utc>>,
181
182    /// The last IP address recorded for this session
183    pub last_active_ip: Option<std::net::IpAddr>,
184
185    /// The time this session was finished
186    pub finished_at: Option<DateTime<Utc>>,
187}
188
189impl
190    From<(
191        mas_data_model::CompatSession,
192        Option<mas_data_model::CompatSsoLogin>,
193    )> for CompatSession
194{
195    fn from(
196        (session, sso_login): (
197            mas_data_model::CompatSession,
198            Option<mas_data_model::CompatSsoLogin>,
199        ),
200    ) -> Self {
201        let finished_at = session.finished_at();
202        Self {
203            id: session.id,
204            user_id: session.user_id,
205            device_id: session.device,
206            user_session_id: session.user_session_id,
207            redirect_uri: sso_login.map(|sso| sso.redirect_uri),
208            created_at: session.created_at,
209            user_agent: session.user_agent.map(|ua| ua.raw),
210            last_active_at: session.last_active_at,
211            last_active_ip: session.last_active_ip,
212            finished_at,
213        }
214    }
215}
216
217impl Resource for CompatSession {
218    const KIND: &'static str = "compat-session";
219    const PATH: &'static str = "/api/admin/v1/compat-sessions";
220
221    fn id(&self) -> Ulid {
222        self.id
223    }
224}
225
226impl CompatSession {
227    pub fn samples() -> [Self; 3] {
228        [
229            Self {
230                id: Ulid::from_bytes([0x01; 16]),
231                user_id: Ulid::from_bytes([0x01; 16]),
232                device_id: Some("AABBCCDDEE".to_owned().into()),
233                user_session_id: Some(Ulid::from_bytes([0x11; 16])),
234                redirect_uri: Some("https://example.com/redirect".parse().unwrap()),
235                created_at: DateTime::default(),
236                user_agent: Some("Mozilla/5.0".to_owned()),
237                last_active_at: Some(DateTime::default()),
238                last_active_ip: Some([1, 2, 3, 4].into()),
239                finished_at: None,
240            },
241            Self {
242                id: Ulid::from_bytes([0x02; 16]),
243                user_id: Ulid::from_bytes([0x01; 16]),
244                device_id: Some("FFGGHHIIJJ".to_owned().into()),
245                user_session_id: Some(Ulid::from_bytes([0x12; 16])),
246                redirect_uri: None,
247                created_at: DateTime::default(),
248                user_agent: Some("Mozilla/5.0".to_owned()),
249                last_active_at: Some(DateTime::default()),
250                last_active_ip: Some([1, 2, 3, 4].into()),
251                finished_at: Some(DateTime::default()),
252            },
253            Self {
254                id: Ulid::from_bytes([0x03; 16]),
255                user_id: Ulid::from_bytes([0x01; 16]),
256                device_id: None,
257                user_session_id: None,
258                redirect_uri: None,
259                created_at: DateTime::default(),
260                user_agent: None,
261                last_active_at: None,
262                last_active_ip: None,
263                finished_at: None,
264            },
265        ]
266    }
267}
268
269/// A OAuth 2.0 session
270#[derive(Serialize, JsonSchema)]
271pub struct OAuth2Session {
272    #[serde(skip)]
273    id: Ulid,
274
275    /// When the object was created
276    created_at: DateTime<Utc>,
277
278    /// When the session was finished
279    finished_at: Option<DateTime<Utc>>,
280
281    /// The ID of the user who owns the session
282    #[schemars(with = "Option<super::schema::Ulid>")]
283    user_id: Option<Ulid>,
284
285    /// The ID of the browser session which started this session
286    #[schemars(with = "Option<super::schema::Ulid>")]
287    user_session_id: Option<Ulid>,
288
289    /// The ID of the client which requested this session
290    #[schemars(with = "super::schema::Ulid")]
291    client_id: Ulid,
292
293    /// The scope granted for this session
294    scope: String,
295
296    /// The user agent string of the client which started this session
297    user_agent: Option<String>,
298
299    /// The last time the session was active
300    last_active_at: Option<DateTime<Utc>>,
301
302    /// The last IP address used by the session
303    last_active_ip: Option<IpAddr>,
304}
305
306impl From<mas_data_model::Session> for OAuth2Session {
307    fn from(session: mas_data_model::Session) -> Self {
308        Self {
309            id: session.id,
310            created_at: session.created_at,
311            finished_at: session.finished_at(),
312            user_id: session.user_id,
313            user_session_id: session.user_session_id,
314            client_id: session.client_id,
315            scope: session.scope.to_string(),
316            user_agent: session.user_agent.map(|ua| ua.raw),
317            last_active_at: session.last_active_at,
318            last_active_ip: session.last_active_ip,
319        }
320    }
321}
322
323impl OAuth2Session {
324    /// Samples of OAuth 2.0 sessions
325    pub fn samples() -> [Self; 3] {
326        [
327            Self {
328                id: Ulid::from_bytes([0x01; 16]),
329                created_at: DateTime::default(),
330                finished_at: None,
331                user_id: Some(Ulid::from_bytes([0x02; 16])),
332                user_session_id: Some(Ulid::from_bytes([0x03; 16])),
333                client_id: Ulid::from_bytes([0x04; 16]),
334                scope: "openid".to_owned(),
335                user_agent: Some("Mozilla/5.0".to_owned()),
336                last_active_at: Some(DateTime::default()),
337                last_active_ip: Some("127.0.0.1".parse().unwrap()),
338            },
339            Self {
340                id: Ulid::from_bytes([0x02; 16]),
341                created_at: DateTime::default(),
342                finished_at: None,
343                user_id: None,
344                user_session_id: None,
345                client_id: Ulid::from_bytes([0x05; 16]),
346                scope: "urn:mas:admin".to_owned(),
347                user_agent: None,
348                last_active_at: None,
349                last_active_ip: None,
350            },
351            Self {
352                id: Ulid::from_bytes([0x03; 16]),
353                created_at: DateTime::default(),
354                finished_at: Some(DateTime::default()),
355                user_id: Some(Ulid::from_bytes([0x04; 16])),
356                user_session_id: Some(Ulid::from_bytes([0x05; 16])),
357                client_id: Ulid::from_bytes([0x06; 16]),
358                scope: "urn:matrix:org.matrix.msc2967.client:api:*".to_owned(),
359                user_agent: Some("Mozilla/5.0".to_owned()),
360                last_active_at: Some(DateTime::default()),
361                last_active_ip: Some("127.0.0.1".parse().unwrap()),
362            },
363        ]
364    }
365}
366
367impl Resource for OAuth2Session {
368    const KIND: &'static str = "oauth2-session";
369    const PATH: &'static str = "/api/admin/v1/oauth2-sessions";
370
371    fn id(&self) -> Ulid {
372        self.id
373    }
374}
375
376/// The browser (cookie) session for a user
377#[derive(Serialize, JsonSchema)]
378pub struct UserSession {
379    #[serde(skip)]
380    id: Ulid,
381
382    /// When the object was created
383    created_at: DateTime<Utc>,
384
385    /// When the session was finished
386    finished_at: Option<DateTime<Utc>>,
387
388    /// The ID of the user who owns the session
389    #[schemars(with = "super::schema::Ulid")]
390    user_id: Ulid,
391
392    /// The user agent string of the client which started this session
393    user_agent: Option<String>,
394
395    /// The last time the session was active
396    last_active_at: Option<DateTime<Utc>>,
397
398    /// The last IP address used by the session
399    last_active_ip: Option<IpAddr>,
400}
401
402impl From<mas_data_model::BrowserSession> for UserSession {
403    fn from(value: mas_data_model::BrowserSession) -> Self {
404        Self {
405            id: value.id,
406            created_at: value.created_at,
407            finished_at: value.finished_at,
408            user_id: value.user.id,
409            user_agent: value.user_agent.map(|ua| ua.raw),
410            last_active_at: value.last_active_at,
411            last_active_ip: value.last_active_ip,
412        }
413    }
414}
415
416impl UserSession {
417    /// Samples of user sessions
418    pub fn samples() -> [Self; 3] {
419        [
420            Self {
421                id: Ulid::from_bytes([0x01; 16]),
422                created_at: DateTime::default(),
423                finished_at: None,
424                user_id: Ulid::from_bytes([0x02; 16]),
425                user_agent: Some("Mozilla/5.0".to_owned()),
426                last_active_at: Some(DateTime::default()),
427                last_active_ip: Some("127.0.0.1".parse().unwrap()),
428            },
429            Self {
430                id: Ulid::from_bytes([0x02; 16]),
431                created_at: DateTime::default(),
432                finished_at: None,
433                user_id: Ulid::from_bytes([0x03; 16]),
434                user_agent: None,
435                last_active_at: None,
436                last_active_ip: None,
437            },
438            Self {
439                id: Ulid::from_bytes([0x03; 16]),
440                created_at: DateTime::default(),
441                finished_at: Some(DateTime::default()),
442                user_id: Ulid::from_bytes([0x04; 16]),
443                user_agent: Some("Mozilla/5.0".to_owned()),
444                last_active_at: Some(DateTime::default()),
445                last_active_ip: Some("127.0.0.1".parse().unwrap()),
446            },
447        ]
448    }
449}
450
451impl Resource for UserSession {
452    const KIND: &'static str = "user-session";
453    const PATH: &'static str = "/api/admin/v1/user-sessions";
454
455    fn id(&self) -> Ulid {
456        self.id
457    }
458}
459
460/// An upstream OAuth 2.0 link
461#[derive(Serialize, JsonSchema)]
462pub struct UpstreamOAuthLink {
463    #[serde(skip)]
464    id: Ulid,
465
466    /// When the object was created
467    created_at: DateTime<Utc>,
468
469    /// The ID of the provider
470    #[schemars(with = "super::schema::Ulid")]
471    provider_id: Ulid,
472
473    /// The subject of the upstream account, unique per provider
474    subject: String,
475
476    /// The ID of the user who owns this link, if any
477    #[schemars(with = "Option<super::schema::Ulid>")]
478    user_id: Option<Ulid>,
479
480    /// A human-readable name of the upstream account
481    human_account_name: Option<String>,
482}
483
484impl Resource for UpstreamOAuthLink {
485    const KIND: &'static str = "upstream-oauth-link";
486    const PATH: &'static str = "/api/admin/v1/upstream-oauth-links";
487
488    fn id(&self) -> Ulid {
489        self.id
490    }
491}
492
493impl From<mas_data_model::UpstreamOAuthLink> for UpstreamOAuthLink {
494    fn from(value: mas_data_model::UpstreamOAuthLink) -> Self {
495        Self {
496            id: value.id,
497            created_at: value.created_at,
498            provider_id: value.provider_id,
499            subject: value.subject,
500            user_id: value.user_id,
501            human_account_name: value.human_account_name,
502        }
503    }
504}
505
506impl UpstreamOAuthLink {
507    /// Samples of upstream OAuth 2.0 links
508    pub fn samples() -> [Self; 3] {
509        [
510            Self {
511                id: Ulid::from_bytes([0x01; 16]),
512                created_at: DateTime::default(),
513                provider_id: Ulid::from_bytes([0x02; 16]),
514                subject: "john-42".to_owned(),
515                user_id: Some(Ulid::from_bytes([0x03; 16])),
516                human_account_name: Some("john.doe@example.com".to_owned()),
517            },
518            Self {
519                id: Ulid::from_bytes([0x02; 16]),
520                created_at: DateTime::default(),
521                provider_id: Ulid::from_bytes([0x03; 16]),
522                subject: "jane-123".to_owned(),
523                user_id: None,
524                human_account_name: None,
525            },
526            Self {
527                id: Ulid::from_bytes([0x03; 16]),
528                created_at: DateTime::default(),
529                provider_id: Ulid::from_bytes([0x04; 16]),
530                subject: "bob@social.example.com".to_owned(),
531                user_id: Some(Ulid::from_bytes([0x05; 16])),
532                human_account_name: Some("bob".to_owned()),
533            },
534        ]
535    }
536}
537
538/// The policy data
539#[derive(Serialize, JsonSchema)]
540pub struct PolicyData {
541    #[serde(skip)]
542    id: Ulid,
543
544    /// The creation date of the policy data
545    created_at: DateTime<Utc>,
546
547    /// The policy data content
548    data: serde_json::Value,
549}
550
551impl From<mas_data_model::PolicyData> for PolicyData {
552    fn from(policy_data: mas_data_model::PolicyData) -> Self {
553        Self {
554            id: policy_data.id,
555            created_at: policy_data.created_at,
556            data: policy_data.data,
557        }
558    }
559}
560
561impl Resource for PolicyData {
562    const KIND: &'static str = "policy-data";
563    const PATH: &'static str = "/api/admin/v1/policy-data";
564
565    fn id(&self) -> Ulid {
566        self.id
567    }
568}
569
570impl PolicyData {
571    /// Samples of policy data
572    pub fn samples() -> [Self; 1] {
573        [Self {
574            id: Ulid::from_bytes([0x01; 16]),
575            created_at: DateTime::default(),
576            data: serde_json::json!({
577                "hello": "world",
578                "foo": 42,
579                "bar": true
580            }),
581        }]
582    }
583}