Take the localpart instead of the MXID in HomeserverConnection methods

This commit is contained in:
Quentin Gliech
2025-07-18 16:32:23 +02:00
parent fb3137ff38
commit 445f26b8bf
17 changed files with 251 additions and 206 deletions

View File

@@ -166,10 +166,7 @@ pub async fn handler(
let user = repo.user().add(&mut rng, &clock, params.username).await?;
homeserver
.provision_user(&ProvisionRequest::new(
homeserver.mxid(&user.username),
&user.sub,
))
.provision_user(&ProvisionRequest::new(&user.username, &user.sub))
.await
.map_err(RouteError::Homeserver)?;
@@ -222,8 +219,7 @@ mod tests {
assert_eq!(user.username, "alice");
// Check that the user was created on the homeserver
let mxid = state.homeserver_connection.mxid("alice");
let result = state.homeserver_connection.query_user(&mxid).await;
let result = state.homeserver_connection.query_user("alice").await;
assert!(result.is_ok());
}

View File

@@ -83,9 +83,8 @@ pub async fn handler(
.ok_or(RouteError::NotFound(id))?;
// Call the homeserver synchronously to reactivate the user
let mxid = homeserver.mxid(&user.username);
homeserver
.reactivate_user(&mxid)
.reactivate_user(&user.username)
.await
.map_err(RouteError::Homeserver)?;
@@ -127,20 +126,23 @@ mod tests {
// Provision and immediately deactivate the user on the homeserver,
// because this endpoint will try to reactivate it
let mxid = state.homeserver_connection.mxid(&user.username);
state
.homeserver_connection
.provision_user(&ProvisionRequest::new(&mxid, &user.sub))
.provision_user(&ProvisionRequest::new(&user.username, &user.sub))
.await
.unwrap();
state
.homeserver_connection
.delete_user(&mxid, true)
.delete_user(&user.username, true)
.await
.unwrap();
// The user should be deactivated on the homeserver
let mx_user = state.homeserver_connection.query_user(&mxid).await.unwrap();
let mx_user = state
.homeserver_connection
.query_user(&user.username)
.await
.unwrap();
assert!(mx_user.deactivated);
let request = Request::post(format!("/api/admin/v1/users/{}/reactivate", user.id))
@@ -176,10 +178,9 @@ mod tests {
repo.save().await.unwrap();
// Provision the user on the homeserver
let mxid = state.homeserver_connection.mxid(&user.username);
state
.homeserver_connection
.provision_user(&ProvisionRequest::new(&mxid, &user.sub))
.provision_user(&ProvisionRequest::new(&user.username, &user.sub))
.await
.unwrap();

View File

@@ -112,10 +112,9 @@ mod tests {
// Also provision the user on the homeserver, because this endpoint will try to
// reactivate it
let mxid = state.homeserver_connection.mxid(&user.username);
state
.homeserver_connection
.provision_user(&ProvisionRequest::new(&mxid, &user.sub))
.provision_user(&ProvisionRequest::new(&user.username, &user.sub))
.await
.unwrap();
@@ -149,21 +148,24 @@ mod tests {
repo.save().await.unwrap();
// Provision the user on the homeserver
let mxid = state.homeserver_connection.mxid(&user.username);
state
.homeserver_connection
.provision_user(&ProvisionRequest::new(&mxid, &user.sub))
.provision_user(&ProvisionRequest::new(&user.username, &user.sub))
.await
.unwrap();
// but then deactivate it
state
.homeserver_connection
.delete_user(&mxid, true)
.delete_user(&user.username, true)
.await
.unwrap();
// The user should be deactivated on the homeserver
let mx_user = state.homeserver_connection.query_user(&mxid).await.unwrap();
let mx_user = state
.homeserver_connection
.query_user(&user.username)
.await
.unwrap();
assert!(mx_user.deactivated);
let request = Request::post(format!("/api/admin/v1/users/{}/unlock", user.id))
@@ -182,7 +184,11 @@ mod tests {
body["data"]["attributes"]["deactivated_at"],
serde_json::json!(state.clock.now())
);
let mx_user = state.homeserver_connection.query_user(&mxid).await.unwrap();
let mx_user = state
.homeserver_connection
.query_user(&user.username)
.await
.unwrap();
assert!(mx_user.deactivated);
}

View File

@@ -411,7 +411,11 @@ pub(crate) async fn post(
// Now we can create the device on the homeserver, without holding the
// transaction
if let Err(err) = homeserver
.create_device(&user_id, device.as_str(), session.human_name.as_deref())
.create_device(
&user.username,
device.as_str(),
session.human_name.as_deref(),
)
.await
{
// Something went wrong, let's end this session and schedule a device sync
@@ -829,10 +833,9 @@ mod tests {
.add(&mut rng, &state.clock, &user, version, hash, None)
.await
.unwrap();
let mxid = state.homeserver_connection.mxid(&user.username);
state
.homeserver_connection
.provision_user(&ProvisionRequest::new(mxid, &user.sub))
.provision_user(&ProvisionRequest::new(&user.username, &user.sub))
.await
.unwrap();
@@ -1133,10 +1136,9 @@ mod tests {
.await
.unwrap();
let mxid = state.homeserver_connection.mxid(&user.username);
state
.homeserver_connection
.provision_user(&ProvisionRequest::new(mxid, &user.sub))
.provision_user(&ProvisionRequest::new(&user.username, &user.sub))
.await
.unwrap();
@@ -1239,10 +1241,9 @@ mod tests {
let user = repo.user().lock(&state.clock, user).await.unwrap();
repo.save().await.unwrap();
let mxid = state.homeserver_connection.mxid(&user.username);
state
.homeserver_connection
.provision_user(&ProvisionRequest::new(mxid, &user.sub))
.provision_user(&ProvisionRequest::new(&user.username, &user.sub))
.await
.unwrap();

View File

@@ -27,9 +27,9 @@ impl MatrixUser {
conn: &C,
user: &str,
) -> Result<MatrixUser, anyhow::Error> {
let mxid = conn.mxid(user);
let info = conn.query_user(user).await?;
let info = conn.query_user(&mxid).await?;
let mxid = conn.mxid(user);
Ok(MatrixUser {
mxid,

View File

@@ -187,10 +187,9 @@ impl CompatSessionMutations {
.await?;
// Update the device on the homeserver side
let mxid = homeserver.mxid(&user.username);
if let Some(device) = session.device.as_ref() {
homeserver
.update_device_display_name(&mxid, device.as_str(), &input.human_name)
.update_device_display_name(&user.username, device.as_str(), &input.human_name)
.await
.context("Failed to provision device")?;
}

View File

@@ -93,7 +93,6 @@ impl MatrixMutations {
repo.cancel().await?;
let conn = state.homeserver_connection();
let mxid = conn.mxid(&user.username);
if let Some(display_name) = &input.display_name {
// Let's do some basic validation on the display name
@@ -105,11 +104,11 @@ impl MatrixMutations {
return Ok(SetDisplayNamePayload::Invalid);
}
conn.set_displayname(&mxid, display_name)
conn.set_displayname(&user.username, display_name)
.await
.context("Failed to set display name")?;
} else {
conn.unset_displayname(&mxid)
conn.unset_displayname(&user.username)
.await
.context("Failed to unset display name")?;
}

View File

@@ -212,11 +212,10 @@ impl OAuth2SessionMutations {
repo.user().acquire_lock_for_sync(&user).await?;
// Look for devices to provision
let mxid = homeserver.mxid(&user.username);
for scope in &*session.scope {
if let Some(device) = Device::from_scope_token(scope) {
homeserver
.create_device(&mxid, device.as_str(), None)
.create_device(&user.username, device.as_str(), None)
.await
.context("Failed to provision device")?;
}
@@ -331,11 +330,10 @@ impl OAuth2SessionMutations {
.await?;
// Update the device on the homeserver side
let mxid = homeserver.mxid(&user.username);
for scope in &*session.scope {
if let Some(device) = Device::from_scope_token(scope) {
homeserver
.update_device_display_name(&mxid, device.as_str(), &input.human_name)
.update_device_display_name(&user.username, device.as_str(), &input.human_name)
.await
.context("Failed to provision device")?;
}

View File

@@ -586,8 +586,7 @@ impl UserMutations {
};
// Call the homeserver synchronously to reactivate the user
let mxid = matrix.mxid(&user.username);
matrix.reactivate_user(&mxid).await?;
matrix.reactivate_user(&user.username).await?;
// Now reactivate & unlock the user in our database
let user = repo.user().reactivate(user).await?;
@@ -654,9 +653,7 @@ impl UserMutations {
};
let conn = state.homeserver_connection();
let mxid = conn.mxid(&user.username);
conn.allow_cross_signing_reset(&mxid)
conn.allow_cross_signing_reset(&user.username)
.await
.context("Failed to allow cross-signing reset")?;

View File

@@ -529,10 +529,9 @@ async fn test_oauth2_client_credentials(pool: PgPool) {
// XXX: we don't run the task worker here, so even though the addUser mutation
// should have scheduled a job to provision the user, it won't run in the test,
// so we need to do it manually
let mxid = state.homeserver_connection.mxid("alice");
state
.homeserver_connection
.provision_user(&ProvisionRequest::new(mxid, user_id))
.provision_user(&ProvisionRequest::new("alice", user_id))
.await
.unwrap();

View File

@@ -634,10 +634,9 @@ mod tests {
.await
.unwrap();
let mxid = state.homeserver_connection.mxid(&user.username);
state
.homeserver_connection
.provision_user(&ProvisionRequest::new(mxid, &user.sub))
.provision_user(&ProvisionRequest::new(&user.username, &user.sub))
.await
.unwrap();
@@ -835,10 +834,9 @@ mod tests {
.await
.unwrap();
let mxid = state.homeserver_connection.mxid(&user.username);
state
.homeserver_connection
.provision_user(&ProvisionRequest::new(mxid, &user.sub))
.provision_user(&ProvisionRequest::new(&user.username, &user.sub))
.await
.unwrap();

View File

@@ -575,11 +575,14 @@ async fn authorization_code_grant(
.await?;
// Look for device to provision
let mxid = homeserver.mxid(&browser_session.user.username);
for scope in &*session.scope {
if let Some(device) = Device::from_scope_token(scope) {
homeserver
.create_device(&mxid, device.as_str(), Some(&device_name))
.create_device(
&browser_session.user.username,
device.as_str(),
Some(&device_name),
)
.await
.map_err(RouteError::ProvisionDeviceFailed)?;
}
@@ -951,11 +954,10 @@ async fn device_code_grant(
.await?;
// Look for device to provision
let mxid = homeserver.mxid(&browser_session.user.username);
for scope in &*session.scope {
if let Some(device) = Device::from_scope_token(scope) {
homeserver
.create_device(&mxid, device.as_str(), None)
.create_device(&browser_session.user.username, device.as_str(), None)
.await
.map_err(RouteError::ProvisionDeviceFailed)?;
}

View File

@@ -177,12 +177,13 @@ impl HomeserverConnection for SynapseConnection {
skip_all,
fields(
matrix.homeserver = self.homeserver,
matrix.mxid = mxid,
matrix.localpart = localpart,
),
err(Debug),
)]
async fn query_user(&self, mxid: &str) -> Result<MatrixUser, anyhow::Error> {
let encoded_mxid = urlencoding::encode(mxid);
async fn query_user(&self, localpart: &str) -> Result<MatrixUser, anyhow::Error> {
let mxid = self.mxid(localpart);
let encoded_mxid = urlencoding::encode(&mxid);
let response = self
.get(&format!("_synapse/admin/v2/users/{encoded_mxid}"))
@@ -263,7 +264,7 @@ impl HomeserverConnection for SynapseConnection {
skip_all,
fields(
matrix.homeserver = self.homeserver,
matrix.mxid = request.mxid(),
matrix.localpart = request.localpart(),
user.id = request.sub(),
),
err(Debug),
@@ -297,7 +298,8 @@ impl HomeserverConnection for SynapseConnection {
);
});
let encoded_mxid = urlencoding::encode(request.mxid());
let mxid = self.mxid(request.localpart());
let encoded_mxid = urlencoding::encode(&mxid);
let response = self
.put(&format!("_synapse/admin/v2/users/{encoded_mxid}"))
.json(&body)
@@ -322,18 +324,19 @@ impl HomeserverConnection for SynapseConnection {
skip_all,
fields(
matrix.homeserver = self.homeserver,
matrix.mxid = mxid,
matrix.localpart = localpart,
matrix.device_id = device_id,
),
err(Debug),
)]
async fn create_device(
&self,
mxid: &str,
localpart: &str,
device_id: &str,
initial_display_name: Option<&str>,
) -> Result<(), anyhow::Error> {
let encoded_mxid = urlencoding::encode(mxid);
let mxid = self.mxid(localpart);
let encoded_mxid = urlencoding::encode(&mxid);
let response = self
.post(&format!("_synapse/admin/v2/users/{encoded_mxid}/devices"))
@@ -360,7 +363,7 @@ impl HomeserverConnection for SynapseConnection {
// It's annoying, but the POST endpoint doesn't let us set the display name
// of the device, so we have to do it manually.
if let Some(display_name) = initial_display_name {
self.update_device_display_name(mxid, device_id, display_name)
self.update_device_display_name(localpart, device_id, display_name)
.await?;
}
@@ -372,18 +375,19 @@ impl HomeserverConnection for SynapseConnection {
skip_all,
fields(
matrix.homeserver = self.homeserver,
matrix.mxid = mxid,
matrix.localpart = localpart,
matrix.device_id = device_id,
),
err(Debug),
)]
async fn update_device_display_name(
&self,
mxid: &str,
localpart: &str,
device_id: &str,
display_name: &str,
) -> Result<(), anyhow::Error> {
let encoded_mxid = urlencoding::encode(mxid);
let mxid = self.mxid(localpart);
let encoded_mxid = urlencoding::encode(&mxid);
let device_id = urlencoding::encode(device_id);
let response = self
.put(&format!(
@@ -416,13 +420,14 @@ impl HomeserverConnection for SynapseConnection {
skip_all,
fields(
matrix.homeserver = self.homeserver,
matrix.mxid = mxid,
matrix.localpart = localpart,
matrix.device_id = device_id,
),
err(Debug),
)]
async fn delete_device(&self, mxid: &str, device_id: &str) -> Result<(), anyhow::Error> {
let encoded_mxid = urlencoding::encode(mxid);
async fn delete_device(&self, localpart: &str, device_id: &str) -> Result<(), anyhow::Error> {
let mxid = self.mxid(localpart);
let encoded_mxid = urlencoding::encode(&mxid);
let encoded_device_id = urlencoding::encode(device_id);
let response = self
@@ -453,17 +458,18 @@ impl HomeserverConnection for SynapseConnection {
skip_all,
fields(
matrix.homeserver = self.homeserver,
matrix.mxid = mxid,
matrix.localpart = localpart,
),
err(Debug),
)]
async fn sync_devices(
&self,
mxid: &str,
localpart: &str,
devices: HashSet<String>,
) -> Result<(), anyhow::Error> {
// Get the list of current devices
let encoded_mxid = urlencoding::encode(mxid);
let mxid = self.mxid(localpart);
let encoded_mxid = urlencoding::encode(&mxid);
let response = self
.get(&format!("_synapse/admin/v2/users/{encoded_mxid}/devices"))
@@ -519,7 +525,7 @@ impl HomeserverConnection for SynapseConnection {
// Then, create the devices that are missing. There is no batching API to do
// this, so we do this sequentially, which is fine as the API is idempotent.
for device_id in devices.difference(&existing_devices) {
self.create_device(mxid, device_id, None).await?;
self.create_device(localpart, device_id, None).await?;
}
Ok(())
@@ -530,13 +536,14 @@ impl HomeserverConnection for SynapseConnection {
skip_all,
fields(
matrix.homeserver = self.homeserver,
matrix.mxid = mxid,
matrix.localpart = localpart,
erase = erase,
),
err(Debug),
)]
async fn delete_user(&self, mxid: &str, erase: bool) -> Result<(), anyhow::Error> {
let encoded_mxid = urlencoding::encode(mxid);
async fn delete_user(&self, localpart: &str, erase: bool) -> Result<(), anyhow::Error> {
let mxid = self.mxid(localpart);
let encoded_mxid = urlencoding::encode(&mxid);
let response = self
.post(&format!("_synapse/admin/v1/deactivate/{encoded_mxid}"))
@@ -567,12 +574,13 @@ impl HomeserverConnection for SynapseConnection {
skip_all,
fields(
matrix.homeserver = self.homeserver,
matrix.mxid = mxid,
matrix.localpart = localpart,
),
err(Debug),
)]
async fn reactivate_user(&self, mxid: &str) -> Result<(), anyhow::Error> {
let encoded_mxid = urlencoding::encode(mxid);
async fn reactivate_user(&self, localpart: &str) -> Result<(), anyhow::Error> {
let mxid = self.mxid(localpart);
let encoded_mxid = urlencoding::encode(&mxid);
let response = self
.put(&format!("_synapse/admin/v2/users/{encoded_mxid}"))
.json(&SynapseUser {
@@ -599,13 +607,18 @@ impl HomeserverConnection for SynapseConnection {
skip_all,
fields(
matrix.homeserver = self.homeserver,
matrix.mxid = mxid,
matrix.localpart = localpart,
matrix.displayname = displayname,
),
err(Debug),
)]
async fn set_displayname(&self, mxid: &str, displayname: &str) -> Result<(), anyhow::Error> {
let encoded_mxid = urlencoding::encode(mxid);
async fn set_displayname(
&self,
localpart: &str,
displayname: &str,
) -> Result<(), anyhow::Error> {
let mxid = self.mxid(localpart);
let encoded_mxid = urlencoding::encode(&mxid);
let response = self
.put(&format!(
"_matrix/client/v3/profile/{encoded_mxid}/displayname"
@@ -635,12 +648,12 @@ impl HomeserverConnection for SynapseConnection {
skip_all,
fields(
matrix.homeserver = self.homeserver,
matrix.mxid = mxid,
matrix.localpart = localpart,
),
err(Display),
)]
async fn unset_displayname(&self, mxid: &str) -> Result<(), anyhow::Error> {
self.set_displayname(mxid, "").await
async fn unset_displayname(&self, localpart: &str) -> Result<(), anyhow::Error> {
self.set_displayname(localpart, "").await
}
#[tracing::instrument(
@@ -648,12 +661,13 @@ impl HomeserverConnection for SynapseConnection {
skip_all,
fields(
matrix.homeserver = self.homeserver,
matrix.mxid = mxid,
matrix.localpart = localpart,
),
err(Debug),
)]
async fn allow_cross_signing_reset(&self, mxid: &str) -> Result<(), anyhow::Error> {
let encoded_mxid = urlencoding::encode(mxid);
async fn allow_cross_signing_reset(&self, localpart: &str) -> Result<(), anyhow::Error> {
let mxid = self.mxid(localpart);
let encoded_mxid = urlencoding::encode(&mxid);
let response = self
.post(&format!(

View File

@@ -31,7 +31,7 @@ enum FieldAction<T> {
}
pub struct ProvisionRequest {
mxid: String,
localpart: String,
sub: String,
displayname: FieldAction<String>,
avatar_url: FieldAction<String>,
@@ -43,12 +43,12 @@ impl ProvisionRequest {
///
/// # Parameters
///
/// * `mxid` - The Matrix ID to provision.
/// * `localpart` - The localpart of the user to provision.
/// * `sub` - The `sub` of the user, aka the internal ID.
#[must_use]
pub fn new(mxid: impl Into<String>, sub: impl Into<String>) -> Self {
pub fn new(localpart: impl Into<String>, sub: impl Into<String>) -> Self {
Self {
mxid: mxid.into(),
localpart: localpart.into(),
sub: sub.into(),
displayname: FieldAction::DoNothing,
avatar_url: FieldAction::DoNothing,
@@ -62,10 +62,10 @@ impl ProvisionRequest {
&self.sub
}
/// Get the Matrix ID to provision.
/// Get the localpart of the user to provision.
#[must_use]
pub fn mxid(&self) -> &str {
&self.mxid
pub fn localpart(&self) -> &str {
&self.localpart
}
/// Ask to set the displayname of the user.
@@ -211,13 +211,13 @@ pub trait HomeserverConnection: Send + Sync {
///
/// # Parameters
///
/// * `mxid` - The Matrix ID of the user to query.
/// * `localpart` - The localpart of the user to query.
///
/// # Errors
///
/// Returns an error if the homeserver is unreachable or the user does not
/// exist.
async fn query_user(&self, mxid: &str) -> Result<MatrixUser, anyhow::Error>;
async fn query_user(&self, localpart: &str) -> Result<MatrixUser, anyhow::Error>;
/// Provision a user on the homeserver.
///
@@ -247,7 +247,7 @@ pub trait HomeserverConnection: Send + Sync {
///
/// # Parameters
///
/// * `mxid` - The Matrix ID of the user to create a device for.
/// * `localpart` - The localpart of the user to create a device for.
/// * `device_id` - The device ID to create.
///
/// # Errors
@@ -256,7 +256,7 @@ pub trait HomeserverConnection: Send + Sync {
/// not be created.
async fn create_device(
&self,
mxid: &str,
localpart: &str,
device_id: &str,
initial_display_name: Option<&str>,
) -> Result<(), anyhow::Error>;
@@ -265,7 +265,7 @@ pub trait HomeserverConnection: Send + Sync {
///
/// # Parameters
///
/// * `mxid` - The Matrix ID of the user to update a device for.
/// * `localpart` - The localpart of the user to update a device for.
/// * `device_id` - The device ID to update.
/// * `display_name` - The new display name to set
///
@@ -275,7 +275,7 @@ pub trait HomeserverConnection: Send + Sync {
/// not be updated.
async fn update_device_display_name(
&self,
mxid: &str,
localpart: &str,
device_id: &str,
display_name: &str,
) -> Result<(), anyhow::Error>;
@@ -284,90 +284,98 @@ pub trait HomeserverConnection: Send + Sync {
///
/// # Parameters
///
/// * `mxid` - The Matrix ID of the user to delete a device for.
/// * `localpart` - The localpart of the user to delete a device for.
/// * `device_id` - The device ID to delete.
///
/// # Errors
///
/// Returns an error if the homeserver is unreachable or the device could
/// not be deleted.
async fn delete_device(&self, mxid: &str, device_id: &str) -> Result<(), anyhow::Error>;
async fn delete_device(&self, localpart: &str, device_id: &str) -> Result<(), anyhow::Error>;
/// Sync the list of devices of a user with the homeserver.
///
/// # Parameters
///
/// * `mxid` - The Matrix ID of the user to sync the devices for.
/// * `localpart` - The localpart of the user to sync the devices for.
/// * `devices` - The list of devices to sync.
///
/// # Errors
///
/// Returns an error if the homeserver is unreachable or the devices could
/// not be synced.
async fn sync_devices(&self, mxid: &str, devices: HashSet<String>)
-> Result<(), anyhow::Error>;
async fn sync_devices(
&self,
localpart: &str,
devices: HashSet<String>,
) -> Result<(), anyhow::Error>;
/// Delete a user on the homeserver.
///
/// # Parameters
///
/// * `mxid` - The Matrix ID of the user to delete.
/// * `localpart` - The localpart of the user to delete.
/// * `erase` - Whether to ask the homeserver to erase the user's data.
///
/// # Errors
///
/// Returns an error if the homeserver is unreachable or the user could not
/// be deleted.
async fn delete_user(&self, mxid: &str, erase: bool) -> Result<(), anyhow::Error>;
async fn delete_user(&self, localpart: &str, erase: bool) -> Result<(), anyhow::Error>;
/// Reactivate a user on the homeserver.
///
/// # Parameters
///
/// * `mxid` - The Matrix ID of the user to reactivate.
/// * `localpart` - The localpart of the user to reactivate.
///
/// # Errors
///
/// Returns an error if the homeserver is unreachable or the user could not
/// be reactivated.
async fn reactivate_user(&self, mxid: &str) -> Result<(), anyhow::Error>;
async fn reactivate_user(&self, localpart: &str) -> Result<(), anyhow::Error>;
/// Set the displayname of a user on the homeserver.
///
/// # Parameters
///
/// * `mxid` - The Matrix ID of the user to set the displayname for.
/// * `localpart` - The localpart of the user to set the displayname for.
/// * `displayname` - The displayname to set.
///
/// # Errors
///
/// Returns an error if the homeserver is unreachable or the displayname
/// could not be set.
async fn set_displayname(&self, mxid: &str, displayname: &str) -> Result<(), anyhow::Error>;
async fn set_displayname(
&self,
localpart: &str,
displayname: &str,
) -> Result<(), anyhow::Error>;
/// Unset the displayname of a user on the homeserver.
///
/// # Parameters
///
/// * `mxid` - The Matrix ID of the user to unset the displayname for.
/// * `localpart` - The localpart of the user to unset the displayname for.
///
/// # Errors
///
/// Returns an error if the homeserver is unreachable or the displayname
/// could not be unset.
async fn unset_displayname(&self, mxid: &str) -> Result<(), anyhow::Error>;
async fn unset_displayname(&self, localpart: &str) -> Result<(), anyhow::Error>;
/// Temporarily allow a user to reset their cross-signing keys.
///
/// # Parameters
///
/// * `mxid` - The Matrix ID of the user to allow cross-signing key reset
/// * `localpart` - The localpart of the user to allow cross-signing key
/// reset
///
/// # Errors
///
/// Returns an error if the homeserver is unreachable or the cross-signing
/// reset could not be allowed.
async fn allow_cross_signing_reset(&self, mxid: &str) -> Result<(), anyhow::Error>;
async fn allow_cross_signing_reset(&self, localpart: &str) -> Result<(), anyhow::Error>;
}
#[async_trait::async_trait]
@@ -376,8 +384,8 @@ impl<T: HomeserverConnection + Send + Sync + ?Sized> HomeserverConnection for &T
(**self).homeserver()
}
async fn query_user(&self, mxid: &str) -> Result<MatrixUser, anyhow::Error> {
(**self).query_user(mxid).await
async fn query_user(&self, localpart: &str) -> Result<MatrixUser, anyhow::Error> {
(**self).query_user(localpart).await
}
async fn provision_user(&self, request: &ProvisionRequest) -> Result<bool, anyhow::Error> {
@@ -390,56 +398,60 @@ impl<T: HomeserverConnection + Send + Sync + ?Sized> HomeserverConnection for &T
async fn create_device(
&self,
mxid: &str,
localpart: &str,
device_id: &str,
initial_display_name: Option<&str>,
) -> Result<(), anyhow::Error> {
(**self)
.create_device(mxid, device_id, initial_display_name)
.create_device(localpart, device_id, initial_display_name)
.await
}
async fn update_device_display_name(
&self,
mxid: &str,
localpart: &str,
device_id: &str,
display_name: &str,
) -> Result<(), anyhow::Error> {
(**self)
.update_device_display_name(mxid, device_id, display_name)
.update_device_display_name(localpart, device_id, display_name)
.await
}
async fn delete_device(&self, mxid: &str, device_id: &str) -> Result<(), anyhow::Error> {
(**self).delete_device(mxid, device_id).await
async fn delete_device(&self, localpart: &str, device_id: &str) -> Result<(), anyhow::Error> {
(**self).delete_device(localpart, device_id).await
}
async fn sync_devices(
&self,
mxid: &str,
localpart: &str,
devices: HashSet<String>,
) -> Result<(), anyhow::Error> {
(**self).sync_devices(mxid, devices).await
(**self).sync_devices(localpart, devices).await
}
async fn delete_user(&self, mxid: &str, erase: bool) -> Result<(), anyhow::Error> {
(**self).delete_user(mxid, erase).await
async fn delete_user(&self, localpart: &str, erase: bool) -> Result<(), anyhow::Error> {
(**self).delete_user(localpart, erase).await
}
async fn reactivate_user(&self, mxid: &str) -> Result<(), anyhow::Error> {
(**self).reactivate_user(mxid).await
async fn reactivate_user(&self, localpart: &str) -> Result<(), anyhow::Error> {
(**self).reactivate_user(localpart).await
}
async fn set_displayname(&self, mxid: &str, displayname: &str) -> Result<(), anyhow::Error> {
(**self).set_displayname(mxid, displayname).await
async fn set_displayname(
&self,
localpart: &str,
displayname: &str,
) -> Result<(), anyhow::Error> {
(**self).set_displayname(localpart, displayname).await
}
async fn unset_displayname(&self, mxid: &str) -> Result<(), anyhow::Error> {
(**self).unset_displayname(mxid).await
async fn unset_displayname(&self, localpart: &str) -> Result<(), anyhow::Error> {
(**self).unset_displayname(localpart).await
}
async fn allow_cross_signing_reset(&self, mxid: &str) -> Result<(), anyhow::Error> {
(**self).allow_cross_signing_reset(mxid).await
async fn allow_cross_signing_reset(&self, localpart: &str) -> Result<(), anyhow::Error> {
(**self).allow_cross_signing_reset(localpart).await
}
}
@@ -450,8 +462,8 @@ impl<T: HomeserverConnection + ?Sized> HomeserverConnection for Arc<T> {
(**self).homeserver()
}
async fn query_user(&self, mxid: &str) -> Result<MatrixUser, anyhow::Error> {
(**self).query_user(mxid).await
async fn query_user(&self, localpart: &str) -> Result<MatrixUser, anyhow::Error> {
(**self).query_user(localpart).await
}
async fn provision_user(&self, request: &ProvisionRequest) -> Result<bool, anyhow::Error> {
@@ -464,55 +476,59 @@ impl<T: HomeserverConnection + ?Sized> HomeserverConnection for Arc<T> {
async fn create_device(
&self,
mxid: &str,
localpart: &str,
device_id: &str,
initial_display_name: Option<&str>,
) -> Result<(), anyhow::Error> {
(**self)
.create_device(mxid, device_id, initial_display_name)
.create_device(localpart, device_id, initial_display_name)
.await
}
async fn update_device_display_name(
&self,
mxid: &str,
localpart: &str,
device_id: &str,
display_name: &str,
) -> Result<(), anyhow::Error> {
(**self)
.update_device_display_name(mxid, device_id, display_name)
.update_device_display_name(localpart, device_id, display_name)
.await
}
async fn delete_device(&self, mxid: &str, device_id: &str) -> Result<(), anyhow::Error> {
(**self).delete_device(mxid, device_id).await
async fn delete_device(&self, localpart: &str, device_id: &str) -> Result<(), anyhow::Error> {
(**self).delete_device(localpart, device_id).await
}
async fn sync_devices(
&self,
mxid: &str,
localpart: &str,
devices: HashSet<String>,
) -> Result<(), anyhow::Error> {
(**self).sync_devices(mxid, devices).await
(**self).sync_devices(localpart, devices).await
}
async fn delete_user(&self, mxid: &str, erase: bool) -> Result<(), anyhow::Error> {
(**self).delete_user(mxid, erase).await
async fn delete_user(&self, localpart: &str, erase: bool) -> Result<(), anyhow::Error> {
(**self).delete_user(localpart, erase).await
}
async fn reactivate_user(&self, mxid: &str) -> Result<(), anyhow::Error> {
(**self).reactivate_user(mxid).await
async fn reactivate_user(&self, localpart: &str) -> Result<(), anyhow::Error> {
(**self).reactivate_user(localpart).await
}
async fn set_displayname(&self, mxid: &str, displayname: &str) -> Result<(), anyhow::Error> {
(**self).set_displayname(mxid, displayname).await
async fn set_displayname(
&self,
localpart: &str,
displayname: &str,
) -> Result<(), anyhow::Error> {
(**self).set_displayname(localpart, displayname).await
}
async fn unset_displayname(&self, mxid: &str) -> Result<(), anyhow::Error> {
(**self).unset_displayname(mxid).await
async fn unset_displayname(&self, localpart: &str) -> Result<(), anyhow::Error> {
(**self).unset_displayname(localpart).await
}
async fn allow_cross_signing_reset(&self, mxid: &str) -> Result<(), anyhow::Error> {
(**self).allow_cross_signing_reset(mxid).await
async fn allow_cross_signing_reset(&self, localpart: &str) -> Result<(), anyhow::Error> {
(**self).allow_cross_signing_reset(localpart).await
}
}

View File

@@ -54,9 +54,10 @@ impl crate::HomeserverConnection for HomeserverConnection {
&self.homeserver
}
async fn query_user(&self, mxid: &str) -> Result<MatrixUser, anyhow::Error> {
async fn query_user(&self, localpart: &str) -> Result<MatrixUser, anyhow::Error> {
let mxid = self.mxid(localpart);
let users = self.users.read().await;
let user = users.get(mxid).context("User not found")?;
let user = users.get(&mxid).context("User not found")?;
Ok(MatrixUser {
displayname: user.displayname.clone(),
avatar_url: user.avatar_url.clone(),
@@ -66,8 +67,9 @@ impl crate::HomeserverConnection for HomeserverConnection {
async fn provision_user(&self, request: &ProvisionRequest) -> Result<bool, anyhow::Error> {
let mut users = self.users.write().await;
let inserted = !users.contains_key(request.mxid());
let user = users.entry(request.mxid().to_owned()).or_insert(MockUser {
let mxid = self.mxid(request.localpart());
let inserted = !users.contains_key(&mxid);
let user = users.entry(mxid).or_insert(MockUser {
sub: request.sub().to_owned(),
avatar_url: None,
displayname: None,
@@ -109,49 +111,54 @@ impl crate::HomeserverConnection for HomeserverConnection {
async fn create_device(
&self,
mxid: &str,
localpart: &str,
device_id: &str,
_initial_display_name: Option<&str>,
) -> Result<(), anyhow::Error> {
let mxid = self.mxid(localpart);
let mut users = self.users.write().await;
let user = users.get_mut(mxid).context("User not found")?;
let user = users.get_mut(&mxid).context("User not found")?;
user.devices.insert(device_id.to_owned());
Ok(())
}
async fn update_device_display_name(
&self,
mxid: &str,
localpart: &str,
device_id: &str,
_display_name: &str,
) -> Result<(), anyhow::Error> {
let mxid = self.mxid(localpart);
let mut users = self.users.write().await;
let user = users.get_mut(mxid).context("User not found")?;
let user = users.get_mut(&mxid).context("User not found")?;
user.devices.get(device_id).context("Device not found")?;
Ok(())
}
async fn delete_device(&self, mxid: &str, device_id: &str) -> Result<(), anyhow::Error> {
async fn delete_device(&self, localpart: &str, device_id: &str) -> Result<(), anyhow::Error> {
let mxid = self.mxid(localpart);
let mut users = self.users.write().await;
let user = users.get_mut(mxid).context("User not found")?;
let user = users.get_mut(&mxid).context("User not found")?;
user.devices.remove(device_id);
Ok(())
}
async fn sync_devices(
&self,
mxid: &str,
localpart: &str,
devices: HashSet<String>,
) -> Result<(), anyhow::Error> {
let mxid = self.mxid(localpart);
let mut users = self.users.write().await;
let user = users.get_mut(mxid).context("User not found")?;
let user = users.get_mut(&mxid).context("User not found")?;
user.devices = devices;
Ok(())
}
async fn delete_user(&self, mxid: &str, erase: bool) -> Result<(), anyhow::Error> {
async fn delete_user(&self, localpart: &str, erase: bool) -> Result<(), anyhow::Error> {
let mxid = self.mxid(localpart);
let mut users = self.users.write().await;
let user = users.get_mut(mxid).context("User not found")?;
let user = users.get_mut(&mxid).context("User not found")?;
user.devices.clear();
user.emails = None;
user.deactivated = true;
@@ -163,31 +170,39 @@ impl crate::HomeserverConnection for HomeserverConnection {
Ok(())
}
async fn reactivate_user(&self, mxid: &str) -> Result<(), anyhow::Error> {
async fn reactivate_user(&self, localpart: &str) -> Result<(), anyhow::Error> {
let mxid = self.mxid(localpart);
let mut users = self.users.write().await;
let user = users.get_mut(mxid).context("User not found")?;
let user = users.get_mut(&mxid).context("User not found")?;
user.deactivated = false;
Ok(())
}
async fn set_displayname(&self, mxid: &str, displayname: &str) -> Result<(), anyhow::Error> {
async fn set_displayname(
&self,
localpart: &str,
displayname: &str,
) -> Result<(), anyhow::Error> {
let mxid = self.mxid(localpart);
let mut users = self.users.write().await;
let user = users.get_mut(mxid).context("User not found")?;
let user = users.get_mut(&mxid).context("User not found")?;
user.displayname = Some(displayname.to_owned());
Ok(())
}
async fn unset_displayname(&self, mxid: &str) -> Result<(), anyhow::Error> {
async fn unset_displayname(&self, localpart: &str) -> Result<(), anyhow::Error> {
let mxid = self.mxid(localpart);
let mut users = self.users.write().await;
let user = users.get_mut(mxid).context("User not found")?;
let user = users.get_mut(&mxid).context("User not found")?;
user.displayname = None;
Ok(())
}
async fn allow_cross_signing_reset(&self, mxid: &str) -> Result<(), anyhow::Error> {
async fn allow_cross_signing_reset(&self, localpart: &str) -> Result<(), anyhow::Error> {
let mxid = self.mxid(localpart);
let mut users = self.users.write().await;
let user = users.get_mut(mxid).context("User not found")?;
let user = users.get_mut(&mxid).context("User not found")?;
user.cross_signing_reset_allowed = true;
Ok(())
}
@@ -207,11 +222,11 @@ mod tests {
assert_eq!(conn.homeserver(), "example.org");
assert_eq!(conn.mxid("test"), mxid);
assert!(conn.query_user(mxid).await.is_err());
assert!(conn.create_device(mxid, device, None).await.is_err());
assert!(conn.delete_device(mxid, device).await.is_err());
assert!(conn.query_user("test").await.is_err());
assert!(conn.create_device("test", device, None).await.is_err());
assert!(conn.delete_device("test", device).await.is_err());
let request = ProvisionRequest::new("@test:example.org", "test")
let request = ProvisionRequest::new("test", "test")
.set_displayname("Test User".into())
.set_avatar_url("mxc://example.org/1234567890".into())
.set_emails(vec!["test@example.org".to_owned()]);
@@ -219,33 +234,33 @@ mod tests {
let inserted = conn.provision_user(&request).await.unwrap();
assert!(inserted);
let user = conn.query_user(mxid).await.unwrap();
let user = conn.query_user("test").await.unwrap();
assert_eq!(user.displayname, Some("Test User".into()));
assert_eq!(user.avatar_url, Some("mxc://example.org/1234567890".into()));
// Set the displayname again
assert!(conn.set_displayname(mxid, "John").await.is_ok());
assert!(conn.set_displayname("test", "John").await.is_ok());
let user = conn.query_user(mxid).await.unwrap();
let user = conn.query_user("test").await.unwrap();
assert_eq!(user.displayname, Some("John".into()));
// Unset the displayname
assert!(conn.unset_displayname(mxid).await.is_ok());
assert!(conn.unset_displayname("test").await.is_ok());
let user = conn.query_user(mxid).await.unwrap();
let user = conn.query_user("test").await.unwrap();
assert_eq!(user.displayname, None);
// Deleting a non-existent device should not fail
assert!(conn.delete_device(mxid, device).await.is_ok());
assert!(conn.delete_device("test", device).await.is_ok());
// Create the device
assert!(conn.create_device(mxid, device, None).await.is_ok());
assert!(conn.create_device("test", device, None).await.is_ok());
// Create the same device again
assert!(conn.create_device(mxid, device, None).await.is_ok());
assert!(conn.create_device("test", device, None).await.is_ok());
// XXX: there is no API to query devices yet in the trait
// Delete the device
assert!(conn.delete_device(mxid, device).await.is_ok());
assert!(conn.delete_device("test", device).await.is_ok());
// The user we just created should be not available
assert!(!conn.is_localpart_available("test").await.unwrap());

View File

@@ -28,8 +28,8 @@ impl<C: HomeserverConnection> HomeserverConnection for ReadOnlyHomeserverConnect
self.inner.homeserver()
}
async fn query_user(&self, mxid: &str) -> Result<MatrixUser, anyhow::Error> {
self.inner.query_user(mxid).await
async fn query_user(&self, localpart: &str) -> Result<MatrixUser, anyhow::Error> {
self.inner.query_user(localpart).await
}
async fn provision_user(&self, _request: &ProvisionRequest) -> Result<bool, anyhow::Error> {
@@ -42,7 +42,7 @@ impl<C: HomeserverConnection> HomeserverConnection for ReadOnlyHomeserverConnect
async fn create_device(
&self,
_mxid: &str,
_localpart: &str,
_device_id: &str,
_initial_display_name: Option<&str>,
) -> Result<(), anyhow::Error> {
@@ -51,42 +51,46 @@ impl<C: HomeserverConnection> HomeserverConnection for ReadOnlyHomeserverConnect
async fn update_device_display_name(
&self,
_mxid: &str,
_localpart: &str,
_device_id: &str,
_display_name: &str,
) -> Result<(), anyhow::Error> {
anyhow::bail!("Device display name update is not supported in read-only mode");
}
async fn delete_device(&self, _mxid: &str, _device_id: &str) -> Result<(), anyhow::Error> {
async fn delete_device(&self, _localpart: &str, _device_id: &str) -> Result<(), anyhow::Error> {
anyhow::bail!("Device deletion is not supported in read-only mode");
}
async fn sync_devices(
&self,
_mxid: &str,
_localpart: &str,
_devices: HashSet<String>,
) -> Result<(), anyhow::Error> {
anyhow::bail!("Device synchronization is not supported in read-only mode");
}
async fn delete_user(&self, _mxid: &str, _erase: bool) -> Result<(), anyhow::Error> {
async fn delete_user(&self, _localpart: &str, _erase: bool) -> Result<(), anyhow::Error> {
anyhow::bail!("User deletion is not supported in read-only mode");
}
async fn reactivate_user(&self, _mxid: &str) -> Result<(), anyhow::Error> {
async fn reactivate_user(&self, _localpart: &str) -> Result<(), anyhow::Error> {
anyhow::bail!("User reactivation is not supported in read-only mode");
}
async fn set_displayname(&self, _mxid: &str, _displayname: &str) -> Result<(), anyhow::Error> {
async fn set_displayname(
&self,
_localpart: &str,
_displayname: &str,
) -> Result<(), anyhow::Error> {
anyhow::bail!("User displayname update is not supported in read-only mode");
}
async fn unset_displayname(&self, _mxid: &str) -> Result<(), anyhow::Error> {
async fn unset_displayname(&self, _localpart: &str) -> Result<(), anyhow::Error> {
anyhow::bail!("User displayname update is not supported in read-only mode");
}
async fn allow_cross_signing_reset(&self, _mxid: &str) -> Result<(), anyhow::Error> {
async fn allow_cross_signing_reset(&self, _localpart: &str) -> Result<(), anyhow::Error> {
anyhow::bail!("Allowing cross-signing reset is not supported in read-only mode");
}
}

View File

@@ -51,7 +51,6 @@ impl RunnableJob for ProvisionUserJob {
.context("User not found")
.map_err(JobError::fail)?;
let mxid = matrix.mxid(&user.username);
let emails = repo
.user_email()
.all(&user)
@@ -60,7 +59,8 @@ impl RunnableJob for ProvisionUserJob {
.into_iter()
.map(|email| email.email)
.collect();
let mut request = ProvisionRequest::new(mxid.clone(), user.sub.clone()).set_emails(emails);
let mut request =
ProvisionRequest::new(user.username.clone(), user.sub.clone()).set_emails(emails);
if let Some(display_name) = self.display_name_to_set() {
request = request.set_displayname(display_name.to_owned());
@@ -71,6 +71,7 @@ impl RunnableJob for ProvisionUserJob {
.await
.map_err(JobError::retry)?;
let mxid = matrix.mxid(&user.username);
if created {
info!(%user.id, %mxid, "User created");
} else {
@@ -241,9 +242,8 @@ impl RunnableJob for SyncDevicesJob {
}
}
let mxid = matrix.mxid(&user.username);
matrix
.sync_devices(&mxid, devices)
.sync_devices(&user.username, devices)
.await
.map_err(JobError::retry)?;