summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTimo Kösters <timo@koesters.xyz>2023-02-22 15:49:55 +0100
committerTimo Kösters <timo@koesters.xyz>2023-03-13 10:39:18 +0100
commit10fa686c77637ed2837ff6348ccbdebeff9dcae3 (patch)
treefff17030d14501815eaab45ea1bc40748ba7fe24
parent2a16a5e967ff15052fe03313711dda89d0f95232 (diff)
downloadconduit-10fa686c77637ed2837ff6348ccbdebeff9dcae3.zip
feat: respect history visibility
-rw-r--r--src/api/client_server/context.rs20
-rw-r--r--src/api/client_server/message.rs25
-rw-r--r--src/api/client_server/room.rs23
-rw-r--r--src/api/client_server/search.rs7
-rw-r--r--src/api/client_server/state.rs81
-rw-r--r--src/api/server_server.rs21
-rw-r--r--src/service/mod.rs3
-rw-r--r--src/service/rooms/state_accessor/mod.rs84
8 files changed, 166 insertions, 98 deletions
diff --git a/src/api/client_server/context.rs b/src/api/client_server/context.rs
index fa3c754..5a3013b 100644
--- a/src/api/client_server/context.rs
+++ b/src/api/client_server/context.rs
@@ -50,12 +50,12 @@ pub async fn get_context_route(
if !services()
.rooms
- .state_cache
- .is_joined(sender_user, &room_id)?
+ .state_accessor
+ .user_can_see_event(sender_user, &room_id, &body.event_id)?
{
return Err(Error::BadRequest(
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,
)
.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();
for (_, event) in &events_before {
@@ -114,6 +121,13 @@ pub async fn get_context_route(
/ 2,
)
.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();
for (_, event) in &events_after {
diff --git a/src/api/client_server/message.rs b/src/api/client_server/message.rs
index a0c9571..f7c77f6 100644
--- a/src/api/client_server/message.rs
+++ b/src/api/client_server/message.rs
@@ -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_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() {
Some(from) => PduCount::try_from_string(&from)?,
None => match body.dir {
@@ -161,6 +150,13 @@ pub async fn get_message_events_route(
.pdus_after(sender_user, &body.room_id, from)?
.take(limit)
.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`
.collect();
@@ -203,6 +199,13 @@ pub async fn get_message_events_route(
.pdus_until(sender_user, &body.room_id, from)?
.take(limit)
.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`
.collect();
diff --git a/src/api/client_server/room.rs b/src/api/client_server/room.rs
index 830e085..aa6fa5f 100644
--- a/src/api/client_server/room.rs
+++ b/src/api/client_server/room.rs
@@ -425,24 +425,25 @@ pub async fn get_room_event_route(
) -> Result<get_room_event::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
- if !services()
+ let event = services()
.rooms
- .state_cache
- .is_joined(sender_user, &body.room_id)?
- {
+ .timeline
+ .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(
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 {
- event: services()
- .rooms
- .timeline
- .get_pdu(&body.event_id)?
- .ok_or(Error::BadRequest(ErrorKind::NotFound, "Event not found."))?
- .to_room_event(),
+ event: event.to_room_event(),
})
}
diff --git a/src/api/client_server/search.rs b/src/api/client_server/search.rs
index 5d760db..fe69e7c 100644
--- a/src/api/client_server/search.rs
+++ b/src/api/client_server/search.rs
@@ -87,6 +87,13 @@ pub async fn search_events_route(
.timeline
.get_pdu_from_id(result)
.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(|result| {
diff --git a/src/api/client_server/state.rs b/src/api/client_server/state.rs
index d9c1464..e2abe48 100644
--- a/src/api/client_server/state.rs
+++ b/src/api/client_server/state.rs
@@ -7,11 +7,7 @@ use ruma::{
state::{get_state_events, get_state_events_for_key, send_state_event},
},
events::{
- room::{
- canonical_alias::RoomCanonicalAliasEventContent,
- history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent},
- },
- AnyStateEventContent, StateEventType,
+ room::canonical_alias::RoomCanonicalAliasEventContent, AnyStateEventContent, StateEventType,
},
serde::Raw,
EventId, RoomId, UserId,
@@ -85,29 +81,10 @@ pub async fn get_state_events_route(
) -> Result<get_state_events::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
- #[allow(clippy::blocks_in_if_conditions)]
- // Users not in the room should not be able to access the state unless history_visibility is
- // WorldReadable
- if !services()
+ if services()
.rooms
- .state_cache
- .is_joined(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))
- )
+ .state_accessor
+ .user_can_see_state_events(&sender_user, &body.room_id)?
{
return Err(Error::BadRequest(
ErrorKind::Forbidden,
@@ -137,29 +114,10 @@ pub async fn get_state_events_for_key_route(
) -> Result<get_state_events_for_key::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
- #[allow(clippy::blocks_in_if_conditions)]
- // Users not in the room should not be able to access the state unless history_visibility is
- // WorldReadable
- if !services()
+ if services()
.rooms
- .state_cache
- .is_joined(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))
- )
+ .state_accessor
+ .user_can_see_state_events(&sender_user, &body.room_id)?
{
return Err(Error::BadRequest(
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>> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
- #[allow(clippy::blocks_in_if_conditions)]
- // Users not in the room should not be able to access the state unless history_visibility is
- // WorldReadable
- if !services()
+ if services()
.rooms
- .state_cache
- .is_joined(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))
- )
+ .state_accessor
+ .user_can_see_state_events(&sender_user, &body.room_id)?
{
return Err(Error::BadRequest(
ErrorKind::Forbidden,
diff --git a/src/api/server_server.rs b/src/api/server_server.rs
index adf4bc2..0247369 100644
--- a/src/api/server_server.rs
+++ b/src/api/server_server.rs
@@ -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 {
origin: services().globals.server_name().to_owned(),
origin_server_ts: MilliSecondsSinceUnixEpoch::now(),
@@ -1098,6 +1109,16 @@ pub async fn get_missing_events_route(
i += 1;
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(
&serde_json::from_value::<Vec<OwnedEventId>>(
serde_json::to_value(pdu.get("prev_events").cloned().ok_or_else(|| {
diff --git a/src/service/mod.rs b/src/service/mod.rs
index 07d80a1..eea397f 100644
--- a/src/service/mod.rs
+++ b/src/service/mod.rs
@@ -82,6 +82,9 @@ impl Services {
server_visibility_cache: Mutex::new(LruCache::new(
(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_compressor: rooms::state_compressor::Service {
diff --git a/src/service/rooms/state_accessor/mod.rs b/src/service/rooms/state_accessor/mod.rs
index 154c189..a25a8b5 100644
--- a/src/service/rooms/state_accessor/mod.rs
+++ b/src/service/rooms/state_accessor/mod.rs
@@ -14,7 +14,7 @@ use ruma::{
},
StateEventType,
},
- EventId, OwnedServerName, RoomId, ServerName, UserId,
+ EventId, OwnedServerName, OwnedUserId, RoomId, ServerName, UserId,
};
use tracing::error;
@@ -23,6 +23,7 @@ use crate::{services, Error, PduEvent, Result};
pub struct Service {
pub db: &'static dyn Data,
pub server_visibility_cache: Mutex<LruCache<(OwnedServerName, u64), bool>>,
+ pub user_visibility_cache: Mutex<LruCache<(OwnedUserId, u64), bool>>,
}
impl Service {
@@ -92,7 +93,7 @@ impl Service {
/// Whether a server is allowed to see an event through federation, based on
/// 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(
&self,
origin: &ServerName,
@@ -154,6 +155,85 @@ impl Service {
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.
pub fn pdu_shortstatehash(&self, event_id: &EventId) -> Result<Option<u64>> {
self.db.pdu_shortstatehash(event_id)