From 557c5b479275ae6b278573ffed240a9d02f55f0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Br=C3=B6nnimann?= Date: Thu, 16 Mar 2017 22:05:47 +0100 Subject: [PATCH] Create a virtual user even if the the user already has a user Because only virtual users can be impersonated by the application service --- migrations/20161204155847_create_users/up.sql | 1 - .../up.sql | 4 +- src/matrix-rocketchat/api/matrix/r0.rs | 6 +- src/matrix-rocketchat/api/rest_api.rs | 6 +- src/matrix-rocketchat/api/rocketchat/v1.rs | 6 +- src/matrix-rocketchat/db/schema.rs | 2 +- src/matrix-rocketchat/db/user.rs | 18 +-- .../db/user_on_rocketchat_server.rs | 17 ++- .../handlers/events/command_handler.rs | 36 +++--- .../handlers/events/room_handler.rs | 14 ++- .../handlers/rocketchat/forwarder.rs | 79 ++++++------ .../middleware/rocketchat.rs | 21 ++-- src/matrix-rocketchat/server.rs | 1 - tests/admin_commands_bridge.rs | 7 +- tests/admin_commands_login.rs | 56 +++++++-- tests/matrix-rocketchat-test/handlers.rs | 16 +-- tests/matrix-rocketchat-test/helpers.rs | 12 +- tests/matrix-rocketchat-test/lib.rs | 29 +++-- .../message_forwarder.rs | 6 +- tests/rocketchat.rs | 117 +++++++++++++++++- 20 files changed, 315 insertions(+), 139 deletions(-) diff --git a/migrations/20161204155847_create_users/up.sql b/migrations/20161204155847_create_users/up.sql index 4406db2..f3e48a3 100644 --- a/migrations/20161204155847_create_users/up.sql +++ b/migrations/20161204155847_create_users/up.sql @@ -2,7 +2,6 @@ CREATE TABLE users ( matrix_user_id VARCHAR NOT NULL, display_name VARCHAR NOT NULL, language VARCHAR NOT NULL, - is_virtual_user BOOLEAN NOT NULL, last_message_sent BIG INT NOT NULL DEFAULT 0, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, diff --git a/migrations/20170121180854_create_users_on_rocketchat_servers/up.sql b/migrations/20170121180854_create_users_on_rocketchat_servers/up.sql index 63c3fbb..77e0e98 100644 --- a/migrations/20170121180854_create_users_on_rocketchat_servers/up.sql +++ b/migrations/20170121180854_create_users_on_rocketchat_servers/up.sql @@ -1,10 +1,10 @@ CREATE TABLE users_on_rocketchat_servers ( + is_virtual_user BOOLEAN NOT NULL, matrix_user_id VARCHAR NOT NULL, rocketchat_server_id INTEGER NOT NULL, rocketchat_user_id VARCHAR, rocketchat_auth_token VARCHAR, rocketchat_username VARCHAR, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - UNIQUE (rocketchat_user_id) + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ) diff --git a/src/matrix-rocketchat/api/matrix/r0.rs b/src/matrix-rocketchat/api/matrix/r0.rs index 3a9902b..9930022 100644 --- a/src/matrix-rocketchat/api/matrix/r0.rs +++ b/src/matrix-rocketchat/api/matrix/r0.rs @@ -127,8 +127,8 @@ impl super::MatrixApi for MatrixApi { debug!(self.logger, "User {} successfully invited into room {}", - matrix_room_id, - matrix_user_id); + matrix_user_id, + matrix_room_id); Ok(()) } @@ -169,7 +169,7 @@ impl super::MatrixApi for MatrixApi { let body_params = register::BodyParams { bind_email: None, password: None, - username: Some(user_id_local_part), + username: Some(user_id_local_part.to_lowercase()), device_id: None, initial_device_display_name: None, auth: None, diff --git a/src/matrix-rocketchat/api/rest_api.rs b/src/matrix-rocketchat/api/rest_api.rs index 16d0bcb..1aa020c 100644 --- a/src/matrix-rocketchat/api/rest_api.rs +++ b/src/matrix-rocketchat/api/rest_api.rs @@ -70,9 +70,9 @@ impl RestApi { } fn encode_url(base: String, parameters: &HashMap<&str, &str>) -> Result { - let query_string = parameters.iter() - .fold("?".to_string(), - |init, (k, v)| [init, [k.to_string(), v.to_string()].join("=")].join("&")); + let query_string = parameters.iter().fold("?".to_string(), |init, (k, v)| { + [init, [k.to_string(), v.to_string()].join("=")].join("&") + }); let url_string = [base, query_string].join(""); let url = Url::parse(&url_string).chain_err(|| ErrorKind::ApiCallFailed(url_string))?; Ok(format!("{}", url)) diff --git a/src/matrix-rocketchat/api/rocketchat/v1.rs b/src/matrix-rocketchat/api/rocketchat/v1.rs index f3a6b64..252e4db 100644 --- a/src/matrix-rocketchat/api/rocketchat/v1.rs +++ b/src/matrix-rocketchat/api/rocketchat/v1.rs @@ -241,9 +241,9 @@ fn build_error(endpoint: String, body: &str, status_code: &StatusCode) -> Error if *status_code == StatusCode::Unauthorized { return Error { - error_chain: ErrorKind::AuthenticationFailed(rocketchat_error_resp.message).into(), - user_message: Some(t!(["errors", "authentication_failed"])), - }; + error_chain: ErrorKind::AuthenticationFailed(rocketchat_error_resp.message).into(), + user_message: Some(t!(["errors", "authentication_failed"])), + }; } Error::from(ErrorKind::RocketchatError(rocketchat_error_resp.message)) diff --git a/src/matrix-rocketchat/db/schema.rs b/src/matrix-rocketchat/db/schema.rs index 9966606..b7ed0c5 100644 --- a/src/matrix-rocketchat/db/schema.rs +++ b/src/matrix-rocketchat/db/schema.rs @@ -5,7 +5,6 @@ table! { matrix_user_id -> Text, display_name -> Text, language -> Text, - is_virtual_user -> Bool, last_message_sent -> BigInt, created_at -> Timestamp, updated_at -> Timestamp, @@ -46,6 +45,7 @@ table! { table! { users_on_rocketchat_servers (matrix_user_id, rocketchat_server_id) { + is_virtual_user -> Bool, matrix_user_id -> Text, rocketchat_server_id -> Integer, rocketchat_user_id -> Nullable, diff --git a/src/matrix-rocketchat/db/user.rs b/src/matrix-rocketchat/db/user.rs index a73ee45..070a375 100644 --- a/src/matrix-rocketchat/db/user.rs +++ b/src/matrix-rocketchat/db/user.rs @@ -16,8 +16,6 @@ pub struct User { pub display_name: String, /// The language the user prefers to get messages in. pub language: String, - /// Flag to indicate if the user is only used to send messages from Rocket.Chat - pub is_virtual_user: bool, /// Time when the user sent the last message in seconds since UNIX_EPOCH pub last_message_sent: i64, /// created timestamp @@ -36,20 +34,23 @@ pub struct NewUser<'a> { pub display_name: String, /// The language the user prefers to get messages in. pub language: &'a str, - /// Flag to indicate if the user is only used to send messages from Rocket.Chat - pub is_virtual_user: bool, } impl User { /// Insert a new `User` into the database. pub fn insert(connection: &SqliteConnection, user: &NewUser) -> Result { - diesel::insert(user).into(users::table).execute(connection).chain_err(|| ErrorKind::DBInsertError)?; + diesel::insert(user).into(users::table) + .execute(connection) + .chain_err(|| ErrorKind::DBInsertError)?; User::find(connection, &user.matrix_user_id) } /// Find a `User` by his matrix user ID, return an error if the user is not found pub fn find(connection: &SqliteConnection, matrix_user_id: &UserId) -> Result { - users::table.find(matrix_user_id).first(connection).chain_err(|| ErrorKind::DBSelectError).map_err(Error::from) + users::table.find(matrix_user_id) + .first(connection) + .chain_err(|| ErrorKind::DBSelectError) + .map_err(Error::from) } /// Find or create `User` with a given Matrix user ID. @@ -61,7 +62,6 @@ impl User { matrix_user_id: matrix_user_id.clone(), display_name: matrix_user_id.to_string(), language: DEFAULT_LANGUAGE, - is_virtual_user: false, }; User::insert(connection, &new_user) } @@ -70,7 +70,9 @@ impl User { /// Find a `User` by his matrix user ID. Returns `None`, if the user is not found. pub fn find_by_matrix_user_id(connection: &SqliteConnection, matrix_user_id: &UserId) -> Result> { - let users = users::table.find(matrix_user_id).load(connection).chain_err(|| ErrorKind::DBSelectError)?; + let users = users::table.find(matrix_user_id) + .load(connection) + .chain_err(|| ErrorKind::DBSelectError)?; Ok(users.into_iter().next()) } } diff --git a/src/matrix-rocketchat/db/user_on_rocketchat_server.rs b/src/matrix-rocketchat/db/user_on_rocketchat_server.rs index 6207e3f..c056beb 100644 --- a/src/matrix-rocketchat/db/user_on_rocketchat_server.rs +++ b/src/matrix-rocketchat/db/user_on_rocketchat_server.rs @@ -10,6 +10,8 @@ use super::User; /// A user on a Rocket.Chat server. #[derive(Debug, Queryable)] pub struct UserOnRocketchatServer { + /// Flag to indicate if the user is only used to send messages from Rocket.Chat + pub is_virtual_user: bool, /// The users unique id on the Rocket.Chat server. pub matrix_user_id: UserId, /// The unique id for the Rocket.Chat server @@ -30,6 +32,8 @@ pub struct UserOnRocketchatServer { #[derive(Insertable)] #[table_name="users_on_rocketchat_servers"] pub struct NewUserOnRocketchatServer { + /// Flag to indicate if the user is only used to send messages from Rocket.Chat + pub is_virtual_user: bool, /// The users unique id on the Rocket.Chat server. pub matrix_user_id: UserId, /// The unique id for the Rocket.Chat server @@ -47,9 +51,11 @@ impl UserOnRocketchatServer { pub fn upsert(connection: &SqliteConnection, user_on_rocketchat_server: &NewUserOnRocketchatServer) -> Result { - let users_on_rocketchat_server: Vec = users_on_rocketchat_servers::table - .find((&user_on_rocketchat_server.matrix_user_id, &user_on_rocketchat_server.rocketchat_server_id)) - .load(connection).chain_err(|| ErrorKind::DBSelectError)?; + let users_on_rocketchat_server: Vec = + users_on_rocketchat_servers::table.find((&user_on_rocketchat_server.matrix_user_id, + &user_on_rocketchat_server.rocketchat_server_id)) + .load(connection) + .chain_err(|| ErrorKind::DBSelectError)?; match users_on_rocketchat_server.into_iter().next() { Some(existing_user_on_rocketchat_server) => { @@ -85,9 +91,10 @@ impl UserOnRocketchatServer { /// Find a `UserOnRocketchatServer` by his Rocket.Chat user ID. Returns `None`, if the `UserOnRocketchatServer` is not found. pub fn find_by_rocketchat_user_id(connection: &SqliteConnection, rocketchat_server_id: i32, - rocketchat_user_id: String) + rocketchat_user_id: String, + is_virtual_user: bool) -> Result> { - let users_on_rocketchat_servers = users_on_rocketchat_servers::table.filter(users_on_rocketchat_servers::rocketchat_server_id.eq(rocketchat_server_id).and(users_on_rocketchat_servers::rocketchat_user_id.eq(rocketchat_user_id))) + let users_on_rocketchat_servers = users_on_rocketchat_servers::table.filter(users_on_rocketchat_servers::rocketchat_server_id.eq(rocketchat_server_id).and(users_on_rocketchat_servers::rocketchat_user_id.eq(rocketchat_user_id)).and(users_on_rocketchat_servers::is_virtual_user.eq(is_virtual_user))) .load(connection) .chain_err(|| ErrorKind::DBSelectError)?; Ok(users_on_rocketchat_servers.into_iter().next()) diff --git a/src/matrix-rocketchat/handlers/events/command_handler.rs b/src/matrix-rocketchat/handlers/events/command_handler.rs index 78588b7..35585bb 100644 --- a/src/matrix-rocketchat/handlers/events/command_handler.rs +++ b/src/matrix-rocketchat/handlers/events/command_handler.rs @@ -103,6 +103,7 @@ impl<'a> CommandHandler<'a> { room.set_rocketchat_server_id(self.connection, rocketchat_server.id)?; let new_user_on_rocketchat_server = NewUserOnRocketchatServer { + is_virtual_user: false, matrix_user_id: event.user_id.clone(), rocketchat_server_id: rocketchat_server.id, rocketchat_user_id: None, @@ -162,8 +163,9 @@ impl<'a> CommandHandler<'a> { None => t!(["admin_room", "connection_instructions"]).with_vars(vec![("as_url", self.config.as_url.clone())]), }; - self.matrix_api - .send_text_message_event(event.room_id.clone(), self.config.matrix_bot_user_id()?, body.l(&user.language)) + self.matrix_api.send_text_message_event(event.room_id.clone(), + self.config.matrix_bot_user_id()?, + body.l(&user.language)) } fn login(&self, event: &MessageEvent, rocketchat_server: &RocketchatServer, message: &str) -> Result<()> { @@ -182,8 +184,8 @@ impl<'a> CommandHandler<'a> { let (rocketchat_user_id, rocketchat_auth_token) = rocketchat_api.login(username, &password)?; user_on_rocketchat_server.set_credentials(self.connection, - Some(rocketchat_user_id.clone()), - Some(rocketchat_auth_token.clone()))?; + Some(rocketchat_user_id.clone()), + Some(rocketchat_auth_token.clone()))?; let username = rocketchat_api.username(rocketchat_user_id, rocketchat_auth_token)?; user_on_rocketchat_server.set_rocketchat_username(self.connection, Some(username))?; @@ -201,8 +203,9 @@ impl<'a> CommandHandler<'a> { let rocketchat_api = RocketchatApi::new(rocketchat_server.rocketchat_url.clone(), rocketchat_server.rocketchat_token.clone(), self.logger.clone())?; - let channels = rocketchat_api.channels_list(user_on_rocketchat_server.rocketchat_user_id.unwrap_or_default(), - user_on_rocketchat_server.rocketchat_auth_token.unwrap_or_default())?; + let channels = + rocketchat_api.channels_list(user_on_rocketchat_server.rocketchat_user_id.unwrap_or_default(), + user_on_rocketchat_server.rocketchat_auth_token.unwrap_or_default())?; let bot_matrix_user_id = self.config.matrix_bot_user_id()?; let channels_list = self.build_channels_list(rocketchat_server.id, &event.user_id, channels)?; @@ -221,7 +224,9 @@ impl<'a> CommandHandler<'a> { let channels = rocketchat_api.channels_list(user_on_rocketchat_server.rocketchat_user_id.clone().unwrap_or_default(), - user_on_rocketchat_server.rocketchat_auth_token.clone().unwrap_or_default())?; + user_on_rocketchat_server.rocketchat_auth_token + .clone() + .unwrap_or_default())?; let mut command = message.split_whitespace().collect::>().into_iter(); let channel_name = command.by_ref().nth(1).unwrap_or_default(); @@ -242,12 +247,10 @@ impl<'a> CommandHandler<'a> { } let username = user_on_rocketchat_server.rocketchat_username.clone().unwrap_or_default(); - if !channel.usernames - .iter() - .any(|u| u == &username) { + if !channel.usernames.iter().any(|u| u == &username) { bail_error!(ErrorKind::RocketchatJoinFirst(channel_name.to_string()), - t!(["errors", "rocketchat_join_first"]) - .with_vars(vec![("channel_name", channel_name.to_string())])); + t!(["errors", "rocketchat_join_first"]).with_vars(vec![("channel_name", + channel_name.to_string())])); } let room = @@ -264,11 +267,12 @@ impl<'a> CommandHandler<'a> { UserInRoom::insert(self.connection, &new_user_in_room)?; let bot_matrix_user_id = self.config.matrix_bot_user_id()?; - let message = t!(["admin_room", "room_successfully_bridged"]) - .with_vars(vec![("channel_name", channel.name.clone())]); + let message = + t!(["admin_room", "room_successfully_bridged"]).with_vars(vec![("channel_name", channel.name.clone())]); info!(self.logger, "Successfully bridged room {}", channel.id.clone()); - self.matrix_api - .send_text_message_event(event.room_id.clone(), bot_matrix_user_id, message.l(&user.language)) + self.matrix_api.send_text_message_event(event.room_id.clone(), + bot_matrix_user_id, + message.l(&user.language)) }) .map_err(Error::from) } diff --git a/src/matrix-rocketchat/handlers/events/room_handler.rs b/src/matrix-rocketchat/handlers/events/room_handler.rs index 9e9f0d8..1bdb711 100644 --- a/src/matrix-rocketchat/handlers/events/room_handler.rs +++ b/src/matrix-rocketchat/handlers/events/room_handler.rs @@ -106,8 +106,8 @@ impl<'a> RoomHandler<'a> { fn handle_bot_join(&self, matrix_room_id: RoomId, matrix_bot_user_id: UserId) -> Result<()> { let room = Room::find(self.connection, &matrix_room_id)?; let users_in_room = room.users(self.connection)?; - let invitation_submitter = users_in_room.first() - .expect("There is always a user in the room, because this user invited the bot"); + let invitation_submitter = + users_in_room.first().expect("There is always a user in the room, because this user invited the bot"); if !self.is_private_room(matrix_room_id.clone())? { return self.handle_non_private_room(&room, invitation_submitter, matrix_bot_user_id); @@ -156,7 +156,9 @@ impl<'a> RoomHandler<'a> { } fn is_private_room(&self, matrix_room_id: RoomId) -> Result { - Ok(self.matrix_api.get_room_members(matrix_room_id)?.len() <= 2) + Ok(self.matrix_api + .get_room_members(matrix_room_id)? + .len() <= 2) } fn handle_non_private_room(&self, room: &Room, invitation_submitter: &User, matrix_bot_user_id: UserId) -> Result<()> { @@ -179,8 +181,10 @@ impl<'a> RoomHandler<'a> { fn admin_room_language(&self, room: &Room) -> Result { let matrix_bot_user_id = self.config.matrix_bot_user_id()?; - let users: Vec = - room.users(self.connection)?.into_iter().filter(|user| user.matrix_user_id != matrix_bot_user_id).collect(); + let users: Vec = room.users(self.connection)? + .into_iter() + .filter(|user| user.matrix_user_id != matrix_bot_user_id) + .collect(); let user = users.first().expect("An admin room always contains another user"); Ok(user.language.clone()) } diff --git a/src/matrix-rocketchat/handlers/rocketchat/forwarder.rs b/src/matrix-rocketchat/handlers/rocketchat/forwarder.rs index cea8b69..fcad485 100644 --- a/src/matrix-rocketchat/handlers/rocketchat/forwarder.rs +++ b/src/matrix-rocketchat/handlers/rocketchat/forwarder.rs @@ -27,74 +27,77 @@ pub struct Forwarder<'a> { impl<'a> Forwarder<'a> { /// Send a message to the Matrix channel. pub fn send(&self, rocketchat_server: &RocketchatServer, message: &Message) -> Result<()> { - self.connection - .transaction(|| { - let user_on_rocketchat_server = - match UserOnRocketchatServer::find_by_rocketchat_user_id(self.connection, - rocketchat_server.id, - message.user_id.clone())? { - Some(user_on_rocketchat_server) => user_on_rocketchat_server, - None => self.create_virtual_user_on_rocketchat_server(rocketchat_server.id, message)?, - }; + let user_on_rocketchat_server = match UserOnRocketchatServer::find_by_rocketchat_user_id(self.connection, + rocketchat_server.id, + message.user_id + .clone(), + true)? { + Some(user_on_rocketchat_server) => user_on_rocketchat_server, + None => { + self.connection + .transaction(|| self.create_virtual_user_on_rocketchat_server(rocketchat_server.id, message))? + } + }; - let room = match Room::find_by_rocketchat_room_id(self.connection, - rocketchat_server.id, - message.channel_id.clone())? { - Some(room) => room, - None => { - debug!(self.logger, - "Ignoring message from Rocket.Chat channel `{}`, because the channel is not bridged.", - message.channel_id); - return Ok(()); - } - }; + let room = + match Room::find_by_rocketchat_room_id(self.connection, rocketchat_server.id, message.channel_id.clone())? { + Some(room) => room, + None => { + debug!(self.logger, + "Ignoring message from Rocket.Chat channel `{}`, because the channel is not bridged.", + message.channel_id); + return Ok(()); + } + }; - let user_in_room = - UserInRoom::find_by_matrix_user_id_and_matrix_room_id(self.connection, - &user_on_rocketchat_server.matrix_user_id, - &room.matrix_room_id)?; - if user_in_room.is_none() { - self.add_virtual_user_to_room(user_on_rocketchat_server.matrix_user_id.clone(), - room.matrix_room_id.clone())?; - } + let user_in_room = + UserInRoom::find_by_matrix_user_id_and_matrix_room_id(self.connection, + &user_on_rocketchat_server.matrix_user_id, + &room.matrix_room_id)?; + if user_in_room.is_none() { + self.add_virtual_user_to_room(user_on_rocketchat_server.matrix_user_id.clone(), room.matrix_room_id.clone())?; + } - self.matrix_api.send_text_message_event(room.matrix_room_id, - user_on_rocketchat_server.matrix_user_id, - message.text.clone()) - }) - .map_err(Error::from) + self.matrix_api.send_text_message_event(room.matrix_room_id, + user_on_rocketchat_server.matrix_user_id, + message.text.clone()) } fn create_virtual_user_on_rocketchat_server(&self, rocketchat_server_id: i32, message: &Message) -> Result { - let user_id_local_part = format!("@{}_{}_{}", self.config.sender_localpart, message.user_id, rocketchat_server_id); - self.matrix_api.register(user_id_local_part.clone())?; - let user_id = format!("{}:{}", user_id_local_part, self.config.hs_domain); + + let user_id_local_part = format!("{}_{}_{}", self.config.sender_localpart, message.user_id, rocketchat_server_id); + let user_id = format!("@{}:{}", user_id_local_part, self.config.hs_domain); let matrix_user_id = UserId::try_from(&user_id).chain_err(|| ErrorKind::InvalidUserId(user_id))?; let new_user = NewUser { display_name: message.user_name.clone(), - is_virtual_user: true, language: DEFAULT_LANGUAGE, matrix_user_id: matrix_user_id.clone(), }; User::insert(self.connection, &new_user)?; let new_user_on_rocketchat_server = NewUserOnRocketchatServer { + is_virtual_user: true, matrix_user_id: matrix_user_id, rocketchat_auth_token: None, rocketchat_server_id: rocketchat_server_id, rocketchat_user_id: Some(message.user_id.clone()), rocketchat_username: Some(message.user_name.clone()), }; - UserOnRocketchatServer::upsert(self.connection, &new_user_on_rocketchat_server) + let user_on_rocketchat_server = UserOnRocketchatServer::upsert(self.connection, &new_user_on_rocketchat_server)?; + + self.matrix_api.register(user_id_local_part.clone())?; + + Ok(user_on_rocketchat_server) } fn add_virtual_user_to_room(&self, matrix_user_id: UserId, matrix_room_id: RoomId) -> Result<()> { self.matrix_api.invite(matrix_room_id.clone(), matrix_user_id.clone())?; + self.matrix_api.join(matrix_room_id.clone(), matrix_user_id.clone())?; let new_user_in_room = NewUserInRoom { matrix_user_id: matrix_user_id, matrix_room_id: matrix_room_id, diff --git a/src/matrix-rocketchat/middleware/rocketchat.rs b/src/matrix-rocketchat/middleware/rocketchat.rs index 74d201b..40851c0 100644 --- a/src/matrix-rocketchat/middleware/rocketchat.rs +++ b/src/matrix-rocketchat/middleware/rocketchat.rs @@ -9,20 +9,25 @@ use errors::*; use log::*; /// Compares the supplied access token to the one that is in the config -pub struct RocketchatToken { -} +pub struct RocketchatToken {} impl BeforeMiddleware for RocketchatToken { fn before(&self, request: &mut Request) -> IronResult<()> { let logger = IronLogger::from_request(request)?; let mut payload = String::new(); - request.body.read_to_string(&mut payload).chain_err(|| ErrorKind::InternalServerError).map_err(Error::from)?; - let message = serde_json::from_str::(&payload).chain_err(|| { - ErrorKind::InvalidJSON(format!("Could not deserialize message that was sent to the rocketchat endpoint: \ - `{}`", - payload)) - }) + request.body + .read_to_string(&mut payload) + .chain_err(|| ErrorKind::InternalServerError) .map_err(Error::from)?; + let message = match serde_json::from_str::(&payload) { + Ok(message) => message, + Err(err) => { + let json_err = simple_error!(ErrorKind::InvalidJSON(format!("Could not deserialize message that was sent to the rocketchat endpoint: `{}`", + payload))); + error!(logger, err); + return Err(json_err.into()); + } + }; let token = match message.token.clone() { Some(token) => token, diff --git a/src/matrix-rocketchat/server.rs b/src/matrix-rocketchat/server.rs index fb0d078..4015712 100644 --- a/src/matrix-rocketchat/server.rs +++ b/src/matrix-rocketchat/server.rs @@ -85,7 +85,6 @@ impl<'a> Server<'a> { matrix_user_id: matrix_bot_user_id.clone(), display_name: matrix_bot_user_id.to_string(), language: DEFAULT_LANGUAGE, - is_virtual_user: false, }; User::insert(connection, &new_user)?; info!(self.logger, format!("Bot user {} successfully registered", matrix_bot_user_id)); diff --git a/tests/admin_commands_bridge.rs b/tests/admin_commands_bridge.rs index cdac2cd..2f867f4 100644 --- a/tests/admin_commands_bridge.rs +++ b/tests/admin_commands_bridge.rs @@ -84,7 +84,12 @@ fn successfully_bridge_a_rocketchat_room_that_an_other_user_already_bridged() { "other_room_members"); let mut rocketchat_router = Router::new(); - rocketchat_router.post(LOGIN_PATH, handlers::RocketchatLogin { successful: true }, "login"); + rocketchat_router.post(LOGIN_PATH, + handlers::RocketchatLogin { + successful: true, + rocketchat_user_id: None, + }, + "login"); rocketchat_router.get(ME_PATH, handlers::RocketchatMe { username: "spec_user".to_string() }, "me"); let mut channels = HashMap::new(); diff --git a/tests/admin_commands_login.rs b/tests/admin_commands_login.rs index 275e427..f25c433 100644 --- a/tests/admin_commands_login.rs +++ b/tests/admin_commands_login.rs @@ -25,7 +25,12 @@ fn sucessfully_login_via_chat_mesage() { let mut matrix_router = Router::new(); matrix_router.put(SendMessageEventEndpoint::router_path(), message_forwarder, "send_message_event"); let mut rocketchat_router = Router::new(); - rocketchat_router.post(LOGIN_PATH, handlers::RocketchatLogin { successful: true }, "login"); + rocketchat_router.post(LOGIN_PATH, + handlers::RocketchatLogin { + successful: true, + rocketchat_user_id: None, + }, + "login"); rocketchat_router.get(ME_PATH, handlers::RocketchatMe { username: "Spec user".to_string() }, "me"); let test = Test::new() .with_matrix_routes(matrix_router) @@ -49,7 +54,7 @@ fn sucessfully_login_via_chat_mesage() { let user_on_rocketchat_server = UserOnRocketchatServer::find(&connection, &UserId::try_from("@spec_user:localhost").unwrap(), rocketchat_server.id) - .unwrap(); + .unwrap(); assert_eq!(user_on_rocketchat_server.rocketchat_auth_token.unwrap(), "spec_auth_token"); let message_received_by_matrix = receiver.recv_timeout(default_timeout()).unwrap(); @@ -62,7 +67,12 @@ fn wrong_password_when_logging_in_via_chat_mesage() { let mut matrix_router = Router::new(); matrix_router.put(SendMessageEventEndpoint::router_path(), message_forwarder, "send_message_event"); let mut rocketchat_router = Router::new(); - rocketchat_router.post(LOGIN_PATH, handlers::RocketchatLogin { successful: false }, "login"); + rocketchat_router.post(LOGIN_PATH, + handlers::RocketchatLogin { + successful: false, + rocketchat_user_id: None, + }, + "login"); let test = Test::new() .with_matrix_routes(matrix_router) .with_rocketchat_mock() @@ -90,7 +100,12 @@ fn login_multiple_times() { let mut matrix_router = Router::new(); matrix_router.put(SendMessageEventEndpoint::router_path(), message_forwarder, "send_message_event"); let mut rocketchat_router = Router::new(); - rocketchat_router.post(LOGIN_PATH, handlers::RocketchatLogin { successful: true }, "login"); + rocketchat_router.post(LOGIN_PATH, + handlers::RocketchatLogin { + successful: true, + rocketchat_user_id: None, + }, + "login"); rocketchat_router.get(ME_PATH, handlers::RocketchatMe { username: "Spec user".to_string() }, "me"); let test = Test::new() .with_matrix_routes(matrix_router) @@ -123,7 +138,12 @@ fn the_user_can_login_again_on_the_same_server_with_a_new_admin_room() { let mut matrix_router = Router::new(); matrix_router.put(SendMessageEventEndpoint::router_path(), message_forwarder, "send_message_event"); let mut rocketchat_router = Router::new(); - rocketchat_router.post(LOGIN_PATH, handlers::RocketchatLogin { successful: true }, "login"); + rocketchat_router.post(LOGIN_PATH, + handlers::RocketchatLogin { + successful: true, + rocketchat_user_id: None, + }, + "login"); let test = Test::new() .with_matrix_routes(matrix_router) .with_rocketchat_mock() @@ -172,7 +192,12 @@ fn server_does_not_respond_when_logging_in_via_chat_mesage() { let mut matrix_router = Router::new(); matrix_router.put(SendMessageEventEndpoint::router_path(), message_forwarder, "send_message_event"); let mut rocketchat_router = Router::new(); - rocketchat_router.post(LOGIN_PATH, handlers::RocketchatLogin { successful: true }, "login"); + rocketchat_router.post(LOGIN_PATH, + handlers::RocketchatLogin { + successful: true, + rocketchat_user_id: None, + }, + "login"); let test = Test::new() .with_matrix_routes(matrix_router) .with_rocketchat_mock() @@ -260,7 +285,12 @@ fn the_user_gets_a_message_when_the_me_response_cannot_be_deserialized() { let mut matrix_router = Router::new(); matrix_router.put(SendMessageEventEndpoint::router_path(), message_forwarder, "send_message_event"); let mut rocketchat_router = Router::new(); - rocketchat_router.post(LOGIN_PATH, handlers::RocketchatLogin { successful: true }, "login"); + rocketchat_router.post(LOGIN_PATH, + handlers::RocketchatLogin { + successful: true, + rocketchat_user_id: None, + }, + "login"); rocketchat_router.get(ME_PATH, handlers::InvalidJsonResponse { status: status::Ok }, "me"); let test = Test::new() .with_matrix_routes(matrix_router) @@ -290,7 +320,12 @@ fn the_user_gets_a_message_when_the_me_endpoint_returns_an_error() { let mut matrix_router = Router::new(); matrix_router.put(SendMessageEventEndpoint::router_path(), message_forwarder, "send_message_event"); let mut rocketchat_router = Router::new(); - rocketchat_router.post(LOGIN_PATH, handlers::RocketchatLogin { successful: true }, "login"); + rocketchat_router.post(LOGIN_PATH, + handlers::RocketchatLogin { + successful: true, + rocketchat_user_id: None, + }, + "login"); rocketchat_router.get(ME_PATH, handlers::RocketchatErrorResponder { status: status::InternalServerError, @@ -323,10 +358,7 @@ fn attempt_to_login_when_the_admin_room_is_not_connected() { let (message_forwarder, receiver) = MessageForwarder::new(); let mut matrix_router = Router::new(); matrix_router.put(SendMessageEventEndpoint::router_path(), message_forwarder, "send_message_event"); - let test = Test::new() - .with_matrix_routes(matrix_router) - .with_admin_room() - .run(); + let test = Test::new().with_matrix_routes(matrix_router).with_admin_room().run(); helpers::send_room_message_from_matrix(&test.config.as_url, RoomId::try_from("!admin:localhost").unwrap(), diff --git a/tests/matrix-rocketchat-test/handlers.rs b/tests/matrix-rocketchat-test/handlers.rs index 6e16cdb..18d31d4 100644 --- a/tests/matrix-rocketchat-test/handlers.rs +++ b/tests/matrix-rocketchat-test/handlers.rs @@ -21,7 +21,7 @@ impl Handler for RocketchatInfo { let payload = r#"{ "version": "VERSION" }"# - .replace("VERSION", self.version); + .replace("VERSION", self.version); Ok(Response::with((status::Ok, payload))) } @@ -29,6 +29,7 @@ impl Handler for RocketchatInfo { pub struct RocketchatLogin { pub successful: bool, + pub rocketchat_user_id: Option, } impl Handler for RocketchatLogin { @@ -36,7 +37,8 @@ impl Handler for RocketchatLogin { let (status, payload) = match self.successful { true => { - let user_id: String = thread_rng().gen_ascii_chars().take(10).collect(); + let user_id: String = + self.rocketchat_user_id.clone().unwrap_or(thread_rng().gen_ascii_chars().take(10).collect()); (status::Ok, r#"{ "status": "success", @@ -45,7 +47,7 @@ impl Handler for RocketchatLogin { "userId": "USER_ID" } }"# - .replace("USER_ID", &user_id)) + .replace("USER_ID", &user_id)) } false => { (status::Unauthorized, @@ -53,7 +55,7 @@ impl Handler for RocketchatLogin { "status": "error", "message": "Unauthorized" }"# - .to_string()) + .to_string()) } }; @@ -70,7 +72,7 @@ impl Handler for RocketchatMe { let payload = r#"{ "username": "USERNAME" }"# - .replace("USERNAME", &self.username); + .replace("USERNAME", &self.username); Ok(Response::with((status::Ok, payload))) } @@ -103,8 +105,8 @@ impl Handler for RocketchatChannelsList { "sysMes": true, "_updatedAt": "2017-02-12T13:20:22.092Z" }"# - .replace("CHANNEL_NAME", channel_name) - .replace("CHANNEL_USERNAMES", &user_names.join("\",\"")); + .replace("CHANNEL_NAME", channel_name) + .replace("CHANNEL_USERNAMES", &user_names.join("\",\"")); channels.push(channel); } diff --git a/tests/matrix-rocketchat-test/helpers.rs b/tests/matrix-rocketchat-test/helpers.rs index acb829f..0afaa8d 100644 --- a/tests/matrix-rocketchat-test/helpers.rs +++ b/tests/matrix-rocketchat-test/helpers.rs @@ -93,9 +93,9 @@ pub fn leave_room(as_url: &str, room_id: RoomId, user_id: UserId) { pub fn send_room_message_from_matrix(as_url: &str, room_id: RoomId, user_id: UserId, body: String) { let message_event = MessageEvent { content: MessageEventContent::Text(TextMessageEventContent { - body: body, - msgtype: MessageType::Text, - }), + body: body, + msgtype: MessageType::Text, + }), event_id: EventId::new("localhost").unwrap(), event_type: EventType::RoomMessage, room_id: room_id, @@ -112,9 +112,9 @@ pub fn send_room_message_from_matrix(as_url: &str, room_id: RoomId, user_id: Use pub fn send_emote_message_from_matrix(as_url: &str, room_id: RoomId, user_id: UserId, body: String) { let message_event = MessageEvent { content: MessageEventContent::Text(TextMessageEventContent { - body: body, - msgtype: MessageType::Emote, - }), + body: body, + msgtype: MessageType::Emote, + }), event_id: EventId::new("localhost").unwrap(), event_type: EventType::RoomMessage, room_id: room_id, diff --git a/tests/matrix-rocketchat-test/lib.rs b/tests/matrix-rocketchat-test/lib.rs index fd2645b..b959306 100644 --- a/tests/matrix-rocketchat-test/lib.rs +++ b/tests/matrix-rocketchat-test/lib.rs @@ -46,7 +46,6 @@ use r2d2_diesel::ConnectionManager; use router::Router; use ruma_identifiers::{RoomId, UserId}; use ruma_client_api::Endpoint; -use ruma_client_api::r0::membership::join_room_by_id::Endpoint as JoinEndpoint; use ruma_client_api::r0::room::create_room::Endpoint as CreateRoomEndpoint; use ruma_client_api::r0::sync::get_member_events::Endpoint as GetMemberEventsEndpoint; use slog::{DrainExt, Record}; @@ -249,7 +248,6 @@ impl Test { UserId::try_from("@rocketchat:localhost").unwrap()], }; router.get(GetMemberEventsEndpoint::router_path(), room_members, "room_members"); - router.post(JoinEndpoint::router_path(), handlers::EmptyJson {}, "join"); } if let Some(bridged_room) = self.bridged_room { @@ -262,11 +260,11 @@ impl Test { } thread::spawn(move || { - let mut server = Iron::new(router); - server.threads = IRON_THREADS; - let listening = server.http(&hs_socket_addr).unwrap(); - hs_tx.send(listening).unwrap(); - }); + let mut server = Iron::new(router); + server.threads = IRON_THREADS; + let listening = server.http(&hs_socket_addr).unwrap(); + hs_tx.send(listening).unwrap(); + }); let hs_listening = hs_rx.recv_timeout(default_timeout()).unwrap(); self.hs_listening = Some(hs_listening); @@ -286,7 +284,12 @@ impl Test { "info"); if self.with_logged_in_user { - router.post(LOGIN_PATH, handlers::RocketchatLogin { successful: true }, "login"); + router.post(LOGIN_PATH, + handlers::RocketchatLogin { + successful: true, + rocketchat_user_id: Some("spec_user_id".to_string()), + }, + "login"); router.get(ME_PATH, handlers::RocketchatMe { username: "spec_user".to_string() }, "me"); } @@ -310,11 +313,11 @@ impl Test { } thread::spawn(move || { - let mut server = Iron::new(router); - server.threads = IRON_THREADS; - let listening = server.http(&socket_addr).unwrap(); - tx.send(listening).unwrap(); - }); + let mut server = Iron::new(router); + server.threads = IRON_THREADS; + let listening = server.http(&socket_addr).unwrap(); + tx.send(listening).unwrap(); + }); let listening = rx.recv_timeout(default_timeout() * 2).unwrap(); self.rocketchat_listening = Some(listening); self.rocketchat_mock_url = Some(format!("http://{}", socket_addr)); diff --git a/tests/matrix-rocketchat-test/message_forwarder.rs b/tests/matrix-rocketchat-test/message_forwarder.rs index b7999ce..dc78d2b 100644 --- a/tests/matrix-rocketchat-test/message_forwarder.rs +++ b/tests/matrix-rocketchat-test/message_forwarder.rs @@ -24,7 +24,11 @@ impl Handler for MessageForwarder { fn handle(&self, request: &mut Request) -> IronResult { let mut payload = String::new(); request.body.read_to_string(&mut payload).unwrap(); - self.tx.lock().unwrap().send(payload).unwrap(); + self.tx + .lock() + .unwrap() + .send(payload) + .unwrap(); Ok(Response::with((status::Ok, "{}".to_string()))) } diff --git a/tests/rocketchat.rs b/tests/rocketchat.rs index 5d6eb05..ae399ae 100644 --- a/tests/rocketchat.rs +++ b/tests/rocketchat.rs @@ -21,17 +21,20 @@ use router::Router; use reqwest::{Method, StatusCode}; use ruma_client_api::Endpoint; use ruma_client_api::r0::membership::invite_user::Endpoint as InviteUserEndpoint; +use ruma_client_api::r0::membership::join_room_by_id::Endpoint as JoinRoomByIdEndpoint; use ruma_client_api::r0::send::send_message_event::Endpoint as SendMessageEventEndpoint; use ruma_identifiers::{RoomId, UserId}; use serde_json::to_string; #[test] -fn successfully_forwards_a_text_message_to_matrix() { +fn successfully_forwards_a_text_message_from_a_user_that_is_not_registered_on_matrix() { let (message_forwarder, receiver) = MessageForwarder::new(); let (invite_forwarder, invite_receiver) = MessageForwarder::new(); + let (join_forwarder, join_receiver) = MessageForwarder::new(); let mut matrix_router = Router::new(); matrix_router.put(SendMessageEventEndpoint::router_path(), message_forwarder, "send_message_event"); matrix_router.post(InviteUserEndpoint::router_path(), invite_forwarder, "invite_user"); + matrix_router.post(JoinRoomByIdEndpoint::router_path(), join_forwarder, "join_room_id"); let mut channels = HashMap::new(); channels.insert("spec_channel", vec!["spec_user"]); @@ -61,6 +64,11 @@ fn successfully_forwards_a_text_message_to_matrix() { let invite_message = invite_receiver.recv_timeout(default_timeout()).unwrap(); assert!(invite_message.contains("@rocketchat_new_user_id_1:localhost")); + // discard admin room join + join_receiver.recv_timeout(default_timeout()).unwrap(); + // receive the join message + assert!(join_receiver.recv_timeout(default_timeout()).is_ok()); + // discard welcome message receiver.recv_timeout(default_timeout()).unwrap(); // discard connect message @@ -88,10 +96,10 @@ fn successfully_forwards_a_text_message_to_matrix() { let spec_user_id = UserId::try_from("@spec_user:localhost").unwrap(); let users_iter = users.iter(); let user_ids = users_iter.filter_map(|u| if u.matrix_user_id != bot_user_id && u.matrix_user_id != spec_user_id { - Some(u.matrix_user_id.clone()) - } else { - None - }) + Some(u.matrix_user_id.clone()) + } else { + None + }) .collect::>(); let new_user_id = user_ids.iter().next().unwrap(); @@ -116,6 +124,105 @@ fn successfully_forwards_a_text_message_to_matrix() { assert!(message_received_by_matrix.contains("spec_message 2")); } +#[test] +fn successfully_forwards_a_text_message_from_a_user_that_is_registered_on_matrix() { + let (message_forwarder, receiver) = MessageForwarder::new(); + let (invite_forwarder, invite_receiver) = MessageForwarder::new(); + let (join_forwarder, join_receiver) = MessageForwarder::new(); + let mut matrix_router = Router::new(); + matrix_router.put(SendMessageEventEndpoint::router_path(), message_forwarder, "send_message_event"); + matrix_router.post(InviteUserEndpoint::router_path(), invite_forwarder, "invite_user"); + matrix_router.post(JoinRoomByIdEndpoint::router_path(), join_forwarder, "join_room_id"); + + let mut channels = HashMap::new(); + channels.insert("spec_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_bridged_room(("spec_channel", "spec_user")) + .run(); + + let message = Message { + message_id: "spec_id".to_string(), + token: Some(RS_TOKEN.to_string()), + channel_id: "spec_channel_id".to_string(), + channel_name: "spec_channel".to_string(), + user_id: "spec_user_id".to_string(), + user_name: "spec_user".to_string(), + text: "spec_message".to_string(), + }; + let payload = to_string(&message).unwrap(); + + 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_spec_user_id_1:localhost")); + + // discard admin room join + join_receiver.recv_timeout(default_timeout()).unwrap(); + // receive the join message + assert!(join_receiver.recv_timeout(default_timeout()).is_ok()); + + // 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(); + // discard room bridged message + receiver.recv_timeout(default_timeout()).unwrap(); + + let message_received_by_matrix = receiver.recv_timeout(default_timeout()).unwrap(); + assert!(message_received_by_matrix.contains("spec_message")); + + let connection = test.connection_pool.get().unwrap(); + let admin_room = Room::find(&connection, &RoomId::try_from("!admin:localhost").unwrap()).unwrap(); + let rocketchat_server_id = admin_room.rocketchat_server_id.unwrap(); + let bridged_room = Room::find_by_rocketchat_room_id(&connection, rocketchat_server_id, "spec_channel_id".to_string()) + .unwrap() + .unwrap(); + + // the bot, the user who bridged the channel and the virtual user are in the channel + let users = bridged_room.users(&connection).unwrap(); + assert_eq!(users.len(), 3); + + let bot_user_id = UserId::try_from("@rocketchat:localhost").unwrap(); + let spec_user_id = UserId::try_from("@spec_user:localhost").unwrap(); + let users_iter = users.iter(); + let user_ids = users_iter.filter_map(|u| if u.matrix_user_id != bot_user_id && u.matrix_user_id != spec_user_id { + Some(u.matrix_user_id.clone()) + } else { + None + }) + .collect::>(); + let new_user_id = user_ids.iter().next().unwrap(); + + // the virtual user was create with the Rocket.Chat user ID because the exiting matrix user + // cannot be used since the application service can only impersonate virtual users. + let user_on_rocketchat = UserOnRocketchatServer::find(&connection, new_user_id, rocketchat_server_id).unwrap(); + assert_eq!(user_on_rocketchat.rocketchat_user_id.unwrap(), "spec_user_id".to_string()); + + let second_message = Message { + message_id: "spec_id_2".to_string(), + token: Some(RS_TOKEN.to_string()), + channel_id: "spec_channel_id".to_string(), + channel_name: "spec_channel".to_string(), + user_id: "spec_user_id".to_string(), + user_name: "spec_spec_user".to_string(), + text: "spec_message 2".to_string(), + }; + let second_payload = to_string(&second_message).unwrap(); + + helpers::simulate_message_from_rocketchat(&test.config.as_url, &second_payload); + + let message_received_by_matrix = receiver.recv_timeout(default_timeout()).unwrap(); + assert!(message_received_by_matrix.contains("spec_message 2")); +} + #[test] fn rocketchat_sends_mal_formatted_json() { let test = Test::new().run();