mirror of
https://gitlab.computer.surgery/matrix/grapevine.git
synced 2025-12-16 15:21:24 +01:00
allow configuring the admin bot localpart
This commit is contained in:
parent
9589382cb8
commit
bb52753a92
5 changed files with 279 additions and 9 deletions
|
|
@ -10,7 +10,7 @@ use once_cell::sync::Lazy;
|
|||
use reqwest::Url;
|
||||
use ruma::{
|
||||
api::federation::discovery::OldVerifyKey, OwnedServerName,
|
||||
OwnedServerSigningKeyId, RoomVersionId,
|
||||
OwnedServerSigningKeyId, RoomVersionId, UserId,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use strum::{Display, EnumIter, IntoEnumIterator};
|
||||
|
|
@ -73,6 +73,7 @@ pub(crate) struct Config {
|
|||
#[serde(default)]
|
||||
pub(crate) turn: TurnConfig,
|
||||
|
||||
pub(crate) admin_bot_localpart: String,
|
||||
pub(crate) emergency_password: Option<String>,
|
||||
}
|
||||
|
||||
|
|
@ -451,5 +452,15 @@ where
|
|||
return Err(Error::RegistrationTokenEmpty);
|
||||
}
|
||||
|
||||
let admin_bot_id = UserId::parse(format!(
|
||||
"@{}:{}",
|
||||
config.admin_bot_localpart, config.server_name
|
||||
))
|
||||
.map_err(Error::InvalidAdminBotId)?;
|
||||
|
||||
if admin_bot_id.is_historical() {
|
||||
return Err(Error::HistoricalAdminBotLocalpart);
|
||||
}
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1089,6 +1089,21 @@ impl KeyValueDatabase {
|
|||
version = latest_database_version,
|
||||
"Loaded database",
|
||||
);
|
||||
|
||||
// TODO: Return this error directly instead of map_err hacks
|
||||
services().admin.apply_admin_bot_localpart().await.map_err(
|
||||
|e| {
|
||||
error!(
|
||||
error = %crate::error::DisplayWithSources{
|
||||
error: &e,
|
||||
infix: ": ",
|
||||
},
|
||||
"failed to apply admin bot localpart"
|
||||
);
|
||||
|
||||
Error::BadConfig("failed to apply admin bot localpart")
|
||||
},
|
||||
)?;
|
||||
} else {
|
||||
services()
|
||||
.globals
|
||||
|
|
|
|||
55
src/error.rs
55
src/error.rs
|
|
@ -136,6 +136,14 @@ pub(crate) enum Config {
|
|||
|
||||
#[error("registration token must not be empty")]
|
||||
RegistrationTokenEmpty,
|
||||
|
||||
#[error("invalid admin bot ID")]
|
||||
InvalidAdminBotId(#[source] ruma::IdParseError),
|
||||
|
||||
#[error(
|
||||
"admin bot ID is historical (i.e. contains deprecated characters)"
|
||||
)]
|
||||
HistoricalAdminBotLocalpart,
|
||||
}
|
||||
|
||||
/// Errors that can occur while searching for a config file
|
||||
|
|
@ -183,3 +191,50 @@ pub(crate) enum Serve {
|
|||
)]
|
||||
FederationSelfTestFailed(#[source] crate::Error),
|
||||
}
|
||||
|
||||
/// Errors applying the admin bot user localpart
|
||||
// Missing docs are allowed here since that kind of information should be
|
||||
// encoded in the error messages themselves anyway.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Error, Debug)]
|
||||
pub(crate) enum ApplyAdminBotLocalpart {
|
||||
#[error("failed to resolve the admin room ID")]
|
||||
ResolveAdminRoomId(#[source] crate::utils::error::Error),
|
||||
|
||||
#[error("admin room alias does not exist")]
|
||||
MissingAdminRoomAlias,
|
||||
|
||||
#[error("failed to check if {1} is a member of the room")]
|
||||
CheckMembership(#[source] crate::utils::error::Error, OwnedUserId),
|
||||
|
||||
#[error("failed to read saved admin bot localpart")]
|
||||
ReadSavedAdminBotLocalpart(#[source] crate::utils::error::Error),
|
||||
|
||||
#[error("failed to create {1}")]
|
||||
CreateUser(#[source] crate::utils::error::Error, OwnedUserId),
|
||||
|
||||
#[error("{1} failed to invite {2} to the room")]
|
||||
PreviousInviteNew(
|
||||
#[source] crate::utils::error::Error,
|
||||
OwnedUserId,
|
||||
OwnedUserId,
|
||||
),
|
||||
|
||||
#[error("{1} failed to join the room")]
|
||||
NewJoin(#[source] crate::utils::error::Error, OwnedUserId),
|
||||
|
||||
#[error("failed to get the room state")]
|
||||
GetState(#[source] crate::utils::error::Error),
|
||||
|
||||
#[error("failed parse power level state")]
|
||||
ParsePowerLevels(#[source] serde_json::Error),
|
||||
|
||||
#[error("{1} failed to set the room state")]
|
||||
SetState(#[source] crate::utils::error::Error, OwnedUserId),
|
||||
|
||||
#[error("{1} failed to leave the room")]
|
||||
PreviousLeave(#[source] crate::utils::error::Error, OwnedUserId),
|
||||
|
||||
#[error("failed to save the new admin bot localpart")]
|
||||
SaveAdminBotLocalpart(#[source] crate::utils::error::Error),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ use ruma::{
|
|||
power_levels::RoomPowerLevelsEventContent,
|
||||
topic::RoomTopicEventContent,
|
||||
},
|
||||
TimelineEventType,
|
||||
StateEventType, TimelineEventType,
|
||||
},
|
||||
signatures::verify_json,
|
||||
EventId, MilliSecondsSinceUnixEpoch, OwnedMxcUri, OwnedRoomId,
|
||||
|
|
@ -1528,6 +1528,200 @@ impl Service {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
pub(crate) async fn apply_admin_bot_localpart(
|
||||
&self,
|
||||
) -> Result<(), crate::error::ApplyAdminBotLocalpart> {
|
||||
use crate::error::ApplyAdminBotLocalpart as Error;
|
||||
|
||||
let cur_user_id = &services().globals.admin_bot_user_id;
|
||||
|
||||
let admin_room_id = self
|
||||
.get_admin_room()
|
||||
.map_err(Error::ResolveAdminRoomId)?
|
||||
.ok_or(Error::MissingAdminRoomAlias)?;
|
||||
|
||||
let cur_in_room = services()
|
||||
.rooms
|
||||
.state_cache
|
||||
.is_joined(cur_user_id, &admin_room_id)
|
||||
.map_err(|e| Error::CheckMembership(e, cur_user_id.clone()))?;
|
||||
|
||||
let previous_user_id = {
|
||||
let localpart = services()
|
||||
.globals
|
||||
.saved_admin_bot_localpart()
|
||||
.map_err(Error::ReadSavedAdminBotLocalpart)?
|
||||
.expect("admin bot localpart should have been saved by now");
|
||||
|
||||
let server_name = services().globals.server_name();
|
||||
|
||||
UserId::parse(format!("@{localpart}:{server_name}")).expect(
|
||||
"localpart and server_name should have been validated by now",
|
||||
)
|
||||
};
|
||||
|
||||
if cur_in_room {
|
||||
// Don't need to make any changes
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Rename variable because now we know it's a new value
|
||||
let new_user_id = cur_user_id;
|
||||
|
||||
// Create new
|
||||
services()
|
||||
.users
|
||||
.create(new_user_id, None)
|
||||
.map_err(|e| Error::CreateUser(e, new_user_id.clone()))?;
|
||||
|
||||
// Acquire lock
|
||||
let room_token = services()
|
||||
.globals
|
||||
.roomid_mutex_state
|
||||
.lock_key(admin_room_id.clone())
|
||||
.await;
|
||||
|
||||
// Make previous invite new
|
||||
services()
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomMember,
|
||||
content: to_raw_value(&RoomMemberEventContent {
|
||||
membership: MembershipState::Invite,
|
||||
displayname: None,
|
||||
avatar_url: None,
|
||||
is_direct: None,
|
||||
third_party_invite: None,
|
||||
blurhash: None,
|
||||
reason: None,
|
||||
join_authorized_via_users_server: None,
|
||||
})
|
||||
.expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: Some(new_user_id.to_string()),
|
||||
redacts: None,
|
||||
},
|
||||
&previous_user_id,
|
||||
&room_token,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
Error::PreviousInviteNew(
|
||||
e,
|
||||
previous_user_id.clone(),
|
||||
new_user_id.clone(),
|
||||
)
|
||||
})?;
|
||||
|
||||
// Make new join
|
||||
services()
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomMember,
|
||||
content: to_raw_value(&RoomMemberEventContent {
|
||||
membership: MembershipState::Join,
|
||||
displayname: None,
|
||||
avatar_url: None,
|
||||
is_direct: None,
|
||||
third_party_invite: None,
|
||||
blurhash: None,
|
||||
reason: None,
|
||||
join_authorized_via_users_server: None,
|
||||
})
|
||||
.expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: Some(new_user_id.to_string()),
|
||||
redacts: None,
|
||||
},
|
||||
new_user_id,
|
||||
&room_token,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| Error::NewJoin(e, new_user_id.clone()))?;
|
||||
|
||||
// Get current power_levels
|
||||
let mut power_levels: RoomPowerLevelsEventContent = services()
|
||||
.rooms
|
||||
.state_accessor
|
||||
.room_state_get(
|
||||
&admin_room_id,
|
||||
&StateEventType::RoomPowerLevels,
|
||||
"",
|
||||
)
|
||||
.map_err(Error::GetState)?
|
||||
.map(|ev| serde_json::from_str(ev.content.get()))
|
||||
.transpose()
|
||||
.map_err(Error::ParsePowerLevels)?
|
||||
.unwrap_or_default();
|
||||
|
||||
// Make previous promote new and demote self
|
||||
services()
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomPowerLevels,
|
||||
content: to_raw_value({
|
||||
power_levels.users.remove(&previous_user_id);
|
||||
power_levels
|
||||
.users
|
||||
.insert(new_user_id.clone(), 100.into());
|
||||
|
||||
&power_levels
|
||||
})
|
||||
.expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: Some(String::new()),
|
||||
redacts: None,
|
||||
},
|
||||
&previous_user_id,
|
||||
&room_token,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| Error::SetState(e, previous_user_id.clone()))?;
|
||||
|
||||
// Make previous leave
|
||||
services()
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomMember,
|
||||
content: to_raw_value(&RoomMemberEventContent {
|
||||
membership: MembershipState::Leave,
|
||||
displayname: None,
|
||||
avatar_url: None,
|
||||
is_direct: None,
|
||||
third_party_invite: None,
|
||||
blurhash: None,
|
||||
reason: None,
|
||||
join_authorized_via_users_server: None,
|
||||
})
|
||||
.expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: Some(previous_user_id.to_string()),
|
||||
redacts: None,
|
||||
},
|
||||
&previous_user_id,
|
||||
&room_token,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| Error::PreviousLeave(e, previous_user_id.clone()))?;
|
||||
|
||||
// Save the new localpart
|
||||
services()
|
||||
.globals
|
||||
.save_admin_bot_localpart(new_user_id.localpart())
|
||||
.map_err(Error::SaveAdminBotLocalpart)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Gets the room ID of the admin room
|
||||
///
|
||||
/// Errors are propagated from the database, and will have None if there is
|
||||
|
|
|
|||
|
|
@ -234,14 +234,9 @@ impl Service {
|
|||
|
||||
let admin_bot_user_id = UserId::parse(format!(
|
||||
"@{}:{}",
|
||||
if config.conduit_compat {
|
||||
"conduit"
|
||||
} else {
|
||||
"grapevine"
|
||||
},
|
||||
config.server_name,
|
||||
config.admin_bot_localpart, config.server_name,
|
||||
))
|
||||
.expect("admin bot user ID should be valid");
|
||||
.map_err(|_| Error::BadConfig("Invalid admin bot localpart"))?;
|
||||
|
||||
let admin_bot_room_alias_id =
|
||||
RoomAliasId::parse(format!("#admins:{}", config.server_name))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue