fix: restrict who can remove aliases
Previously, anyone could remove any local alias, meaning that someone could re-route a popular alias elsewhere Now, only the creator of the alias, users who can set canonical aliases for the room, server admins and the server user can delete aliases added some additional changes/fixes to adapt to our codebase Co-authored-by: strawberry <strawberry@puppygock.gay> Signed-off-by: strawberry <strawberry@puppygock.gay>
This commit is contained in:
parent
26d103d314
commit
f712c0cefb
7 changed files with 151 additions and 36 deletions
|
@ -1,10 +1,10 @@
|
|||
use ruma::{api::client::error::ErrorKind, OwnedRoomAliasId, OwnedRoomId, RoomAliasId, RoomId};
|
||||
use ruma::{api::client::error::ErrorKind, OwnedRoomAliasId, OwnedRoomId, OwnedUserId, RoomAliasId, RoomId, UserId};
|
||||
|
||||
use crate::{services, utils, Error, KeyValueDatabase, Result};
|
||||
|
||||
pub trait Data: Send + Sync {
|
||||
/// Creates or updates the alias to the given room id.
|
||||
fn set_alias(&self, alias: &RoomAliasId, room_id: &RoomId) -> Result<()>;
|
||||
fn set_alias(&self, alias: &RoomAliasId, room_id: &RoomId, user_id: &UserId) -> Result<()>;
|
||||
|
||||
/// Forgets about an alias. Returns an error if the alias did not exist.
|
||||
fn remove_alias(&self, alias: &RoomAliasId) -> Result<()>;
|
||||
|
@ -12,6 +12,9 @@ pub trait Data: Send + Sync {
|
|||
/// Looks up the roomid for the given alias.
|
||||
fn resolve_local_alias(&self, alias: &RoomAliasId) -> Result<Option<OwnedRoomId>>;
|
||||
|
||||
/// Finds the user who assigned the given alias to a room
|
||||
fn who_created_alias(&self, alias: &RoomAliasId) -> Result<Option<OwnedUserId>>;
|
||||
|
||||
/// Returns all local aliases that point to the given room
|
||||
fn local_aliases_for_room<'a>(
|
||||
&'a self, room_id: &RoomId,
|
||||
|
@ -22,13 +25,19 @@ pub trait Data: Send + Sync {
|
|||
}
|
||||
|
||||
impl Data for KeyValueDatabase {
|
||||
fn set_alias(&self, alias: &RoomAliasId, room_id: &RoomId) -> Result<()> {
|
||||
fn set_alias(&self, alias: &RoomAliasId, room_id: &RoomId, user_id: &UserId) -> Result<()> {
|
||||
// Comes first as we don't want a stuck alias
|
||||
self.alias_userid
|
||||
.insert(alias.alias().as_bytes(), user_id.as_bytes())?;
|
||||
|
||||
self.alias_roomid
|
||||
.insert(alias.alias().as_bytes(), room_id.as_bytes())?;
|
||||
|
||||
let mut aliasid = room_id.as_bytes().to_vec();
|
||||
aliasid.push(0xFF);
|
||||
aliasid.extend_from_slice(&services().globals.next_count()?.to_be_bytes());
|
||||
self.aliasid_alias.insert(&aliasid, alias.as_bytes())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -40,10 +49,14 @@ impl Data for KeyValueDatabase {
|
|||
for (key, _) in self.aliasid_alias.scan_prefix(prefix) {
|
||||
self.aliasid_alias.remove(&key)?;
|
||||
}
|
||||
|
||||
self.alias_roomid.remove(alias.alias().as_bytes())?;
|
||||
|
||||
self.alias_userid.remove(alias.alias().as_bytes())?;
|
||||
} else {
|
||||
return Err(Error::BadRequest(ErrorKind::NotFound, "Alias does not exist."));
|
||||
return Err(Error::BadRequest(ErrorKind::NotFound, "Alias does not exist or is invalid."));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -60,6 +73,19 @@ impl Data for KeyValueDatabase {
|
|||
.transpose()
|
||||
}
|
||||
|
||||
fn who_created_alias(&self, alias: &RoomAliasId) -> Result<Option<OwnedUserId>> {
|
||||
self.alias_userid
|
||||
.get(alias.alias().as_bytes())?
|
||||
.map(|bytes| {
|
||||
UserId::parse(
|
||||
utils::string_from_bytes(&bytes)
|
||||
.map_err(|_| Error::bad_database("User ID in alias_userid is invalid unicode."))?,
|
||||
)
|
||||
.map_err(|_| Error::bad_database("User ID in alias_roomid is invalid."))
|
||||
})
|
||||
.transpose()
|
||||
}
|
||||
|
||||
fn local_aliases_for_room<'a>(
|
||||
&'a self, room_id: &RoomId,
|
||||
) -> Box<dyn Iterator<Item = Result<OwnedRoomAliasId>> + 'a> {
|
||||
|
|
|
@ -3,9 +3,16 @@ mod data;
|
|||
use std::sync::Arc;
|
||||
|
||||
use data::Data;
|
||||
use ruma::{OwnedRoomAliasId, OwnedRoomId, RoomAliasId, RoomId};
|
||||
use ruma::{
|
||||
api::client::error::ErrorKind,
|
||||
events::{
|
||||
room::power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
|
||||
StateEventType,
|
||||
},
|
||||
OwnedRoomAliasId, OwnedRoomId, RoomAliasId, RoomId, UserId,
|
||||
};
|
||||
|
||||
use crate::Result;
|
||||
use crate::{services, Error, Result};
|
||||
|
||||
pub struct Service {
|
||||
pub db: Arc<dyn Data>,
|
||||
|
@ -13,10 +20,21 @@ pub struct Service {
|
|||
|
||||
impl Service {
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn set_alias(&self, alias: &RoomAliasId, room_id: &RoomId) -> Result<()> { self.db.set_alias(alias, room_id) }
|
||||
pub fn set_alias(&self, alias: &RoomAliasId, room_id: &RoomId, user_id: &UserId) -> Result<()> {
|
||||
self.db.set_alias(alias, room_id, user_id)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn remove_alias(&self, alias: &RoomAliasId) -> Result<()> { self.db.remove_alias(alias) }
|
||||
pub async fn remove_alias(&self, alias: &RoomAliasId, user_id: &UserId) -> Result<()> {
|
||||
if self.user_can_remove_alias(alias, user_id).await? {
|
||||
self.db.remove_alias(alias)
|
||||
} else {
|
||||
Err(Error::BadRequest(
|
||||
ErrorKind::forbidden(),
|
||||
"User is not permitted to remove this alias.",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn resolve_local_alias(&self, alias: &RoomAliasId) -> Result<Option<OwnedRoomId>> {
|
||||
|
@ -34,4 +52,50 @@ impl Service {
|
|||
pub fn all_local_aliases<'a>(&'a self) -> Box<dyn Iterator<Item = Result<(OwnedRoomId, String)>> + 'a> {
|
||||
self.db.all_local_aliases()
|
||||
}
|
||||
|
||||
async fn user_can_remove_alias(&self, alias: &RoomAliasId, user_id: &UserId) -> Result<bool> {
|
||||
let Some(room_id) = self.resolve_local_alias(alias)? else {
|
||||
return Err(Error::BadRequest(ErrorKind::NotFound, "Alias not found."));
|
||||
};
|
||||
|
||||
let server_user =
|
||||
UserId::parse_with_server_name(String::from("conduit"), services().globals.server_name()).unwrap();
|
||||
|
||||
// The creator of an alias can remove it
|
||||
if self
|
||||
.db
|
||||
.who_created_alias(alias)?
|
||||
.is_some_and(|user| user == user_id)
|
||||
// Server admins can remove any local alias
|
||||
|| services().admin.user_is_admin(user_id).await?
|
||||
// Always allow the server service account to remove the alias, since there may not be an admin room
|
||||
|| server_user == user_id
|
||||
{
|
||||
Ok(true)
|
||||
// Checking whether the user is able to change canonical aliases of the
|
||||
// room
|
||||
} else if let Some(event) =
|
||||
services()
|
||||
.rooms
|
||||
.state_accessor
|
||||
.room_state_get(&room_id, &StateEventType::RoomPowerLevels, "")?
|
||||
{
|
||||
serde_json::from_str(event.content.get())
|
||||
.map_err(|_| Error::bad_database("Invalid event content for m.room.power_levels"))
|
||||
.map(|content: RoomPowerLevelsEventContent| {
|
||||
RoomPowerLevels::from(content).user_can_send_state(user_id, StateEventType::RoomCanonicalAlias)
|
||||
})
|
||||
// If there is no power levels event, only the room creator can change
|
||||
// canonical aliases
|
||||
} else if let Some(event) =
|
||||
services()
|
||||
.rooms
|
||||
.state_accessor
|
||||
.room_state_get(&room_id, &StateEventType::RoomCreate, "")?
|
||||
{
|
||||
Ok(event.sender == user_id)
|
||||
} else {
|
||||
Err(Error::bad_database("Room has no m.room.create event"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue