Skip to content

Commit

Permalink
Matrix mock server: Do not allow access to state events when the user…
Browse files Browse the repository at this point in the history
… left the room
  • Loading branch information
exul committed Oct 5, 2017
1 parent 2096084 commit 3f1b801
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 95 deletions.
33 changes: 18 additions & 15 deletions src/matrix-rocketchat/handlers/events/room_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ impl<'a> RoomHandler<'a> {
self.logger,
"Could not determine if the room that the bot user was invited to is an admin room or not, bot is leaving"
);
self.leave_and_forget_room(matrix_room_id, matrix_bot_user_id)?;
self.handle_admin_room_setup_error(&err, matrix_room_id, matrix_bot_user_id);
return Err(err);
}
};
Expand Down Expand Up @@ -198,20 +198,7 @@ impl<'a> RoomHandler<'a> {

if let Err(err) = self.is_admin_room_valid(matrix_room_id.clone(), &inviter_id) {
info!(self.logger, "Admin room {} is not valid, bot will leave and forget the room", matrix_room_id);
let error_notifier = ErrorNotifier {
config: self.config,
connection: self.connection,
logger: self.logger,
matrix_api: self.matrix_api,
};
if let Err(err) = error_notifier.send_message_to_user(&err, matrix_room_id.clone(), &inviter_id) {
log::log_error(self.logger, &err);
}

if let Err(err) = self.leave_and_forget_room(matrix_room_id.clone(), matrix_bot_user_id.clone()) {
log::log_error(self.logger, &err);
}

self.handle_admin_room_setup_error(&err, matrix_room_id, matrix_bot_user_id);
return Ok(());
}

Expand Down Expand Up @@ -356,4 +343,20 @@ impl<'a> RoomHandler<'a> {

Ok(())
}

fn handle_admin_room_setup_error(&self, err: &Error, matrix_room_id: RoomId, matrix_bot_user_id: UserId) {
let error_notifier = ErrorNotifier {
config: self.config,
connection: self.connection,
logger: self.logger,
matrix_api: self.matrix_api,
};
if let Err(err) = error_notifier.send_message_to_user(err, matrix_room_id.clone(), &matrix_bot_user_id) {
log::log_error(self.logger, &err);
}

if let Err(err) = self.leave_and_forget_room(matrix_room_id.clone(), matrix_bot_user_id.clone()) {
log::log_error(self.logger, &err);
}
}
}
166 changes: 92 additions & 74 deletions tests/matrix-rocketchat-test/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use ruma_events::room::member::{MemberEvent, MemberEventContent, MembershipState
use ruma_identifiers::{EventId, RoomAliasId, RoomId, UserId};
use serde_json;
use super::{DEFAULT_LOGGER, Message, MessageForwarder, PendingInvites, RoomAliasMap, RoomsStatesMap, TestError, UsernameList,
extract_payload, helpers};
UsersInRooms, extract_payload, helpers};

#[derive(Serialize)]
pub struct RocketchatInfo {
Expand Down Expand Up @@ -318,20 +318,17 @@ impl Handler for MatrixCreateRoom {
let mut room_id = RoomId::try_from(&test_room_id).unwrap();
let user_id = user_id_from_request(request);

// check if the room id already exists, if it does, append `_next` to it
if get_state_from_room(request, room_id.clone(), user_id, "creator".to_string()).unwrap().is_some() {
let next_room_id = format!("!{}_next_id:localhost", &room_id_local_part);
room_id = RoomId::try_from(&next_room_id).unwrap();
// scope to release the mutex
{
// check if the room id already exists, if it does, append `_next` to it
let users_in_rooms_mutex = request.get::<Write<UsersInRooms>>().unwrap();
let users_in_rooms = users_in_rooms_mutex.lock().unwrap();
if users_in_rooms.get(&room_id).is_some() {
let next_room_id = format!("!{}_next_id:localhost", &room_id_local_part);
room_id = RoomId::try_from(&next_room_id).unwrap();
}
}

let url: Url = request.url.clone().into();
let mut query_pairs = url.query_pairs();
let (_, user_id_param) = query_pairs.find(|&(ref key, _)| key == "user_id").unwrap_or((
Cow::from("user_id"),
Cow::from("@rocketchat:localhost"),
));
let user_id = UserId::try_from(user_id_param.borrow()).unwrap();

if let Err(err) = add_membership_event_to_room(request, user_id.clone(), room_id.clone(), MembershipState::Join) {
debug!(DEFAULT_LOGGER, format!("{}", err));
let payload = r#"{
Expand Down Expand Up @@ -419,18 +416,32 @@ impl Handler for RoomMembers {
let decoded_room_id = percent_decode(url_room_id.as_bytes()).decode_utf8().unwrap();
let room_id = RoomId::try_from(&decoded_room_id).unwrap();

let mutex = request.get::<Write<RoomsStatesMap>>().unwrap();
let mut rooms_states_for_users = mutex.lock().unwrap();
let mut empty_room_states_for_users = HashMap::new();
let mut user_list = Vec::new();
let url: Url = request.url.clone().into();
let mut query_pairs = url.query_pairs();
let (_, user_id_param) = query_pairs.find(|&(ref key, _)| key == "user_id").unwrap_or((
Cow::from("user_id"),
Cow::from("@rocketchat:localhost"),
));
let user_id = UserId::try_from(user_id_param.borrow()).unwrap();

let mutex = request.get::<Write<UsersInRooms>>().unwrap();
let mut users_in_rooms = mutex.lock().unwrap();
let mut empty_users_in_room = HashMap::new();

let room_states_for_users = rooms_states_for_users.get_mut(&room_id).unwrap_or(&mut empty_room_states_for_users);
for (user_id, membership_with_room_states) in room_states_for_users {
let &mut (membership_state, _) = membership_with_room_states;
user_list.push((user_id.clone(), membership_state))
}
let users_in_room_for_users = users_in_rooms.get_mut(&room_id).unwrap_or(&mut empty_users_in_room);
let users_in_room_for_user = match users_in_room_for_users.get(&user_id) {
Some(&(_, ref users_in_room_for_user)) => users_in_room_for_user,
None => {
let payload = r#"{
"errcode":"M_GUEST_ACCESS_FORBIDDEN",
"error":"User is not in room"
}"#;

let member_events = build_member_events_from_user_ids(&user_list, room_id);
return Ok(Response::with((status::Forbidden, payload.to_string())));
}
};

let member_events = build_member_events_from_user_ids(&users_in_room_for_user, room_id);

let response = get_member_events::Response { chunk: member_events };
let payload = serde_json::to_string(&response).unwrap();
Expand Down Expand Up @@ -570,7 +581,7 @@ impl Handler for GetRoomState {
Ok(state_option) => state_option,
Err(err) => {
let payload = r#"{
"errcode":"M_NOT_FOUND",
"errcode":"M_GUEST_ACCESS_FORBIDDEN",
"error":"ERR_MSG"
}"#
.replace("ERR_MSG", err);
Expand Down Expand Up @@ -888,8 +899,8 @@ pub struct InvalidJsonResponse {
}

impl Handler for InvalidJsonResponse {
fn handle(&self, _request: &mut Request) -> IronResult<Response> {
debug!(DEFAULT_LOGGER, "Matrix mock server got invali JSON responder request");
fn handle(&self, request: &mut Request) -> IronResult<Response> {
debug!(DEFAULT_LOGGER, "Matrix mock server got invalid JSON responder request for URL {}", request.url);
Ok(Response::with((self.status, "invalid json")))
}
}
Expand Down Expand Up @@ -921,21 +932,15 @@ fn add_state_to_room(request: &mut Request, room_id: RoomId, state_key: String,
debug!(DEFAULT_LOGGER, "Matrix mock server adds room state {} with value {}", state_key, state_value);

let mutex = request.get::<Write<RoomsStatesMap>>().unwrap();
let mut rooms_states_for_users = mutex.lock().unwrap();
let mut rooms_states = mutex.lock().unwrap();

if !rooms_states_for_users.contains_key(&room_id) {
rooms_states_for_users.insert(room_id.clone(), HashMap::new());
if !rooms_states.contains_key(&room_id) {
rooms_states.insert(room_id.clone(), HashMap::new());
}


let room_states_for_users = rooms_states_for_users.get_mut(&room_id).unwrap();
for (_, membership_with_room_states) in room_states_for_users {
let &mut (membership_state, ref mut room_states) = membership_with_room_states;
if membership_state == MembershipState::Join {
room_states.insert(state_key.clone(), state_value.clone());
}
}

let room_states = rooms_states.get_mut(&room_id).unwrap();
room_states.insert(state_key.clone(), state_value.clone());
}

fn get_state_from_room(
Expand All @@ -946,21 +951,28 @@ fn get_state_from_room(
) -> Result<Option<(String, String)>, &'static str> {
debug!(DEFAULT_LOGGER, "Matrix mock server gets room state {}", state_key);

let mutex = request.get::<Write<RoomsStatesMap>>().unwrap();
let mut rooms_states_for_users = mutex.lock().unwrap();

let users_with_room_states = match rooms_states_for_users.get_mut(&room_id) {
Some(users_with_room_states) => users_with_room_states,
let users_in_rooms_mutex = request.get::<Write<UsersInRooms>>().unwrap();
let users_in_rooms = users_in_rooms_mutex.lock().unwrap();
let users_in_room = match users_in_rooms.get(&room_id) {
Some(users_in_room) => users_in_room,
None => {
return Ok(None);
debug!(DEFAULT_LOGGER, "Matrix mock server: User {} not in room {}", user_id, room_id);
return Err("User not in room");
}
};

let room_states: &mut HashMap<String, String> = match users_with_room_states.get_mut(&user_id) {
Some(&mut (_, ref mut room_states)) => room_states,
if !users_in_room.contains_key(&user_id) {
debug!(DEFAULT_LOGGER, "Matrix mock server: User {} not in room {}", user_id, room_id);
return Err("User not in room");
}

let rooms_states_mutex = request.get::<Write<RoomsStatesMap>>().unwrap();
let mut rooms_states = rooms_states_mutex.lock().unwrap();

let room_states = match rooms_states.get_mut(&room_id) {
Some(room_states) => room_states,
None => {
debug!(DEFAULT_LOGGER, "Matrix mock server: User {} not in room {}", user_id, room_id);
return Err("User not in room");
return Ok(None);
}
};

Expand All @@ -979,48 +991,54 @@ fn add_membership_event_to_room(
request: &mut Request,
user_id: UserId,
room_id: RoomId,
membership_event: MembershipState,
membership_state: MembershipState,
) -> Result<(), &'static str> {
let mutex = request.get::<Write<RoomsStatesMap>>().unwrap();
let mut rooms_states_for_users = mutex.lock().unwrap();
let empty_room_states_for_users = HashMap::new();
let mutex = request.get::<Write<UsersInRooms>>().unwrap();
let mut users_in_rooms = mutex.lock().unwrap();
let empty_users_in_room = HashMap::new();

if !rooms_states_for_users.contains_key(&room_id) {
rooms_states_for_users.insert(room_id.clone(), empty_room_states_for_users);
if !users_in_rooms.contains_key(&room_id) {
users_in_rooms.insert(room_id.clone(), empty_users_in_room);
}

let room_states_for_users = rooms_states_for_users.get_mut(&room_id).unwrap();
let users_in_room_for_users = users_in_rooms.get_mut(&room_id).unwrap();

for (id, membership_with_room_states) in room_states_for_users.iter() {
for (id, membership_with_room_states) in users_in_room_for_users.iter() {
let &(membership, _) = membership_with_room_states;
if id == &user_id && membership == membership_event {
match membership_event {
if id == &user_id && membership == membership_state {
match membership_state {
MembershipState::Join => return Err("User is already in room"),
MembershipState::Leave => return Err("User not in room"),
_ => return Err("Unknown membership state"),
}
}
}

let mut room_states = HashMap::new();
match membership_event {
MembershipState::Join => {
for (_, membership_with_room_states) in room_states_for_users.iter() {
let &(membership, ref existing_room_states) = membership_with_room_states;
if membership == MembershipState::Join {
room_states = existing_room_states.clone();
break;
}
}
}
MembershipState::Leave => {
let &mut (_, ref mut existing_room_states) = room_states_for_users.get_mut(&user_id).unwrap();
room_states = existing_room_states.clone()
let mut existing_users = Vec::new();
for (_, membership_with_users) in users_in_room_for_users.iter() {
let &(membership, ref users) = membership_with_users;
if membership == MembershipState::Join {
existing_users = users.clone();
break;
}
_ => return Err("Unknown membership state"),
};
}

if !users_in_room_for_users.contains_key(&user_id) {
users_in_room_for_users.insert(user_id.clone(), (membership_state.clone(), existing_users));
}

room_states_for_users.insert(user_id, (membership_event, room_states));
// update the users own membership state
let users = users_in_room_for_users.get(&user_id).unwrap().1.clone();
users_in_room_for_users.insert(user_id.clone(), (membership_state, users));

// update the memberships state for all users that are currently in the room
for (_, membership_with_users) in users_in_room_for_users {
let &mut (ref mut membership, ref mut users) = membership_with_users;
if membership == &MembershipState::Join {
users.retain(|&(ref id, _)| id != &user_id);
users.push((user_id.clone(), membership_state.clone()));
}
}

Ok(())
}
Expand Down
10 changes: 9 additions & 1 deletion tests/matrix-rocketchat-test/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@ pub use message_forwarder::{Message, MessageForwarder};
#[derive(Copy, Clone)]
pub struct UsernameList;

#[derive(Copy, Clone)]
pub struct UsersInRooms;

#[derive(Copy, Clone)]
pub struct RoomsStatesMap;

Expand All @@ -128,8 +131,12 @@ impl Key for UsernameList {
type Value = Vec<String>;
}

impl Key for UsersInRooms {
type Value = HashMap<RoomId, HashMap<UserId, (MembershipState, Vec<(UserId, MembershipState)>)>>;
}

impl Key for RoomsStatesMap {
type Value = HashMap<RoomId, HashMap<UserId, (MembershipState, HashMap<String, String>)>>;
type Value = HashMap<RoomId, HashMap<String, String>>;
}

impl Key for RoomAliasMap {
Expand Down Expand Up @@ -320,6 +327,7 @@ impl Test {
thread::spawn(move || {
let mut chain = Chain::new(router);
chain.link_before(Write::<UsernameList>::one(Vec::new()));
chain.link_before(Write::<UsersInRooms>::one(HashMap::new()));
chain.link_before(Write::<RoomsStatesMap>::one(HashMap::new()));
chain.link_before(Write::<RoomAliasMap>::one(HashMap::new()));
chain.link_before(Write::<PendingInvites>::one(HashMap::new()));
Expand Down
11 changes: 6 additions & 5 deletions tests/matrix-rocketchat-test/message_forwarder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ use iron::url::percent_encoding::percent_decode;
use matrix_rocketchat::errors::MatrixErrorResponse;
use persistent::Write;
use router::Router;
use ruma_events::room::member::MembershipState;
use ruma_identifiers::{RoomId, UserId};
use serde_json;

use super::{RoomsStatesMap, TestError, extract_payload};
use super::{TestError, UsersInRooms, extract_payload};

/// Forwards a message from an iron handler to a channel so that it can be received outside of the
/// iron handler.
Expand Down Expand Up @@ -92,12 +93,12 @@ fn validate_message_forwarding_for_user(request: &mut Request, url: Url) -> Iron
Cow::from("@rocketchat:localhost"),
));
let user_id = UserId::try_from(user_id_param.borrow()).unwrap();
let mutex = request.get::<Write<RoomsStatesMap>>().unwrap();
let rooms_states_for_users = mutex.lock().unwrap();
let mutex = request.get::<Write<UsersInRooms>>().unwrap();
let users_in_rooms = mutex.lock().unwrap();
let empty_users = HashMap::new();
let room_states_for_users = &rooms_states_for_users.get(&room_id).unwrap_or(&empty_users);
let users_in_room = &users_in_rooms.get(&room_id).unwrap_or(&empty_users);

if !room_states_for_users.iter().any(|(id, _)| id == &user_id) {
if !users_in_room.iter().any(|(id, &(membership, _))| id == &user_id && membership == MembershipState::Join) {
let matrix_err = MatrixErrorResponse {
errcode: "M_FORBIDDEN".to_string(),
error: format!("{} not in room {}", user_id, room_id),
Expand Down

0 comments on commit 3f1b801

Please sign in to comment.