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    /// The user-provided name, if any
189    pub human_name: Option<String>,
190}
191
192impl
193    From<(
194        mas_data_model::CompatSession,
195        Option<mas_data_model::CompatSsoLogin>,
196    )> for CompatSession
197{
198    fn from(
199        (session, sso_login): (
200            mas_data_model::CompatSession,
201            Option<mas_data_model::CompatSsoLogin>,
202        ),
203    ) -> Self {
204        let finished_at = session.finished_at();
205        Self {
206            id: session.id,
207            user_id: session.user_id,
208            device_id: session.device,
209            user_session_id: session.user_session_id,
210            redirect_uri: sso_login.map(|sso| sso.redirect_uri),
211            created_at: session.created_at,
212            user_agent: session.user_agent,
213            last_active_at: session.last_active_at,
214            last_active_ip: session.last_active_ip,
215            finished_at,
216            human_name: session.human_name,
217        }
218    }
219}
220
221impl Resource for CompatSession {
222    const KIND: &'static str = "compat-session";
223    const PATH: &'static str = "/api/admin/v1/compat-sessions";
224
225    fn id(&self) -> Ulid {
226        self.id
227    }
228}
229
230impl CompatSession {
231    pub fn samples() -> [Self; 3] {
232        [
233            Self {
234                id: Ulid::from_bytes([0x01; 16]),
235                user_id: Ulid::from_bytes([0x01; 16]),
236                device_id: Some("AABBCCDDEE".to_owned().into()),
237                user_session_id: Some(Ulid::from_bytes([0x11; 16])),
238                redirect_uri: Some("https://example.com/redirect".parse().unwrap()),
239                created_at: DateTime::default(),
240                user_agent: Some("Mozilla/5.0".to_owned()),
241                last_active_at: Some(DateTime::default()),
242                last_active_ip: Some([1, 2, 3, 4].into()),
243                finished_at: None,
244                human_name: Some("Laptop".to_owned()),
245            },
246            Self {
247                id: Ulid::from_bytes([0x02; 16]),
248                user_id: Ulid::from_bytes([0x01; 16]),
249                device_id: Some("FFGGHHIIJJ".to_owned().into()),
250                user_session_id: Some(Ulid::from_bytes([0x12; 16])),
251                redirect_uri: None,
252                created_at: DateTime::default(),
253                user_agent: Some("Mozilla/5.0".to_owned()),
254                last_active_at: Some(DateTime::default()),
255                last_active_ip: Some([1, 2, 3, 4].into()),
256                finished_at: Some(DateTime::default()),
257                human_name: None,
258            },
259            Self {
260                id: Ulid::from_bytes([0x03; 16]),
261                user_id: Ulid::from_bytes([0x01; 16]),
262                device_id: None,
263                user_session_id: None,
264                redirect_uri: None,
265                created_at: DateTime::default(),
266                user_agent: None,
267                last_active_at: None,
268                last_active_ip: None,
269                finished_at: None,
270                human_name: None,
271            },
272        ]
273    }
274}
275
276/// A OAuth 2.0 session
277#[derive(Serialize, JsonSchema)]
278pub struct OAuth2Session {
279    #[serde(skip)]
280    id: Ulid,
281
282    /// When the object was created
283    created_at: DateTime<Utc>,
284
285    /// When the session was finished
286    finished_at: Option<DateTime<Utc>>,
287
288    /// The ID of the user who owns the session
289    #[schemars(with = "Option<super::schema::Ulid>")]
290    user_id: Option<Ulid>,
291
292    /// The ID of the browser session which started this session
293    #[schemars(with = "Option<super::schema::Ulid>")]
294    user_session_id: Option<Ulid>,
295
296    /// The ID of the client which requested this session
297    #[schemars(with = "super::schema::Ulid")]
298    client_id: Ulid,
299
300    /// The scope granted for this session
301    scope: String,
302
303    /// The user agent string of the client which started this session
304    user_agent: Option<String>,
305
306    /// The last time the session was active
307    last_active_at: Option<DateTime<Utc>>,
308
309    /// The last IP address used by the session
310    last_active_ip: Option<IpAddr>,
311
312    /// The user-provided name, if any
313    human_name: Option<String>,
314}
315
316impl From<mas_data_model::Session> for OAuth2Session {
317    fn from(session: mas_data_model::Session) -> Self {
318        Self {
319            id: session.id,
320            created_at: session.created_at,
321            finished_at: session.finished_at(),
322            user_id: session.user_id,
323            user_session_id: session.user_session_id,
324            client_id: session.client_id,
325            scope: session.scope.to_string(),
326            user_agent: session.user_agent,
327            last_active_at: session.last_active_at,
328            last_active_ip: session.last_active_ip,
329            human_name: session.human_name,
330        }
331    }
332}
333
334impl OAuth2Session {
335    /// Samples of OAuth 2.0 sessions
336    pub fn samples() -> [Self; 3] {
337        [
338            Self {
339                id: Ulid::from_bytes([0x01; 16]),
340                created_at: DateTime::default(),
341                finished_at: None,
342                user_id: Some(Ulid::from_bytes([0x02; 16])),
343                user_session_id: Some(Ulid::from_bytes([0x03; 16])),
344                client_id: Ulid::from_bytes([0x04; 16]),
345                scope: "openid".to_owned(),
346                user_agent: Some("Mozilla/5.0".to_owned()),
347                last_active_at: Some(DateTime::default()),
348                last_active_ip: Some("127.0.0.1".parse().unwrap()),
349                human_name: Some("Laptop".to_owned()),
350            },
351            Self {
352                id: Ulid::from_bytes([0x02; 16]),
353                created_at: DateTime::default(),
354                finished_at: None,
355                user_id: None,
356                user_session_id: None,
357                client_id: Ulid::from_bytes([0x05; 16]),
358                scope: "urn:mas:admin".to_owned(),
359                user_agent: None,
360                last_active_at: None,
361                last_active_ip: None,
362                human_name: None,
363            },
364            Self {
365                id: Ulid::from_bytes([0x03; 16]),
366                created_at: DateTime::default(),
367                finished_at: Some(DateTime::default()),
368                user_id: Some(Ulid::from_bytes([0x04; 16])),
369                user_session_id: Some(Ulid::from_bytes([0x05; 16])),
370                client_id: Ulid::from_bytes([0x06; 16]),
371                scope: "urn:matrix:org.matrix.msc2967.client:api:*".to_owned(),
372                user_agent: Some("Mozilla/5.0".to_owned()),
373                last_active_at: Some(DateTime::default()),
374                last_active_ip: Some("127.0.0.1".parse().unwrap()),
375                human_name: None,
376            },
377        ]
378    }
379}
380
381impl Resource for OAuth2Session {
382    const KIND: &'static str = "oauth2-session";
383    const PATH: &'static str = "/api/admin/v1/oauth2-sessions";
384
385    fn id(&self) -> Ulid {
386        self.id
387    }
388}
389
390/// The browser (cookie) session for a user
391#[derive(Serialize, JsonSchema)]
392pub struct UserSession {
393    #[serde(skip)]
394    id: Ulid,
395
396    /// When the object was created
397    created_at: DateTime<Utc>,
398
399    /// When the session was finished
400    finished_at: Option<DateTime<Utc>>,
401
402    /// The ID of the user who owns the session
403    #[schemars(with = "super::schema::Ulid")]
404    user_id: Ulid,
405
406    /// The user agent string of the client which started this session
407    user_agent: Option<String>,
408
409    /// The last time the session was active
410    last_active_at: Option<DateTime<Utc>>,
411
412    /// The last IP address used by the session
413    last_active_ip: Option<IpAddr>,
414}
415
416impl From<mas_data_model::BrowserSession> for UserSession {
417    fn from(value: mas_data_model::BrowserSession) -> Self {
418        Self {
419            id: value.id,
420            created_at: value.created_at,
421            finished_at: value.finished_at,
422            user_id: value.user.id,
423            user_agent: value.user_agent,
424            last_active_at: value.last_active_at,
425            last_active_ip: value.last_active_ip,
426        }
427    }
428}
429
430impl UserSession {
431    /// Samples of user sessions
432    pub fn samples() -> [Self; 3] {
433        [
434            Self {
435                id: Ulid::from_bytes([0x01; 16]),
436                created_at: DateTime::default(),
437                finished_at: None,
438                user_id: Ulid::from_bytes([0x02; 16]),
439                user_agent: Some("Mozilla/5.0".to_owned()),
440                last_active_at: Some(DateTime::default()),
441                last_active_ip: Some("127.0.0.1".parse().unwrap()),
442            },
443            Self {
444                id: Ulid::from_bytes([0x02; 16]),
445                created_at: DateTime::default(),
446                finished_at: None,
447                user_id: Ulid::from_bytes([0x03; 16]),
448                user_agent: None,
449                last_active_at: None,
450                last_active_ip: None,
451            },
452            Self {
453                id: Ulid::from_bytes([0x03; 16]),
454                created_at: DateTime::default(),
455                finished_at: Some(DateTime::default()),
456                user_id: Ulid::from_bytes([0x04; 16]),
457                user_agent: Some("Mozilla/5.0".to_owned()),
458                last_active_at: Some(DateTime::default()),
459                last_active_ip: Some("127.0.0.1".parse().unwrap()),
460            },
461        ]
462    }
463}
464
465impl Resource for UserSession {
466    const KIND: &'static str = "user-session";
467    const PATH: &'static str = "/api/admin/v1/user-sessions";
468
469    fn id(&self) -> Ulid {
470        self.id
471    }
472}
473
474/// An upstream OAuth 2.0 link
475#[derive(Serialize, JsonSchema)]
476pub struct UpstreamOAuthLink {
477    #[serde(skip)]
478    id: Ulid,
479
480    /// When the object was created
481    created_at: DateTime<Utc>,
482
483    /// The ID of the provider
484    #[schemars(with = "super::schema::Ulid")]
485    provider_id: Ulid,
486
487    /// The subject of the upstream account, unique per provider
488    subject: String,
489
490    /// The ID of the user who owns this link, if any
491    #[schemars(with = "Option<super::schema::Ulid>")]
492    user_id: Option<Ulid>,
493
494    /// A human-readable name of the upstream account
495    human_account_name: Option<String>,
496}
497
498impl Resource for UpstreamOAuthLink {
499    const KIND: &'static str = "upstream-oauth-link";
500    const PATH: &'static str = "/api/admin/v1/upstream-oauth-links";
501
502    fn id(&self) -> Ulid {
503        self.id
504    }
505}
506
507impl From<mas_data_model::UpstreamOAuthLink> for UpstreamOAuthLink {
508    fn from(value: mas_data_model::UpstreamOAuthLink) -> Self {
509        Self {
510            id: value.id,
511            created_at: value.created_at,
512            provider_id: value.provider_id,
513            subject: value.subject,
514            user_id: value.user_id,
515            human_account_name: value.human_account_name,
516        }
517    }
518}
519
520impl UpstreamOAuthLink {
521    /// Samples of upstream OAuth 2.0 links
522    pub fn samples() -> [Self; 3] {
523        [
524            Self {
525                id: Ulid::from_bytes([0x01; 16]),
526                created_at: DateTime::default(),
527                provider_id: Ulid::from_bytes([0x02; 16]),
528                subject: "john-42".to_owned(),
529                user_id: Some(Ulid::from_bytes([0x03; 16])),
530                human_account_name: Some("john.doe@example.com".to_owned()),
531            },
532            Self {
533                id: Ulid::from_bytes([0x02; 16]),
534                created_at: DateTime::default(),
535                provider_id: Ulid::from_bytes([0x03; 16]),
536                subject: "jane-123".to_owned(),
537                user_id: None,
538                human_account_name: None,
539            },
540            Self {
541                id: Ulid::from_bytes([0x03; 16]),
542                created_at: DateTime::default(),
543                provider_id: Ulid::from_bytes([0x04; 16]),
544                subject: "bob@social.example.com".to_owned(),
545                user_id: Some(Ulid::from_bytes([0x05; 16])),
546                human_account_name: Some("bob".to_owned()),
547            },
548        ]
549    }
550}
551
552/// The policy data
553#[derive(Serialize, JsonSchema)]
554pub struct PolicyData {
555    #[serde(skip)]
556    id: Ulid,
557
558    /// The creation date of the policy data
559    created_at: DateTime<Utc>,
560
561    /// The policy data content
562    data: serde_json::Value,
563}
564
565impl From<mas_data_model::PolicyData> for PolicyData {
566    fn from(policy_data: mas_data_model::PolicyData) -> Self {
567        Self {
568            id: policy_data.id,
569            created_at: policy_data.created_at,
570            data: policy_data.data,
571        }
572    }
573}
574
575impl Resource for PolicyData {
576    const KIND: &'static str = "policy-data";
577    const PATH: &'static str = "/api/admin/v1/policy-data";
578
579    fn id(&self) -> Ulid {
580        self.id
581    }
582}
583
584impl PolicyData {
585    /// Samples of policy data
586    pub fn samples() -> [Self; 1] {
587        [Self {
588            id: Ulid::from_bytes([0x01; 16]),
589            created_at: DateTime::default(),
590            data: serde_json::json!({
591                "hello": "world",
592                "foo": 42,
593                "bar": true
594            }),
595        }]
596    }
597}