Support reading and writing guests
This commit is contained in:
committed by
Quentin Gliech
parent
dc305ddc40
commit
ac58b4f326
@@ -0,0 +1,10 @@
|
||||
-- Copyright 2025 New Vector Ltd.
|
||||
--
|
||||
-- SPDX-License-Identifier: AGPL-3.0-only
|
||||
-- Please see LICENSE in the repository root for full details.
|
||||
|
||||
ALTER TABLE users
|
||||
-- Track whether users are guests.
|
||||
-- Although guest support is not present in MAS yet, syn2mas should import
|
||||
-- these users and therefore we should track their state.
|
||||
ADD COLUMN is_guest BOOLEAN NOT NULL DEFAULT FALSE;
|
||||
19
crates/syn2mas/.sqlx/query-06cd6bff12000db3e64e98c344cc9e3b5de7af6a497ad84036ae104576ae0575.json
generated
Normal file
19
crates/syn2mas/.sqlx/query-06cd6bff12000db3e64e98c344cc9e3b5de7af6a497ad84036ae104576ae0575.json
generated
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO syn2mas__users (\n user_id, username,\n created_at, locked_at,\n can_request_admin, is_guest)\n SELECT * FROM UNNEST(\n $1::UUID[], $2::TEXT[],\n $3::TIMESTAMP WITH TIME ZONE[], $4::TIMESTAMP WITH TIME ZONE[],\n $5::BOOL[], $6::BOOL[])\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"UuidArray",
|
||||
"TextArray",
|
||||
"TimestamptzArray",
|
||||
"TimestamptzArray",
|
||||
"BoolArray",
|
||||
"BoolArray"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "06cd6bff12000db3e64e98c344cc9e3b5de7af6a497ad84036ae104576ae0575"
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO syn2mas__users\n (user_id, username, created_at, locked_at, can_request_admin)\n SELECT * FROM UNNEST($1::UUID[], $2::TEXT[], $3::TIMESTAMP WITH TIME ZONE[], $4::TIMESTAMP WITH TIME ZONE[], $5::BOOL[])\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"UuidArray",
|
||||
"TextArray",
|
||||
"TimestamptzArray",
|
||||
"TimestamptzArray",
|
||||
"BoolArray"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "c7d2277606b4b326b0c375a056cd57488c930fe431311e53e5e1af6fb1d4e56f"
|
||||
}
|
||||
@@ -199,6 +199,10 @@ pub struct MasNewUser {
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub locked_at: Option<DateTime<Utc>>,
|
||||
pub can_request_admin: bool,
|
||||
/// Whether the user was a Synapse guest.
|
||||
/// Although MAS doesn't support guest access, it's still useful to track
|
||||
/// for the future.
|
||||
pub is_guest: bool,
|
||||
}
|
||||
|
||||
pub struct MasNewUserPassword {
|
||||
@@ -563,52 +567,66 @@ impl<'conn> MasWriter<'conn> {
|
||||
#[allow(clippy::missing_panics_doc)] // not a real panic
|
||||
#[tracing::instrument(skip_all, level = Level::DEBUG)]
|
||||
pub fn write_users(&mut self, users: Vec<MasNewUser>) -> BoxFuture<'_, Result<(), Error>> {
|
||||
self.writer_pool.spawn_with_connection(move |conn| Box::pin(async move {
|
||||
// `UNNEST` is a fast way to do bulk inserts, as it lets us send multiple rows in one statement
|
||||
// without having to change the statement SQL thus altering the query plan.
|
||||
// See <https://github.com/launchbadge/sqlx/blob/main/FAQ.md#how-can-i-bind-an-array-to-a-values-clause-how-can-i-do-bulk-inserts>.
|
||||
// In the future we could consider using sqlx's support for `PgCopyIn` / the `COPY FROM STDIN` statement,
|
||||
// which is allegedly the best for insert performance, but is less simple to encode.
|
||||
if users.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
self.writer_pool
|
||||
.spawn_with_connection(move |conn| {
|
||||
Box::pin(async move {
|
||||
// `UNNEST` is a fast way to do bulk inserts, as it lets us send multiple rows
|
||||
// in one statement without having to change the statement
|
||||
// SQL thus altering the query plan. See <https://github.com/launchbadge/sqlx/blob/main/FAQ.md#how-can-i-bind-an-array-to-a-values-clause-how-can-i-do-bulk-inserts>.
|
||||
// In the future we could consider using sqlx's support for `PgCopyIn` / the
|
||||
// `COPY FROM STDIN` statement, which is allegedly the best
|
||||
// for insert performance, but is less simple to encode.
|
||||
let mut user_ids: Vec<Uuid> = Vec::with_capacity(users.len());
|
||||
let mut usernames: Vec<String> = Vec::with_capacity(users.len());
|
||||
let mut created_ats: Vec<DateTime<Utc>> = Vec::with_capacity(users.len());
|
||||
let mut locked_ats: Vec<Option<DateTime<Utc>>> =
|
||||
Vec::with_capacity(users.len());
|
||||
let mut can_request_admins: Vec<bool> = Vec::with_capacity(users.len());
|
||||
let mut is_guests: Vec<bool> = Vec::with_capacity(users.len());
|
||||
for MasNewUser {
|
||||
user_id,
|
||||
username,
|
||||
created_at,
|
||||
locked_at,
|
||||
can_request_admin,
|
||||
is_guest,
|
||||
} in users
|
||||
{
|
||||
user_ids.push(user_id);
|
||||
usernames.push(username);
|
||||
created_ats.push(created_at);
|
||||
locked_ats.push(locked_at);
|
||||
can_request_admins.push(can_request_admin);
|
||||
is_guests.push(is_guest);
|
||||
}
|
||||
|
||||
let mut user_ids: Vec<Uuid> = Vec::with_capacity(users.len());
|
||||
let mut usernames: Vec<String> = Vec::with_capacity(users.len());
|
||||
let mut created_ats: Vec<DateTime<Utc>> = Vec::with_capacity(users.len());
|
||||
let mut locked_ats: Vec<Option<DateTime<Utc>>> = Vec::with_capacity(users.len());
|
||||
let mut can_request_admins: Vec<bool> = Vec::with_capacity(users.len());
|
||||
for MasNewUser {
|
||||
user_id,
|
||||
username,
|
||||
created_at,
|
||||
locked_at,
|
||||
can_request_admin,
|
||||
} in users
|
||||
{
|
||||
user_ids.push(user_id);
|
||||
usernames.push(username);
|
||||
created_ats.push(created_at);
|
||||
locked_ats.push(locked_at);
|
||||
can_request_admins.push(can_request_admin);
|
||||
}
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO syn2mas__users (
|
||||
user_id, username,
|
||||
created_at, locked_at,
|
||||
can_request_admin, is_guest)
|
||||
SELECT * FROM UNNEST(
|
||||
$1::UUID[], $2::TEXT[],
|
||||
$3::TIMESTAMP WITH TIME ZONE[], $4::TIMESTAMP WITH TIME ZONE[],
|
||||
$5::BOOL[], $6::BOOL[])
|
||||
"#,
|
||||
&user_ids[..],
|
||||
&usernames[..],
|
||||
&created_ats[..],
|
||||
// We need to override the typing for arrays of optionals (sqlx limitation)
|
||||
&locked_ats[..] as &[Option<DateTime<Utc>>],
|
||||
&can_request_admins[..],
|
||||
&is_guests[..],
|
||||
)
|
||||
.execute(&mut *conn)
|
||||
.await
|
||||
.into_database("writing users to MAS")?;
|
||||
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO syn2mas__users
|
||||
(user_id, username, created_at, locked_at, can_request_admin)
|
||||
SELECT * FROM UNNEST($1::UUID[], $2::TEXT[], $3::TIMESTAMP WITH TIME ZONE[], $4::TIMESTAMP WITH TIME ZONE[], $5::BOOL[])
|
||||
"#,
|
||||
&user_ids[..],
|
||||
&usernames[..],
|
||||
&created_ats[..],
|
||||
// We need to override the typing for arrays of optionals (sqlx limitation)
|
||||
&locked_ats[..] as &[Option<DateTime<Utc>>],
|
||||
&can_request_admins[..],
|
||||
).execute(&mut *conn).await.into_database("writing users to MAS")?;
|
||||
|
||||
Ok(())
|
||||
})).boxed()
|
||||
Ok(())
|
||||
})
|
||||
})
|
||||
.boxed()
|
||||
}
|
||||
|
||||
/// Write a batch of user passwords to the database.
|
||||
@@ -1197,6 +1215,7 @@ mod test {
|
||||
created_at: DateTime::default(),
|
||||
locked_at: None,
|
||||
can_request_admin: false,
|
||||
is_guest: false,
|
||||
}])
|
||||
.await
|
||||
.expect("failed to write user");
|
||||
@@ -1221,6 +1240,7 @@ mod test {
|
||||
created_at: DateTime::default(),
|
||||
locked_at: None,
|
||||
can_request_admin: false,
|
||||
is_guest: false,
|
||||
}])
|
||||
.await
|
||||
.expect("failed to write user");
|
||||
@@ -1252,6 +1272,7 @@ mod test {
|
||||
created_at: DateTime::default(),
|
||||
locked_at: None,
|
||||
can_request_admin: false,
|
||||
is_guest: false,
|
||||
}])
|
||||
.await
|
||||
.expect("failed to write user");
|
||||
@@ -1285,6 +1306,7 @@ mod test {
|
||||
created_at: DateTime::default(),
|
||||
locked_at: None,
|
||||
can_request_admin: false,
|
||||
is_guest: false,
|
||||
}])
|
||||
.await
|
||||
.expect("failed to write user");
|
||||
@@ -1319,6 +1341,7 @@ mod test {
|
||||
created_at: DateTime::default(),
|
||||
locked_at: None,
|
||||
can_request_admin: false,
|
||||
is_guest: false,
|
||||
}])
|
||||
.await
|
||||
.expect("failed to write user");
|
||||
@@ -1352,6 +1375,7 @@ mod test {
|
||||
created_at: DateTime::default(),
|
||||
locked_at: None,
|
||||
can_request_admin: false,
|
||||
is_guest: false,
|
||||
}])
|
||||
.await
|
||||
.expect("failed to write user");
|
||||
@@ -1389,6 +1413,7 @@ mod test {
|
||||
created_at: DateTime::default(),
|
||||
locked_at: None,
|
||||
can_request_admin: false,
|
||||
is_guest: false,
|
||||
}])
|
||||
.await
|
||||
.expect("failed to write user");
|
||||
@@ -1438,6 +1463,7 @@ mod test {
|
||||
created_at: DateTime::default(),
|
||||
locked_at: None,
|
||||
can_request_admin: false,
|
||||
is_guest: false,
|
||||
}])
|
||||
.await
|
||||
.expect("failed to write user");
|
||||
|
||||
@@ -5,6 +5,7 @@ expression: db_snapshot
|
||||
users:
|
||||
- can_request_admin: "false"
|
||||
created_at: "1970-01-01 00:00:00+00"
|
||||
is_guest: "false"
|
||||
locked_at: ~
|
||||
primary_user_email_id: ~
|
||||
user_id: 00000000-0000-0000-0000-000000000001
|
||||
|
||||
@@ -23,6 +23,7 @@ compat_sessions:
|
||||
users:
|
||||
- can_request_admin: "false"
|
||||
created_at: "1970-01-01 00:00:00+00"
|
||||
is_guest: "false"
|
||||
locked_at: ~
|
||||
primary_user_email_id: ~
|
||||
user_id: 00000000-0000-0000-0000-000000000001
|
||||
|
||||
@@ -17,6 +17,7 @@ compat_sessions:
|
||||
users:
|
||||
- can_request_admin: "false"
|
||||
created_at: "1970-01-01 00:00:00+00"
|
||||
is_guest: "false"
|
||||
locked_at: ~
|
||||
primary_user_email_id: ~
|
||||
user_id: 00000000-0000-0000-0000-000000000001
|
||||
|
||||
@@ -11,6 +11,7 @@ user_emails:
|
||||
users:
|
||||
- can_request_admin: "false"
|
||||
created_at: "1970-01-01 00:00:00+00"
|
||||
is_guest: "false"
|
||||
locked_at: ~
|
||||
primary_user_email_id: ~
|
||||
user_id: 00000000-0000-0000-0000-000000000001
|
||||
|
||||
@@ -12,6 +12,7 @@ user_passwords:
|
||||
users:
|
||||
- can_request_admin: "false"
|
||||
created_at: "1970-01-01 00:00:00+00"
|
||||
is_guest: "false"
|
||||
locked_at: ~
|
||||
primary_user_email_id: ~
|
||||
user_id: 00000000-0000-0000-0000-000000000001
|
||||
|
||||
@@ -30,6 +30,7 @@ compat_sessions:
|
||||
users:
|
||||
- can_request_admin: "false"
|
||||
created_at: "1970-01-01 00:00:00+00"
|
||||
is_guest: "false"
|
||||
locked_at: ~
|
||||
primary_user_email_id: ~
|
||||
user_id: 00000000-0000-0000-0000-000000000001
|
||||
|
||||
@@ -10,6 +10,7 @@ user_unsupported_third_party_ids:
|
||||
users:
|
||||
- can_request_admin: "false"
|
||||
created_at: "1970-01-01 00:00:00+00"
|
||||
is_guest: "false"
|
||||
locked_at: ~
|
||||
primary_user_email_id: ~
|
||||
user_id: 00000000-0000-0000-0000-000000000001
|
||||
|
||||
@@ -36,6 +36,7 @@ upstream_oauth_providers:
|
||||
users:
|
||||
- can_request_admin: "false"
|
||||
created_at: "1970-01-01 00:00:00+00"
|
||||
is_guest: "false"
|
||||
locked_at: ~
|
||||
primary_user_email_id: ~
|
||||
user_id: 00000000-0000-0000-0000-000000000001
|
||||
|
||||
@@ -687,6 +687,7 @@ fn transform_user(
|
||||
created_at: user.creation_ts.into(),
|
||||
locked_at: bool::from(user.deactivated).then_some(user.creation_ts.into()),
|
||||
can_request_admin: bool::from(user.admin),
|
||||
is_guest: bool::from(user.is_guest),
|
||||
};
|
||||
|
||||
let mas_password = user
|
||||
|
||||
@@ -187,8 +187,10 @@ pub struct SynapseUser {
|
||||
pub deactivated: SynapseBool,
|
||||
/// When the user was created
|
||||
pub creation_ts: SecondsTimestamp,
|
||||
// TODO ...
|
||||
// TODO is_guest
|
||||
/// Whether the user is a guest.
|
||||
/// Note that not all numeric user IDs are guests; guests can upgrade their
|
||||
/// account!
|
||||
pub is_guest: SynapseBool,
|
||||
// TODO do we care about upgrade_ts (users who upgraded from guest accounts to real accounts)
|
||||
}
|
||||
|
||||
@@ -335,7 +337,7 @@ impl<'conn> SynapseReader<'conn> {
|
||||
let users: i64 = sqlx::query_scalar(
|
||||
"
|
||||
SELECT COUNT(1) FROM users
|
||||
WHERE appservice_id IS NULL AND is_guest = 0
|
||||
WHERE appservice_id IS NULL
|
||||
",
|
||||
)
|
||||
.fetch_one(&mut *self.txn)
|
||||
@@ -361,9 +363,9 @@ impl<'conn> SynapseReader<'conn> {
|
||||
sqlx::query_as(
|
||||
"
|
||||
SELECT
|
||||
name, password_hash, admin, deactivated, creation_ts
|
||||
name, password_hash, admin, deactivated, creation_ts, is_guest
|
||||
FROM users
|
||||
WHERE appservice_id IS NULL AND is_guest = 0
|
||||
WHERE appservice_id IS NULL
|
||||
",
|
||||
)
|
||||
.fetch(&mut *self.txn)
|
||||
|
||||
@@ -19,5 +19,8 @@ expression: users
|
||||
creation_ts: SecondsTimestamp(
|
||||
2018-06-30T21:26:02Z,
|
||||
),
|
||||
is_guest: SynapseBool(
|
||||
false,
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user