Skip to content

Commit

Permalink
Set default power levels when creating a room
Browse files Browse the repository at this point in the history
To make sure only the bot user can manage the room
  • Loading branch information
exul committed Apr 3, 2017
1 parent 6650e0c commit 37a435e
Show file tree
Hide file tree
Showing 6 changed files with 186 additions and 21 deletions.
11 changes: 7 additions & 4 deletions src/matrix-rocketchat/api/matrix/mod.rs
Expand Up @@ -18,7 +18,7 @@ pub mod r0;
/// Matrix REST API
pub trait MatrixApi: Send + Sync + MatrixApiClone {
/// Create a room.
fn create_room(&self, room_name: String, matrix_user_id: UserId) -> Result<RoomId>;
fn create_room(&self, room_name: String) -> Result<RoomId>;
/// Forget a room.
fn forget_room(&self, matrix_room_id: RoomId) -> Result<()>;
/// Get the list of members for this room.
Expand All @@ -33,6 +33,9 @@ pub trait MatrixApi: Send + Sync + MatrixApiClone {
fn register(&self, user_id_local_part: String) -> Result<()>;
/// Send a text message to a room.
fn send_text_message_event(&self, matrix_room_id: RoomId, matrix_user_id: UserId, body: String) -> Result<()>;
/// Set the default power levels for a room. Only the bot will be able to control the room.
/// The power levels for invite, kick, ban, and redact are all set to 50.
fn set_default_powerlevels(&self, matrix_room_id: RoomId, bot_user_id: UserId) -> Result<()>;
/// Set the display name for a user
fn set_display_name(&self, matrix_user_id: UserId, name: String) -> Result<()>;
/// Set the name for a room
Expand Down Expand Up @@ -79,10 +82,10 @@ impl MatrixApi {
}

let supported_versions: GetSupportedVersionsResponse = serde_json::from_str(&body).chain_err(|| {
ErrorKind::InvalidJSON(format!("Could not deserialize response from Matrix supported versions API \
ErrorKind::InvalidJSON(format!("Could not deserialize response from Matrix supported versions API \
endpoint: `{}`",
body))
})?;
body))
})?;
debug!(logger, format!("Homeserver supports versions {:?}", supported_versions.versions));
MatrixApi::get_max_supported_version_api(supported_versions.versions, config, logger)
}
Expand Down
41 changes: 34 additions & 7 deletions src/matrix-rocketchat/api/matrix/r0.rs
Expand Up @@ -19,7 +19,7 @@ use ruma_events::room::message::MessageType;
use ruma_identifiers::{EventId, RoomId, UserId};
use serde_json::Map;
use slog::Logger;
use serde_json;
use serde_json::{self, Value};

use api::RestApi;
use config::Config;
Expand Down Expand Up @@ -54,11 +54,11 @@ impl MatrixApi {
}

