mirror of
https://gitlab.com/famedly/conduit.git
synced 2024-12-28 05:04:22 +01:00
feat: respect history visibility
This commit is contained in:
parent
2a16a5e967
commit
10fa686c77
8 changed files with 166 additions and 98 deletions
|
@ -50,12 +50,12 @@ pub async fn get_context_route(
|
||||||
|
|
||||||
if !services()
|
if !services()
|
||||||
.rooms
|
.rooms
|
||||||
.state_cache
|
.state_accessor
|
||||||
.is_joined(sender_user, &room_id)?
|
.user_can_see_event(sender_user, &room_id, &body.event_id)?
|
||||||
{
|
{
|
||||||
return Err(Error::BadRequest(
|
return Err(Error::BadRequest(
|
||||||
ErrorKind::Forbidden,
|
ErrorKind::Forbidden,
|
||||||
"You don't have permission to view this room.",
|
"You don't have permission to view this event.",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,6 +82,13 @@ pub async fn get_context_route(
|
||||||
/ 2,
|
/ 2,
|
||||||
)
|
)
|
||||||
.filter_map(|r| r.ok()) // Remove buggy events
|
.filter_map(|r| r.ok()) // Remove buggy events
|
||||||
|
.filter(|(_, pdu)| {
|
||||||
|
services()
|
||||||
|
.rooms
|
||||||
|
.state_accessor
|
||||||
|
.user_can_see_event(sender_user, &room_id, &pdu.event_id)
|
||||||
|
.unwrap_or(false)
|
||||||
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
for (_, event) in &events_before {
|
for (_, event) in &events_before {
|
||||||
|
@ -114,6 +121,13 @@ pub async fn get_context_route(
|
||||||
/ 2,
|
/ 2,
|
||||||
)
|
)
|
||||||
.filter_map(|r| r.ok()) // Remove buggy events
|
.filter_map(|r| r.ok()) // Remove buggy events
|
||||||
|
.filter(|(_, pdu)| {
|
||||||
|
services()
|
||||||
|
.rooms
|
||||||
|
.state_accessor
|
||||||
|
.user_can_see_event(sender_user, &room_id, &pdu.event_id)
|
||||||
|
.unwrap_or(false)
|
||||||
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
for (_, event) in &events_after {
|
for (_, event) in &events_after {
|
||||||
|
|
|
@ -113,17 +113,6 @@ pub async fn get_message_events_route(
|
||||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||||
let sender_device = body.sender_device.as_ref().expect("user is authenticated");
|
let sender_device = body.sender_device.as_ref().expect("user is authenticated");
|
||||||
|
|
||||||
if !services()
|
|
||||||
.rooms
|
|
||||||
.state_cache
|
|
||||||
.is_joined(sender_user, &body.room_id)?
|
|
||||||
{
|
|
||||||
return Err(Error::BadRequest(
|
|
||||||
ErrorKind::Forbidden,
|
|
||||||
"You don't have permission to view this room.",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let from = match body.from.clone() {
|
let from = match body.from.clone() {
|
||||||
Some(from) => PduCount::try_from_string(&from)?,
|
Some(from) => PduCount::try_from_string(&from)?,
|
||||||
None => match body.dir {
|
None => match body.dir {
|
||||||
|
@ -161,6 +150,13 @@ pub async fn get_message_events_route(
|
||||||
.pdus_after(sender_user, &body.room_id, from)?
|
.pdus_after(sender_user, &body.room_id, from)?
|
||||||
.take(limit)
|
.take(limit)
|
||||||
.filter_map(|r| r.ok()) // Filter out buggy events
|
.filter_map(|r| r.ok()) // Filter out buggy events
|
||||||
|
.filter(|(_, pdu)| {
|
||||||
|
services()
|
||||||
|
.rooms
|
||||||
|
.state_accessor
|
||||||
|
.user_can_see_event(sender_user, &body.room_id, &pdu.event_id)
|
||||||
|
.unwrap_or(false)
|
||||||
|
})
|
||||||
.take_while(|&(k, _)| Some(k) != to) // Stop at `to`
|
.take_while(|&(k, _)| Some(k) != to) // Stop at `to`
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
@ -203,6 +199,13 @@ pub async fn get_message_events_route(
|
||||||
.pdus_until(sender_user, &body.room_id, from)?
|
.pdus_until(sender_user, &body.room_id, from)?
|
||||||
.take(limit)
|
.take(limit)
|
||||||
.filter_map(|r| r.ok()) // Filter out buggy events
|
.filter_map(|r| r.ok()) // Filter out buggy events
|
||||||
|
.filter(|(_, pdu)| {
|
||||||
|
services()
|
||||||
|
.rooms
|
||||||
|
.state_accessor
|
||||||
|
.user_can_see_event(sender_user, &body.room_id, &pdu.event_id)
|
||||||
|
.unwrap_or(false)
|
||||||
|
})
|
||||||
.take_while(|&(k, _)| Some(k) != to) // Stop at `to`
|
.take_while(|&(k, _)| Some(k) != to) // Stop at `to`
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
|
|
@ -425,24 +425,25 @@ pub async fn get_room_event_route(
|
||||||
) -> Result<get_room_event::v3::Response> {
|
) -> Result<get_room_event::v3::Response> {
|
||||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||||
|
|
||||||
if !services()
|
let event = services()
|
||||||
.rooms
|
.rooms
|
||||||
.state_cache
|
.timeline
|
||||||
.is_joined(sender_user, &body.room_id)?
|
.get_pdu(&body.event_id)?
|
||||||
{
|
.ok_or(Error::BadRequest(ErrorKind::NotFound, "Event not found."))?;
|
||||||
|
|
||||||
|
if !services().rooms.state_accessor.user_can_see_event(
|
||||||
|
sender_user,
|
||||||
|
&event.room_id,
|
||||||
|
&body.event_id,
|
||||||
|
)? {
|
||||||
return Err(Error::BadRequest(
|
return Err(Error::BadRequest(
|
||||||
ErrorKind::Forbidden,
|
ErrorKind::Forbidden,
|
||||||
"You don't have permission to view this room.",
|
"You don't have permission to view this event.",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(get_room_event::v3::Response {
|
Ok(get_room_event::v3::Response {
|
||||||
event: services()
|
event: event.to_room_event(),
|
||||||
.rooms
|
|
||||||
.timeline
|
|
||||||
.get_pdu(&body.event_id)?
|
|
||||||
.ok_or(Error::BadRequest(ErrorKind::NotFound, "Event not found."))?
|
|
||||||
.to_room_event(),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -87,6 +87,13 @@ pub async fn search_events_route(
|
||||||
.timeline
|
.timeline
|
||||||
.get_pdu_from_id(result)
|
.get_pdu_from_id(result)
|
||||||
.ok()?
|
.ok()?
|
||||||
|
.filter(|pdu| {
|
||||||
|
services()
|
||||||
|
.rooms
|
||||||
|
.state_accessor
|
||||||
|
.user_can_see_event(sender_user, &pdu.room_id, &pdu.event_id)
|
||||||
|
.unwrap_or(false)
|
||||||
|
})
|
||||||
.map(|pdu| pdu.to_room_event())
|
.map(|pdu| pdu.to_room_event())
|
||||||
})
|
})
|
||||||
.map(|result| {
|
.map(|result| {
|
||||||
|
|
|
@ -7,11 +7,7 @@ use ruma::{
|
||||||
state::{get_state_events, get_state_events_for_key, send_state_event},
|
state::{get_state_events, get_state_events_for_key, send_state_event},
|
||||||
},
|
},
|
||||||
events::{
|
events::{
|
||||||
room::{
|
room::canonical_alias::RoomCanonicalAliasEventContent, AnyStateEventContent, StateEventType,
|
||||||
canonical_alias::RoomCanonicalAliasEventContent,
|
|
||||||
history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent},
|
|
||||||
},
|
|
||||||
AnyStateEventContent, StateEventType,
|
|
||||||
},
|
},
|
||||||
serde::Raw,
|
serde::Raw,
|
||||||
EventId, RoomId, UserId,
|
EventId, RoomId, UserId,
|
||||||
|
@ -85,29 +81,10 @@ pub async fn get_state_events_route(
|
||||||
) -> Result<get_state_events::v3::Response> {
|
) -> Result<get_state_events::v3::Response> {
|
||||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||||
|
|
||||||
#[allow(clippy::blocks_in_if_conditions)]
|
if services()
|
||||||
// Users not in the room should not be able to access the state unless history_visibility is
|
|
||||||
// WorldReadable
|
|
||||||
if !services()
|
|
||||||
.rooms
|
.rooms
|
||||||
.state_cache
|
.state_accessor
|
||||||
.is_joined(sender_user, &body.room_id)?
|
.user_can_see_state_events(&sender_user, &body.room_id)?
|
||||||
&& !matches!(
|
|
||||||
services()
|
|
||||||
.rooms
|
|
||||||
.state_accessor
|
|
||||||
.room_state_get(&body.room_id, &StateEventType::RoomHistoryVisibility, "")?
|
|
||||||
.map(|event| {
|
|
||||||
serde_json::from_str(event.content.get())
|
|
||||||
.map(|e: RoomHistoryVisibilityEventContent| e.history_visibility)
|
|
||||||
.map_err(|_| {
|
|
||||||
Error::bad_database(
|
|
||||||
"Invalid room history visibility event in database.",
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
Some(Ok(HistoryVisibility::WorldReadable))
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
return Err(Error::BadRequest(
|
return Err(Error::BadRequest(
|
||||||
ErrorKind::Forbidden,
|
ErrorKind::Forbidden,
|
||||||
|
@ -137,29 +114,10 @@ pub async fn get_state_events_for_key_route(
|
||||||
) -> Result<get_state_events_for_key::v3::Response> {
|
) -> Result<get_state_events_for_key::v3::Response> {
|
||||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||||
|
|
||||||
#[allow(clippy::blocks_in_if_conditions)]
|
if services()
|
||||||
// Users not in the room should not be able to access the state unless history_visibility is
|
|
||||||
// WorldReadable
|
|
||||||
if !services()
|
|
||||||
.rooms
|
.rooms
|
||||||
.state_cache
|
.state_accessor
|
||||||
.is_joined(sender_user, &body.room_id)?
|
.user_can_see_state_events(&sender_user, &body.room_id)?
|
||||||
&& !matches!(
|
|
||||||
services()
|
|
||||||
.rooms
|
|
||||||
.state_accessor
|
|
||||||
.room_state_get(&body.room_id, &StateEventType::RoomHistoryVisibility, "")?
|
|
||||||
.map(|event| {
|
|
||||||
serde_json::from_str(event.content.get())
|
|
||||||
.map(|e: RoomHistoryVisibilityEventContent| e.history_visibility)
|
|
||||||
.map_err(|_| {
|
|
||||||
Error::bad_database(
|
|
||||||
"Invalid room history visibility event in database.",
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
Some(Ok(HistoryVisibility::WorldReadable))
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
return Err(Error::BadRequest(
|
return Err(Error::BadRequest(
|
||||||
ErrorKind::Forbidden,
|
ErrorKind::Forbidden,
|
||||||
|
@ -192,29 +150,10 @@ pub async fn get_state_events_for_empty_key_route(
|
||||||
) -> Result<RumaResponse<get_state_events_for_key::v3::Response>> {
|
) -> Result<RumaResponse<get_state_events_for_key::v3::Response>> {
|
||||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||||
|
|
||||||
#[allow(clippy::blocks_in_if_conditions)]
|
if services()
|
||||||
// Users not in the room should not be able to access the state unless history_visibility is
|
|
||||||
// WorldReadable
|
|
||||||
if !services()
|
|
||||||
.rooms
|
.rooms
|
||||||
.state_cache
|
.state_accessor
|
||||||
.is_joined(sender_user, &body.room_id)?
|
.user_can_see_state_events(&sender_user, &body.room_id)?
|
||||||
&& !matches!(
|
|
||||||
services()
|
|
||||||
.rooms
|
|
||||||
.state_accessor
|
|
||||||
.room_state_get(&body.room_id, &StateEventType::RoomHistoryVisibility, "")?
|
|
||||||
.map(|event| {
|
|
||||||
serde_json::from_str(event.content.get())
|
|
||||||
.map(|e: RoomHistoryVisibilityEventContent| e.history_visibility)
|
|
||||||
.map_err(|_| {
|
|
||||||
Error::bad_database(
|
|
||||||
"Invalid room history visibility event in database.",
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
Some(Ok(HistoryVisibility::WorldReadable))
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
return Err(Error::BadRequest(
|
return Err(Error::BadRequest(
|
||||||
ErrorKind::Forbidden,
|
ErrorKind::Forbidden,
|
||||||
|
|
|
@ -954,6 +954,17 @@ pub async fn get_event_route(
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !services().rooms.state_accessor.server_can_see_event(
|
||||||
|
sender_servername,
|
||||||
|
&room_id,
|
||||||
|
&body.event_id,
|
||||||
|
)? {
|
||||||
|
return Err(Error::BadRequest(
|
||||||
|
ErrorKind::Forbidden,
|
||||||
|
"Server is not allowed to see event.",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
Ok(get_event::v1::Response {
|
Ok(get_event::v1::Response {
|
||||||
origin: services().globals.server_name().to_owned(),
|
origin: services().globals.server_name().to_owned(),
|
||||||
origin_server_ts: MilliSecondsSinceUnixEpoch::now(),
|
origin_server_ts: MilliSecondsSinceUnixEpoch::now(),
|
||||||
|
@ -1098,6 +1109,16 @@ pub async fn get_missing_events_route(
|
||||||
i += 1;
|
i += 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !services().rooms.state_accessor.server_can_see_event(
|
||||||
|
sender_servername,
|
||||||
|
&body.room_id,
|
||||||
|
&queued_events[i],
|
||||||
|
)? {
|
||||||
|
i += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
queued_events.extend_from_slice(
|
queued_events.extend_from_slice(
|
||||||
&serde_json::from_value::<Vec<OwnedEventId>>(
|
&serde_json::from_value::<Vec<OwnedEventId>>(
|
||||||
serde_json::to_value(pdu.get("prev_events").cloned().ok_or_else(|| {
|
serde_json::to_value(pdu.get("prev_events").cloned().ok_or_else(|| {
|
||||||
|
|
|
@ -82,6 +82,9 @@ impl Services {
|
||||||
server_visibility_cache: Mutex::new(LruCache::new(
|
server_visibility_cache: Mutex::new(LruCache::new(
|
||||||
(100.0 * config.conduit_cache_capacity_modifier) as usize,
|
(100.0 * config.conduit_cache_capacity_modifier) as usize,
|
||||||
)),
|
)),
|
||||||
|
user_visibility_cache: Mutex::new(LruCache::new(
|
||||||
|
(100.0 * config.conduit_cache_capacity_modifier) as usize,
|
||||||
|
)),
|
||||||
},
|
},
|
||||||
state_cache: rooms::state_cache::Service { db },
|
state_cache: rooms::state_cache::Service { db },
|
||||||
state_compressor: rooms::state_compressor::Service {
|
state_compressor: rooms::state_compressor::Service {
|
||||||
|
|
|
@ -14,7 +14,7 @@ use ruma::{
|
||||||
},
|
},
|
||||||
StateEventType,
|
StateEventType,
|
||||||
},
|
},
|
||||||
EventId, OwnedServerName, RoomId, ServerName, UserId,
|
EventId, OwnedServerName, OwnedUserId, RoomId, ServerName, UserId,
|
||||||
};
|
};
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
|
||||||
|
@ -23,6 +23,7 @@ use crate::{services, Error, PduEvent, Result};
|
||||||
pub struct Service {
|
pub struct Service {
|
||||||
pub db: &'static dyn Data,
|
pub db: &'static dyn Data,
|
||||||
pub server_visibility_cache: Mutex<LruCache<(OwnedServerName, u64), bool>>,
|
pub server_visibility_cache: Mutex<LruCache<(OwnedServerName, u64), bool>>,
|
||||||
|
pub user_visibility_cache: Mutex<LruCache<(OwnedUserId, u64), bool>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Service {
|
impl Service {
|
||||||
|
@ -92,7 +93,7 @@ impl Service {
|
||||||
|
|
||||||
/// Whether a server is allowed to see an event through federation, based on
|
/// Whether a server is allowed to see an event through federation, based on
|
||||||
/// the room's history_visibility at that event's state.
|
/// the room's history_visibility at that event's state.
|
||||||
#[tracing::instrument(skip(self))]
|
#[tracing::instrument(skip(self, origin, room_id, event_id))]
|
||||||
pub fn server_can_see_event(
|
pub fn server_can_see_event(
|
||||||
&self,
|
&self,
|
||||||
origin: &ServerName,
|
origin: &ServerName,
|
||||||
|
@ -154,6 +155,85 @@ impl Service {
|
||||||
Ok(visibility)
|
Ok(visibility)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether a user is allowed to see an event, based on
|
||||||
|
/// the room's history_visibility at that event's state.
|
||||||
|
#[tracing::instrument(skip(self, user_id, room_id, event_id))]
|
||||||
|
pub fn user_can_see_event(
|
||||||
|
&self,
|
||||||
|
user_id: &UserId,
|
||||||
|
room_id: &RoomId,
|
||||||
|
event_id: &EventId,
|
||||||
|
) -> Result<bool> {
|
||||||
|
let shortstatehash = match self.pdu_shortstatehash(event_id)? {
|
||||||
|
Some(shortstatehash) => shortstatehash,
|
||||||
|
None => return Ok(true),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(visibility) = self
|
||||||
|
.user_visibility_cache
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.get_mut(&(user_id.to_owned(), shortstatehash))
|
||||||
|
{
|
||||||
|
return Ok(*visibility);
|
||||||
|
}
|
||||||
|
|
||||||
|
let currently_member = services().rooms.state_cache.is_joined(&user_id, &room_id)?;
|
||||||
|
|
||||||
|
let history_visibility = self
|
||||||
|
.state_get(shortstatehash, &StateEventType::RoomHistoryVisibility, "")?
|
||||||
|
.map_or(Ok(HistoryVisibility::Shared), |s| {
|
||||||
|
serde_json::from_str(s.content.get())
|
||||||
|
.map(|c: RoomHistoryVisibilityEventContent| c.history_visibility)
|
||||||
|
.map_err(|_| {
|
||||||
|
Error::bad_database("Invalid history visibility event in database.")
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let visibility = match history_visibility {
|
||||||
|
HistoryVisibility::WorldReadable => true,
|
||||||
|
HistoryVisibility::Shared => currently_member,
|
||||||
|
HistoryVisibility::Invited => {
|
||||||
|
// Allow if any member on requesting server was AT LEAST invited, else deny
|
||||||
|
self.user_was_invited(shortstatehash, &user_id)
|
||||||
|
}
|
||||||
|
HistoryVisibility::Joined => {
|
||||||
|
// Allow if any member on requested server was joined, else deny
|
||||||
|
self.user_was_joined(shortstatehash, &user_id)
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
error!("Unknown history visibility {history_visibility}");
|
||||||
|
false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.user_visibility_cache
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.insert((user_id.to_owned(), shortstatehash), visibility);
|
||||||
|
|
||||||
|
Ok(visibility)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether a user is allowed to see an event, based on
|
||||||
|
/// the room's history_visibility at that event's state.
|
||||||
|
#[tracing::instrument(skip(self, user_id, room_id))]
|
||||||
|
pub fn user_can_see_state_events(&self, user_id: &UserId, room_id: &RoomId) -> Result<bool> {
|
||||||
|
let currently_member = services().rooms.state_cache.is_joined(&user_id, &room_id)?;
|
||||||
|
|
||||||
|
let history_visibility = self
|
||||||
|
.room_state_get(&room_id, &StateEventType::RoomHistoryVisibility, "")?
|
||||||
|
.map_or(Ok(HistoryVisibility::Shared), |s| {
|
||||||
|
serde_json::from_str(s.content.get())
|
||||||
|
.map(|c: RoomHistoryVisibilityEventContent| c.history_visibility)
|
||||||
|
.map_err(|_| {
|
||||||
|
Error::bad_database("Invalid history visibility event in database.")
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(currently_member || history_visibility == HistoryVisibility::WorldReadable)
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the state hash for this pdu.
|
/// Returns the state hash for this pdu.
|
||||||
pub fn pdu_shortstatehash(&self, event_id: &EventId) -> Result<Option<u64>> {
|
pub fn pdu_shortstatehash(&self, event_id: &EventId) -> Result<Option<u64>> {
|
||||||
self.db.pdu_shortstatehash(event_id)
|
self.db.pdu_shortstatehash(event_id)
|
||||||
|
|
Loading…
Reference in a new issue