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    /// When the user was deactivated. If null, the user is not deactivated.
51    deactivated_at: Option<DateTime<Utc>>,
52
53    /// Whether the user can request admin privileges.
54    admin: bool,
55}
56
57impl User {
58    /// Samples of users with different properties for examples in the schema
59    pub fn samples() -> [Self; 3] {
60        [
61            Self {
62                id: Ulid::from_bytes([0x01; 16]),
63                username: "alice".to_owned(),
64                created_at: DateTime::default(),
65                locked_at: None,
66                deactivated_at: None,
67                admin: false,
68            },
69            Self {
70                id: Ulid::from_bytes([0x02; 16]),
71                username: "bob".to_owned(),
72                created_at: DateTime::default(),
73                locked_at: None,
74                deactivated_at: None,
75                admin: true,
76            },
77            Self {
78                id: Ulid::from_bytes([0x03; 16]),
79                username: "charlie".to_owned(),
80                created_at: DateTime::default(),
81                locked_at: Some(DateTime::default()),
82                deactivated_at: None,
83                admin: false,
84            },
85        ]
86    }
87}
88
89impl From<mas_data_model::User> for User {
90    fn from(user: mas_data_model::User) -> Self {
91        Self {
92            id: user.id,
93            username: user.username,
94            created_at: user.created_at,
95            locked_at: user.locked_at,
96            deactivated_at: user.deactivated_at,
97            admin: user.can_request_admin,
98        }
99    }
100}
101
102impl Resource for User {
103    const KIND: &'static str = "user";
104    const PATH: &'static str = "/api/admin/v1/users";
105
106    fn id(&self) -> Ulid {
107        self.id
108    }
109}
110
111/// An email address for a user
112#[derive(Serialize, JsonSchema)]
113pub struct UserEmail {
114    #[serde(skip)]
115    id: Ulid,
116
117    /// When the object was created
118    created_at: DateTime<Utc>,
119
120    /// The ID of the user who owns this email address
121    #[schemars(with = "super::schema::Ulid")]
122    user_id: Ulid,
123
124    /// The email address
125    email: String,
126}
127
128impl Resource for UserEmail {
129    const KIND: &'static str = "user-email";
130    const PATH: &'static str = "/api/admin/v1/user-emails";
131
132    fn id(&self) -> Ulid {
133        self.id
134    }
135}
136
137impl From<mas_data_model::UserEmail> for UserEmail {
138    fn from(value: mas_data_model::UserEmail) -> Self {
139        Self {
140            id: value.id,
141            created_at: value.created_at,
142            user_id: value.user_id,
143            email: value.email,
144        }
145    }
146}
147
148impl UserEmail {
149    pub fn samples() -> [Self; 1] {
150        [Self {
151            id: Ulid::from_bytes([0x01; 16]),
152            created_at: DateTime::default(),
153            user_id: Ulid::from_bytes([0x02; 16]),
154            email: "alice@example.com".to_owned(),
155        }]
156    }
157}
158
159/// A compatibility session for legacy clients
160#[derive(Serialize, JsonSchema)]
161pub struct CompatSession {
162    #[serde(skip)]
163    pub id: Ulid,
164
165    /// The ID of the user that owns this session
166    #[schemars(with = "super::schema::Ulid")]
167    pub user_id: Ulid,
168
169    /// The Matrix device ID of this session
170    #[schemars(with = "super::schema::Device")]
171    pub device_id: Option<Device>,
172
173    /// The ID of the user session that started this session, if any
174    #[schemars(with = "super::schema::Ulid")]
175    pub user_session_id: Option<Ulid>,
176
177    /// The redirect URI used to login in the client, if it was an SSO login
178    pub redirect_uri: Option<Url>,
179
180    /// The time this session was created
181    pub created_at: DateTime<Utc>,
182
183    /// The user agent string that started this session, if any
184    pub user_agent: Option<String>,
185
186    /// The time this session was last active
187    pub last_active_at: Option<DateTime<Utc>>,
188
189    /// The last IP address recorded for this session
190    pub last_active_ip: Option<std::net::IpAddr>,
191
192    /// The time this session was finished
193    pub finished_at: Option<DateTime<Utc>>,
194
195    /// The user-provided name, if any
196    pub human_name: Option<String>,
197}
198
199impl
200    From<(
201        mas_data_model::CompatSession,
202        Option<mas_data_model::CompatSsoLogin>,
203    )> for CompatSession
204{
205    fn from(
206        (session, sso_login): (
207            mas_data_model::CompatSession,
208            Option<mas_data_model::CompatSsoLogin>,
209        ),
210    ) -> Self {
211        let finished_at = session.finished_at();
212        Self {
213            id: session.id,
214            user_id: session.user_id,
215            device_id: session.device,
216            user_session_id: session.user_session_id,
217            redirect_uri: sso_login.map(|sso| sso.redirect_uri),
218            created_at: session.created_at,
219            user_agent: session.user_agent,
220            last_active_at: session.last_active_at,
221            last_active_ip: session.last_active_ip,
222            finished_at,
223            human_name: session.human_name,
224        }
225    }
226}
227
228impl Resource for CompatSession {
229    const KIND: &'static str = "compat-session";
230    const PATH: &'static str = "/api/admin/v1/compat-sessions";
231
232    fn id(&self) -> Ulid {
233        self.id
234    }
235}
236
237impl CompatSession {
238    pub fn samples() -> [Self; 3] {
239        [
240            Self {
241                id: Ulid::from_bytes([0x01; 16]),
242                user_id: Ulid::from_bytes([0x01; 16]),
243                device_id: Some("AABBCCDDEE".to_owned().into()),
244                user_session_id: Some(Ulid::from_bytes([0x11; 16])),
245                redirect_uri: Some("https://example.com/redirect".parse().unwrap()),
246                created_at: DateTime::default(),
247                user_agent: Some("Mozilla/5.0".to_owned()),
248                last_active_at: Some(DateTime::default()),
249                last_active_ip: Some([1, 2, 3, 4].into()),
250                finished_at: None,
251                human_name: Some("Laptop".to_owned()),
252            },
253            Self {
254                id: Ulid::from_bytes([0x02; 16]),
255                user_id: Ulid::from_bytes([0x01; 16]),
256                device_id: Some("FFGGHHIIJJ".to_owned().into()),
257                user_session_id: Some(Ulid::from_bytes([0x12; 16])),
258                redirect_uri: None,
259                created_at: DateTime::default(),
260                user_agent: Some("Mozilla/5.0".to_owned()),
261                last_active_at: Some(DateTime::default()),
262                last_active_ip: Some([1, 2, 3, 4].into()),
263                finished_at: Some(DateTime::default()),
264                human_name: None,
265            },
266            Self {
267                id: Ulid::from_bytes([0x03; 16]),
268                user_id: Ulid::from_bytes([0x01; 16]),
269                device_id: None,
270                user_session_id: None,
271                redirect_uri: None,
272                created_at: DateTime::default(),
273                user_agent: None,
274                last_active_at: None,
275                last_active_ip: None,
276                finished_at: None,
277                human_name: None,
278            },
279        ]
280    }
281}
282
283/// A OAuth 2.0 session
284#[derive(Serialize, JsonSchema)]
285pub struct OAuth2Session {
286    #[serde(skip)]
287    id: Ulid,
288
289    /// When the object was created
290    created_at: DateTime<Utc>,
291
292    /// When the session was finished
293    finished_at: Option<DateTime<Utc>>,
294
295    /// The ID of the user who owns the session
296    #[schemars(with = "Option<super::schema::Ulid>")]
297    user_id: Option<Ulid>,
298
299    /// The ID of the browser session which started this session
300    #[schemars(with = "Option<super::schema::Ulid>")]
301    user_session_id: Option<Ulid>,
302
303    /// The ID of the client which requested this session
304    #[schemars(with = "super::schema::Ulid")]
305    client_id: Ulid,
306
307    /// The scope granted for this session
308    scope: String,
309
310    /// The user agent string of the client which started this session
311    user_agent: Option<String>,
312
313    /// The last time the session was active
314    last_active_at: Option<DateTime<Utc>>,
315
316    /// The last IP address used by the session
317    last_active_ip: Option<IpAddr>,
318
319    /// The user-provided name, if any
320    human_name: Option<String>,
321}
322
323impl From<mas_data_model::Session> for OAuth2Session {
324    fn from(session: mas_data_model::Session) -> Self {
325        Self {
326            id: session.id,
327            created_at: session.created_at,
328            finished_at: session.finished_at(),
329            user_id: session.user_id,
330            user_session_id: session.user_session_id,
331            client_id: session.client_id,
332            scope: session.scope.to_string(),
333            user_agent: session.user_agent,
334            last_active_at: session.last_active_at,
335            last_active_ip: session.last_active_ip,
336            human_name: session.human_name,
337        }
338    }
339}
340
341impl OAuth2Session {
342    /// Samples of OAuth 2.0 sessions
343    pub fn samples() -> [Self; 3] {
344        [
345            Self {
346                id: Ulid::from_bytes([0x01; 16]),
347                created_at: DateTime::default(),
348                finished_at: None,
349                user_id: Some(Ulid::from_bytes([0x02; 16])),
350                user_session_id: Some(Ulid::from_bytes([0x03; 16])),
351                client_id: Ulid::from_bytes([0x04; 16]),
352                scope: "openid".to_owned(),
353                user_agent: Some("Mozilla/5.0".to_owned()),
354                last_active_at: Some(DateTime::default()),
355                last_active_ip: Some("127.0.0.1".parse().unwrap()),
356                human_name: Some("Laptop".to_owned()),
357            },
358            Self {
359                id: Ulid::from_bytes([0x02; 16]),
360                created_at: DateTime::default(),
361                finished_at: None,
362                user_id: None,
363                user_session_id: None,
364                client_id: Ulid::from_bytes([0x05; 16]),
365                scope: "urn:mas:admin".to_owned(),
366                user_agent: None,
367                last_active_at: None,
368                last_active_ip: None,
369                human_name: None,
370            },
371            Self {
372                id: Ulid::from_bytes([0x03; 16]),
373                created_at: DateTime::default(),
374                finished_at: Some(DateTime::default()),
375                user_id: Some(Ulid::from_bytes([0x04; 16])),
376                user_session_id: Some(Ulid::from_bytes([0x05; 16])),
377                client_id: Ulid::from_bytes([0x06; 16]),
378                scope: "urn:matrix:org.matrix.msc2967.client:api:*".to_owned(),
379                user_agent: Some("Mozilla/5.0".to_owned()),
380                last_active_at: Some(DateTime::default()),
381                last_active_ip: Some("127.0.0.1".parse().unwrap()),
382                human_name: None,
383            },
384        ]
385    }
386}
387
388impl Resource for OAuth2Session {
389    const KIND: &'static str = "oauth2-session";
390    const PATH: &'static str = "/api/admin/v1/oauth2-sessions";
391
392    fn id(&self) -> Ulid {
393        self.id
394    }
395}
396
397/// The browser (cookie) session for a user
398#[derive(Serialize, JsonSchema)]
399pub struct UserSession {
400    #[serde(skip)]
401    id: Ulid,
402
403    /// When the object was created
404    created_at: DateTime<Utc>,
405
406    /// When the session was finished
407    finished_at: Option<DateTime<Utc>>,
408
409    /// The ID of the user who owns the session
410    #[schemars(with = "super::schema::Ulid")]
411    user_id: Ulid,
412
413    /// The user agent string of the client which started this session
414    user_agent: Option<String>,
415
416    /// The last time the session was active
417    last_active_at: Option<DateTime<Utc>>,
418
419    /// The last IP address used by the session
420    last_active_ip: Option<IpAddr>,
421}
422
423impl From<mas_data_model::BrowserSession> for UserSession {
424    fn from(value: mas_data_model::BrowserSession) -> Self {
425        Self {
426            id: value.id,
427            created_at: value.created_at,
428            finished_at: value.finished_at,
429            user_id: value.user.id,
430            user_agent: value.user_agent,
431            last_active_at: value.last_active_at,
432            last_active_ip: value.last_active_ip,
433        }
434    }
435}
436
437impl UserSession {
438    /// Samples of user sessions
439    pub fn samples() -> [Self; 3] {
440        [
441            Self {
442                id: Ulid::from_bytes([0x01; 16]),
443                created_at: DateTime::default(),
444                finished_at: None,
445                user_id: Ulid::from_bytes([0x02; 16]),
446                user_agent: Some("Mozilla/5.0".to_owned()),
447                last_active_at: Some(DateTime::default()),
448                last_active_ip: Some("127.0.0.1".parse().unwrap()),
449            },
450            Self {
451                id: Ulid::from_bytes([0x02; 16]),
452                created_at: DateTime::default(),
453                finished_at: None,
454                user_id: Ulid::from_bytes([0x03; 16]),
455                user_agent: None,
456                last_active_at: None,
457                last_active_ip: None,
458            },
459            Self {
460                id: Ulid::from_bytes([0x03; 16]),
461                created_at: DateTime::default(),
462                finished_at: Some(DateTime::default()),
463                user_id: Ulid::from_bytes([0x04; 16]),
464                user_agent: Some("Mozilla/5.0".to_owned()),
465                last_active_at: Some(DateTime::default()),
466                last_active_ip: Some("127.0.0.1".parse().unwrap()),
467            },
468        ]
469    }
470}
471
472impl Resource for UserSession {
473    const KIND: &'static str = "user-session";
474    const PATH: &'static str = "/api/admin/v1/user-sessions";
475
476    fn id(&self) -> Ulid {
477        self.id
478    }
479}
480
481/// An upstream OAuth 2.0 link
482#[derive(Serialize, JsonSchema)]
483pub struct UpstreamOAuthLink {
484    #[serde(skip)]
485    id: Ulid,
486
487    /// When the object was created
488    created_at: DateTime<Utc>,
489
490    /// The ID of the provider
491    #[schemars(with = "super::schema::Ulid")]
492    provider_id: Ulid,
493
494    /// The subject of the upstream account, unique per provider
495    subject: String,
496
497    /// The ID of the user who owns this link, if any
498    #[schemars(with = "Option<super::schema::Ulid>")]
499    user_id: Option<Ulid>,
500
501    /// A human-readable name of the upstream account
502    human_account_name: Option<String>,
503}
504
505impl Resource for UpstreamOAuthLink {
506    const KIND: &'static str = "upstream-oauth-link";
507    const PATH: &'static str = "/api/admin/v1/upstream-oauth-links";
508
509    fn id(&self) -> Ulid {
510        self.id
511    }
512}
513
514impl From<mas_data_model::UpstreamOAuthLink> for UpstreamOAuthLink {
515    fn from(value: mas_data_model::UpstreamOAuthLink) -> Self {
516        Self {
517            id: value.id,
518            created_at: value.created_at,
519            provider_id: value.provider_id,
520            subject: value.subject,
521            user_id: value.user_id,
522            human_account_name: value.human_account_name,
523        }
524    }
525}
526
527impl UpstreamOAuthLink {
528    /// Samples of upstream OAuth 2.0 links
529    pub fn samples() -> [Self; 3] {
530        [
531            Self {
532                id: Ulid::from_bytes([0x01; 16]),
533                created_at: DateTime::default(),
534                provider_id: Ulid::from_bytes([0x02; 16]),
535                subject: "john-42".to_owned(),
536                user_id: Some(Ulid::from_bytes([0x03; 16])),
537                human_account_name: Some("john.doe@example.com".to_owned()),
538            },
539            Self {
540                id: Ulid::from_bytes([0x02; 16]),
541                created_at: DateTime::default(),
542                provider_id: Ulid::from_bytes([0x03; 16]),
543                subject: "jane-123".to_owned(),
544                user_id: None,
545                human_account_name: None,
546            },
547            Self {
548                id: Ulid::from_bytes([0x03; 16]),
549                created_at: DateTime::default(),
550                provider_id: Ulid::from_bytes([0x04; 16]),
551                subject: "bob@social.example.com".to_owned(),
552                user_id: Some(Ulid::from_bytes([0x05; 16])),
553                human_account_name: Some("bob".to_owned()),
554            },
555        ]
556    }
557}
558
559/// The policy data
560#[derive(Serialize, JsonSchema)]
561pub struct PolicyData {
562    #[serde(skip)]
563    id: Ulid,
564
565    /// The creation date of the policy data
566    created_at: DateTime<Utc>,
567
568    /// The policy data content
569    data: serde_json::Value,
570}
571
572impl From<mas_data_model::PolicyData> for PolicyData {
573    fn from(policy_data: mas_data_model::PolicyData) -> Self {
574        Self {
575            id: policy_data.id,
576            created_at: policy_data.created_at,
577            data: policy_data.data,
578        }
579    }
580}
581
582impl Resource for PolicyData {
583    const KIND: &'static str = "policy-data";
584    const PATH: &'static str = "/api/admin/v1/policy-data";
585
586    fn id(&self) -> Ulid {
587        self.id
588    }
589}
590
591impl PolicyData {
592    /// Samples of policy data
593    pub fn samples() -> [Self; 1] {
594        [Self {
595            id: Ulid::from_bytes([0x01; 16]),
596            created_at: DateTime::default(),
597            data: serde_json::json!({
598                "hello": "world",
599                "foo": 42,
600                "bar": true
601            }),
602        }]
603    }
604}
605
606/// A registration token
607#[derive(Serialize, JsonSchema)]
608pub struct UserRegistrationToken {
609    #[serde(skip)]
610    id: Ulid,
611
612    /// The token string
613    token: String,
614
615    /// Whether the token is valid
616    valid: bool,
617
618    /// Maximum number of times this token can be used
619    usage_limit: Option<u32>,
620
621    /// Number of times this token has been used
622    times_used: u32,
623
624    /// When the token was created
625    created_at: DateTime<Utc>,
626
627    /// When the token was last used. If null, the token has never been used.
628    last_used_at: Option<DateTime<Utc>>,
629
630    /// When the token expires. If null, the token never expires.
631    expires_at: Option<DateTime<Utc>>,
632
633    /// When the token was revoked. If null, the token is not revoked.
634    revoked_at: Option<DateTime<Utc>>,
635}
636
637impl UserRegistrationToken {
638    pub fn new(token: mas_data_model::UserRegistrationToken, now: DateTime<Utc>) -> Self {
639        Self {
640            id: token.id,
641            valid: token.is_valid(now),
642            token: token.token,
643            usage_limit: token.usage_limit,
644            times_used: token.times_used,
645            created_at: token.created_at,
646            last_used_at: token.last_used_at,
647            expires_at: token.expires_at,
648            revoked_at: token.revoked_at,
649        }
650    }
651}
652
653impl Resource for UserRegistrationToken {
654    const KIND: &'static str = "user-registration_token";
655    const PATH: &'static str = "/api/admin/v1/user-registration-tokens";
656
657    fn id(&self) -> Ulid {
658        self.id
659    }
660}
661
662impl UserRegistrationToken {
663    /// Samples of registration tokens
664    pub fn samples() -> [Self; 2] {
665        [
666            Self {
667                id: Ulid::from_bytes([0x01; 16]),
668                token: "abc123def456".to_owned(),
669                valid: true,
670                usage_limit: Some(10),
671                times_used: 5,
672                created_at: DateTime::default(),
673                last_used_at: Some(DateTime::default()),
674                expires_at: Some(DateTime::default() + chrono::Duration::days(30)),
675                revoked_at: None,
676            },
677            Self {
678                id: Ulid::from_bytes([0x02; 16]),
679                token: "xyz789abc012".to_owned(),
680                valid: false,
681                usage_limit: None,
682                times_used: 0,
683                created_at: DateTime::default(),
684                last_used_at: None,
685                expires_at: None,
686                revoked_at: Some(DateTime::default()),
687            },
688        ]
689    }
690}