impl super::MatrixApi for MatrixApi {
fn create_room(&self, room_name: String, matrix_user_id: UserId) -> Result<RoomId> {
fn create_room(&self, room_name: String) -> Result<RoomId> {
let endpoint = self.base_url.clone() + &CreateRoomEndpoint::request_path(());
let body_params = create_room::BodyParams {
creation_content: None,
invite: vec![matrix_user_id],
invite: vec![],
name: Some(room_name),
preset: Some(RoomPreset::PrivateChat),
room_alias_name: None,
Expand Down Expand Up @@ -208,17 +208,24 @@ impl super::MatrixApi for MatrixApi {
Ok(())
}

fn set_room_name(&self, matrix_room_id: RoomId, name: String) -> Result<()> {
fn set_default_powerlevels(&self, matrix_room_id: RoomId, bot_user_id: UserId) -> Result<()> {
let path_params = send_state_event_for_empty_key::PathParams {
room_id: matrix_room_id,
event_type: EventType::RoomName,
event_type: EventType::RoomPowerLevels,
};
let endpoint = self.base_url.clone() + &SendStateEventForEmptyKeyEndpoint::request_path(path_params);
let params = self.params_hash();
let mut body_params = serde_json::Map::new();
body_params.insert("name", name);
let mut users = serde_json::Map::new();
users.insert(bot_user_id.to_string(), Value::I64(100));
body_params.insert("invite", Value::I64(50));
body_params.insert("kick", Value::I64(50));
body_params.insert("ban", Value::I64(50));
body_params.insert("redact", Value::I64(50));
body_params.insert("users", Value::Object(users));
body_params.insert("events", Value::Object(serde_json::Map::new()));

let payload = serde_json::to_string(&body_params).chain_err(|| ErrorKind::InvalidJSON("Could not serialize account body params".to_string()))?;
let payload = serde_json::to_string(&body_params).chain_err(|| ErrorKind::InvalidJSON("Could not serialize power levels body params".to_string()))?;

let (body, status_code) =
RestApi::call_matrix(SendStateEventForEmptyKeyEndpoint::method(), &endpoint, &payload, &params)?;
Expand All @@ -244,6 +251,26 @@ impl super::MatrixApi for MatrixApi {
}
Ok(())
}

fn set_room_name(&self, matrix_room_id: RoomId, name: String) -> Result<()> {
let path_params = send_state_event_for_empty_key::PathParams {
room_id: matrix_room_id,
event_type: EventType::RoomName,
};
let endpoint = self.base_url.clone() + &SendStateEventForEmptyKeyEndpoint::request_path(path_params);
let params = self.params_hash();
let mut body_params = serde_json::Map::new();
body_params.insert("name", name);

let payload = serde_json::to_string(&body_params).chain_err(|| ErrorKind::InvalidJSON("Could not serialize room name body params".to_string()))?;

let (body, status_code) =
RestApi::call_matrix(SendStateEventForEmptyKeyEndpoint::method(), &endpoint, &payload, &params)?;
if !status_code.is_success() {
return Err(build_error(&endpoint, &body, &status_code));
}
Ok(())
}
}

fn build_error(endpoint: &str, body: &str, status_code: &StatusCode) -> Error {
Expand Down
4 changes: 3 additions & 1 deletion src/matrix-rocketchat/handlers/events/command_handler.rs
Expand Up @@ -355,7 +355,9 @@ impl<'a> CommandHandler<'a> {

fn create_room(&self, channel: &Channel, rocketchat_server_id: i32, user_id: UserId) -> Result<Room> {
let bot_matrix_user_id = self.config.matrix_bot_user_id()?;
let matrix_room_id = self.matrix_api.create_room(channel.name.clone(), user_id)?;
let matrix_room_id = self.matrix_api.create_room(channel.name.clone())?;
self.matrix_api.set_default_powerlevels(matrix_room_id.clone(), bot_matrix_user_id.clone())?;
self.matrix_api.invite(matrix_room_id.clone(), user_id.clone())?;
let new_room = NewRoom {
matrix_room_id: matrix_room_id.clone(),
display_name: channel.name.clone(),
Expand Down
107 changes: 105 additions & 2 deletions tests/admin_commands_bridge.rs
Expand Up @@ -19,18 +19,23 @@ use ruma_client_api::Endpoint;
use ruma_client_api::r0::membership::invite_user::Endpoint as InviteEndpoint;
use ruma_client_api::r0::room::create_room::Endpoint as CreateRoomEndpoint;
use ruma_client_api::r0::send::send_message_event::Endpoint as SendMessageEventEndpoint;
use ruma_client_api::r0::send::send_state_event_for_empty_key::Endpoint as SendStateEventForEmptyKeyEndpoint;
use ruma_client_api::r0::sync::get_member_events::{self, Endpoint as GetMemberEventsEndpoint};
use ruma_identifiers::{RoomId, UserId};


#[test]
fn successfully_bridge_a_rocketchat_room() {
let (message_forwarder, receiver) = MessageForwarder::new();
let (invite_forwarder, invite_receiver) = MessageForwarder::new();
let (state_forwarder, state_receiver) = MessageForwarder::new();
let mut matrix_router = Router::new();
matrix_router.put(SendMessageEventEndpoint::router_path(), message_forwarder, "send_message_event");
matrix_router.post(CreateRoomEndpoint::router_path(),
handlers::MatrixCreateRoom { room_id: RoomId::try_from("!joined_channel_id:localhost").unwrap() },
"create_room");
matrix_router.put(SendStateEventForEmptyKeyEndpoint::router_path(), state_forwarder, "send_state_event_for_key");
matrix_router.post(InviteEndpoint::router_path(), invite_forwarder, "invite_user");
let mut channels = HashMap::new();
channels.insert("joined_channel", vec!["spec_user"]);

Expand All @@ -54,9 +59,23 @@ fn successfully_bridge_a_rocketchat_room() {
UserId::try_from("@spec_user:localhost").unwrap(),
"bridge joined_channel".to_string());

let invite_received_by_matrix = invite_receiver.recv_timeout(default_timeout()).unwrap();
assert!(invite_received_by_matrix.contains("@spec_user:localhost"));

let message_received_by_matrix = receiver.recv_timeout(default_timeout()).unwrap();
assert!(message_received_by_matrix.contains("joined_channel is now bridged."));

let set_room_name_received_by_matrix = state_receiver.recv_timeout(default_timeout()).unwrap();
assert!(set_room_name_received_by_matrix.contains("Admin Room (Rocket.Chat)"));

// only moderators and admins can invite other users
let power_levels_received_by_matrix = state_receiver.recv_timeout(default_timeout()).unwrap();
assert!(power_levels_received_by_matrix.contains("\"invite\":50"));
assert!(power_levels_received_by_matrix.contains("\"kick\":50"));
assert!(power_levels_received_by_matrix.contains("\"ban\":50"));
assert!(power_levels_received_by_matrix.contains("\"redact\":50"));
assert!(power_levels_received_by_matrix.contains("@rocketchat:localhost"));

// spec_user accepts invite from bot user
helpers::join(&test.config.as_url,
RoomId::try_from("!joined_channel_id:localhost").unwrap(),
Expand Down Expand Up @@ -175,8 +194,11 @@ fn successfully_bridge_a_rocketchat_room_that_an_other_user_already_bridged() {
RoomId::try_from("!joined_channel_id:localhost").unwrap(),
UserId::try_from("@other_user:localhost").unwrap());

let invite_received_by_matrix = invite_receiver.recv_timeout(default_timeout()).unwrap();
assert!(invite_received_by_matrix.contains("@other_user:localhost"));
let spec_user_invite_received_by_matrix = invite_receiver.recv_timeout(default_timeout()).unwrap();
assert!(spec_user_invite_received_by_matrix.contains("@spec_user:localhost"));

let other_user_invite_received_by_matrix = invite_receiver.recv_timeout(default_timeout()).unwrap();
assert!(other_user_invite_received_by_matrix.contains("@other_user:localhost"));

let connection = test.connection_pool.get().unwrap();
let room = Room::find(&connection, &RoomId::try_from("!joined_channel_id:localhost").unwrap()).unwrap();
Expand Down Expand Up @@ -398,6 +420,87 @@ fn the_user_gets_a_message_when_creating_the_room_failes() {
assert!(message_received_by_matrix.contains("An internal error occurred"));
}

#[test]
fn the_user_gets_a_message_when_setting_the_powerlevels_failes() {
let (message_forwarder, receiver) = MessageForwarder::new();
let mut matrix_router = Router::new();
matrix_router.put(SendMessageEventEndpoint::router_path(), message_forwarder, "send_message_event");
matrix_router.post(CreateRoomEndpoint::router_path(),
handlers::MatrixCreateRoom { room_id: RoomId::try_from("!joined_channel_id:localhost").unwrap() },
"create_room");
matrix_router.put(SendStateEventForEmptyKeyEndpoint::router_path(),
handlers::MatrixConditionalErrorResponder {
status: status::InternalServerError,
message: "Could not set power levels".to_string(),
conditional_content: "invite",
},
"set_power_levels");
let mut channels = HashMap::new();
channels.insert("joined_channel", vec!["spec_user"]);
let test = Test::new()
.with_matrix_routes(matrix_router)
.with_rocketchat_mock()
.with_connected_admin_room()
.with_logged_in_user()
.with_custom_channel_list(channels)
.run();

// discard welcome message
receiver.recv_timeout(default_timeout()).unwrap();
// discard connect message
receiver.recv_timeout(default_timeout()).unwrap();
// discard login message
receiver.recv_timeout(default_timeout()).unwrap();

helpers::send_room_message_from_matrix(&test.config.as_url,
RoomId::try_from("!admin:localhost").unwrap(),
UserId::try_from("@spec_user:localhost").unwrap(),
"bridge joined_channel".to_string());

let message_received_by_matrix = receiver.recv_timeout(default_timeout()).unwrap();
assert!(message_received_by_matrix.contains("An internal error occurred"));
}

#[test]
fn the_user_gets_a_message_when_inviting_the_user_failes() {
let (message_forwarder, receiver) = MessageForwarder::new();
let mut matrix_router = Router::new();
matrix_router.put(SendMessageEventEndpoint::router_path(), message_forwarder, "send_message_event");
matrix_router.post(CreateRoomEndpoint::router_path(),
handlers::MatrixCreateRoom { room_id: RoomId::try_from("!joined_channel_id:localhost").unwrap() },
"create_room");
matrix_router.post(InviteEndpoint::router_path(),
handlers::MatrixErrorResponder {
status: status::InternalServerError,
message: "Could not invite user".to_string(),
},
"invite");
let mut channels = HashMap::new();
channels.insert("joined_channel", vec!["spec_user"]);
let test = Test::new()
.with_matrix_routes(matrix_router)
.with_rocketchat_mock()
.with_connected_admin_room()
.with_logged_in_user()
.with_custom_channel_list(channels)
.run();

// discard welcome message
receiver.recv_timeout(default_timeout()).unwrap();
// discard connect message
receiver.recv_timeout(default_timeout()).unwrap();
// discard login message
receiver.recv_timeout(default_timeout()).unwrap();

helpers::send_room_message_from_matrix(&test.config.as_url,
RoomId::try_from("!admin:localhost").unwrap(),
UserId::try_from("@spec_user:localhost").unwrap(),
"bridge joined_channel".to_string());

let message_received_by_matrix = receiver.recv_timeout(default_timeout()).unwrap();
assert!(message_received_by_matrix.contains("An internal error occurred"));
}

#[test]
fn the_user_gets_a_message_when_the_create_room_response_cannot_be_deserialized() {
let (message_forwarder, receiver) = MessageForwarder::new();
Expand Down
19 changes: 12 additions & 7 deletions tests/forward_rocketchat_to_matrix.rs
Expand Up @@ -60,9 +60,11 @@ fn successfully_forwards_a_text_message_from_rocketchat_to_matrix_when_the_user_

helpers::simulate_message_from_rocketchat(&test.config.as_url, &payload);

// receive the invite message
let invite_message = invite_receiver.recv_timeout(default_timeout()).unwrap();
assert!(invite_message.contains("@rocketchat_new_user_id_1:localhost"));
// receive the invite messages
let spec_user_invite_message = invite_receiver.recv_timeout(default_timeout()).unwrap();
assert!(spec_user_invite_message.contains("@spec_user:localhost"));
let new_user_invite_message = invite_receiver.recv_timeout(default_timeout()).unwrap();
assert!(new_user_invite_message.contains("@rocketchat_new_user_id_1:localhost"));

// discard admin room join
join_receiver.recv_timeout(default_timeout()).unwrap();
Expand Down Expand Up @@ -163,9 +165,11 @@ fn successfully_forwards_a_text_message_from_rocketchat_to_matrix_when_the_user_

helpers::simulate_message_from_rocketchat(&test.config.as_url, &payload);

// receive the invite message
let invite_message = invite_receiver.recv_timeout(default_timeout()).unwrap();
assert!(invite_message.contains("@rocketchat_virtual_spec_user_id_1:localhost"));
// receive the invite messages
let spec_user_invite_message = invite_receiver.recv_timeout(default_timeout()).unwrap();
assert!(spec_user_invite_message.contains("@spec_user:localhost"));
let virtual_user_invite_message = invite_receiver.recv_timeout(default_timeout()).unwrap();
assert!(virtual_user_invite_message.contains("@rocketchat_virtual_spec_user_id_1:localhost"));

// discard admin room join
join_receiver.recv_timeout(default_timeout()).unwrap();
Expand Down Expand Up @@ -356,9 +360,10 @@ fn no_message_is_forwarded_when_inviting_the_user_failes() {
let mut matrix_router = Router::new();
matrix_router.put(SendMessageEventEndpoint::router_path(), message_forwarder, "send_message_event");
matrix_router.post(InviteUserEndpoint::router_path(),
handlers::MatrixErrorResponder {
handlers::MatrixConditionalErrorResponder {
status: status::InternalServerError,
message: "Could not invite user".to_string(),
conditional_content: "new_user",
},
"invite_user");

Expand Down
25 changes: 25 additions & 0 deletions tests/matrix-rocketchat-test/handlers.rs
@@ -1,4 +1,5 @@
use std::collections::HashMap;
use std::io::Read;
use rand::{Rng, thread_rng};

use iron::prelude::*;
Expand Down Expand Up @@ -216,6 +217,30 @@ impl Handler for MatrixErrorResponder {
}
}

pub struct MatrixConditionalErrorResponder {
pub status: status::Status,
pub message: String,
pub conditional_content: &'static str,
}

impl Handler for MatrixConditionalErrorResponder {
fn handle(&self, request: &mut Request) -> IronResult<Response> {
let mut request_payload = String::new();
request.body.read_to_string(&mut request_payload).unwrap();

if request_payload.contains(self.conditional_content) {
let error_response = MatrixErrorResponse {
errcode: "1234".to_string(),
error: self.message.clone(),
};
let payload = serde_json::to_string(&error_response).unwrap();
Ok(Response::with((self.status, payload)))
} else {
Ok(Response::with((status::Ok, "{}".to_string())))
}
}
}

pub struct InvalidJsonResponse {
pub status: status::Status,
}
Expand Down

0 comments on commit 37a435e

Please sign in to comment.