mirror of
https://gitlab.computer.surgery/matrix/grapevine.git
synced 2025-12-16 23:31: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 reqwest::Url;
|
||||||
use ruma::{
|
use ruma::{
|
||||||
api::federation::discovery::OldVerifyKey, OwnedServerName,
|
api::federation::discovery::OldVerifyKey, OwnedServerName,
|
||||||
OwnedServerSigningKeyId, RoomVersionId,
|
OwnedServerSigningKeyId, RoomVersionId, UserId,
|
||||||
};
|
};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use strum::{Display, EnumIter, IntoEnumIterator};
|
use strum::{Display, EnumIter, IntoEnumIterator};
|
||||||
|
|
@ -73,6 +73,7 @@ pub(crate) struct Config {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub(crate) turn: TurnConfig,
|
pub(crate) turn: TurnConfig,
|
||||||
|
|
||||||
|
pub(crate) admin_bot_localpart: String,
|
||||||
pub(crate) emergency_password: Option<String>,
|
pub(crate) emergency_password: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -451,5 +452,15 @@ where
|
||||||
return Err(Error::RegistrationTokenEmpty);
|
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)
|
Ok(config)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1089,6 +1089,21 @@ impl KeyValueDatabase {
|
||||||
version = latest_database_version,
|
version = latest_database_version,
|
||||||
"Loaded database",
|
"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 {
|
} else {
|
||||||
services()
|
services()
|
||||||
.globals
|
.globals
|
||||||
|
|
|
||||||
55
src/error.rs
55
src/error.rs
|
|
@ -136,6 +136,14 @@ pub(crate) enum Config {
|
||||||
|
|
||||||
#[error("registration token must not be empty")]
|
#[error("registration token must not be empty")]
|
||||||
RegistrationTokenEmpty,
|
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
|
/// Errors that can occur while searching for a config file
|
||||||
|
|
@ -183,3 +191,50 @@ pub(crate) enum Serve {
|
||||||
)]
|
)]
|
||||||
FederationSelfTestFailed(#[source] crate::Error),
|
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,
|
power_levels::RoomPowerLevelsEventContent,
|
||||||
topic::RoomTopicEventContent,
|
topic::RoomTopicEventContent,
|
||||||
},
|
},
|
||||||
TimelineEventType,
|
StateEventType, TimelineEventType,
|
||||||
},
|
},
|
||||||
signatures::verify_json,
|
signatures::verify_json,
|
||||||
EventId, MilliSecondsSinceUnixEpoch, OwnedMxcUri, OwnedRoomId,
|
EventId, MilliSecondsSinceUnixEpoch, OwnedMxcUri, OwnedRoomId,
|
||||||
|
|
@ -1528,6 +1528,200 @@ impl Service {
|
||||||
Ok(())
|
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
|
/// Gets the room ID of the admin room
|
||||||
///
|
///
|
||||||
/// Errors are propagated from the database, and will have None if there is
|
/// 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!(
|
let admin_bot_user_id = UserId::parse(format!(
|
||||||
"@{}:{}",
|
"@{}:{}",
|
||||||
if config.conduit_compat {
|
config.admin_bot_localpart, config.server_name,
|
||||||
"conduit"
|
|
||||||
} else {
|
|
||||||
"grapevine"
|
|
||||||
},
|
|
||||||
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 =
|
let admin_bot_room_alias_id =
|
||||||
RoomAliasId::parse(format!("#admins:{}", config.server_name))
|
RoomAliasId::parse(format!("#admins:{}", config.server_name))
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue