From 8903a46fc98782924dede9791b7f3cb4f479aad7 Mon Sep 17 00:00:00 2001 From: Andreas Studer Date: Sat, 23 Jun 2018 14:32:19 +0200 Subject: [PATCH] Store Rocket.Chat token in memory --- assets/translations.yaml | 2 + src/bin/matrix-rocketchat.rs | 14 +++- src/matrix-rocketchat/api/matrix/mod.rs | 18 +++-- src/matrix-rocketchat/api/matrix/r0.rs | 22 ++++- src/matrix-rocketchat/api/rest_api.rs | 4 +- src/matrix-rocketchat/api/rocketchat/mod.rs | 8 +- src/matrix-rocketchat/api/rocketchat/v1.rs | 10 ++- .../handlers/error_notifier.rs | 2 +- .../handlers/iron/rocketchat.rs | 4 +- .../handlers/iron/rocketchat_login.rs | 2 +- .../handlers/iron/transactions.rs | 4 +- .../handlers/matrix/command_handler.rs | 16 ++-- .../handlers/matrix/dispatcher.rs | 4 +- .../handlers/matrix/forwarder.rs | 46 ++++++++--- .../handlers/matrix/membership_handler.rs | 4 +- .../handlers/matrix/message_handler.rs | 4 +- .../handlers/rocketchat/forwarder.rs | 8 +- src/matrix-rocketchat/i18n.rs | 1 - .../models/rocketchat_room.rs | 10 +-- .../models/rocketchat_server.rs | 4 +- src/matrix-rocketchat/models/room.rs | 12 +-- src/matrix-rocketchat/models/schema.rs | 1 - .../models/user_on_rocketchat_server.rs | 80 +++++++++++-------- src/matrix-rocketchat/models/virtual_user.rs | 4 +- src/matrix-rocketchat/server.rs | 36 ++++++++- tests/admin_commands_list.rs | 13 +-- tests/admin_commands_login.rs | 66 +++++++++------ tests/admin_commands_unkown.rs | 15 +++- tests/admin_room.rs | 2 +- ...ard_rocketchat_direct_message_to_matrix.rs | 22 +---- tests/matrix-rocketchat-test/handlers.rs | 59 ++++++-------- tests/matrix-rocketchat-test/helpers.rs | 3 +- tests/matrix-rocketchat-test/lib.rs | 56 +++++++++---- tests/startup.rs | 8 +- 34 files changed, 340 insertions(+), 224 deletions(-) diff --git a/assets/translations.yaml b/assets/translations.yaml index 0a95702..796b364 100644 --- a/assets/translations.yaml +++ b/assets/translations.yaml @@ -67,6 +67,7 @@ en: room_successfully_unbridged: "${rocketchat_room_name} is now unbridged." channels: "Channels" groups: "Private Groups" + re_login: "Please login again" defaults: admin_room_display_name: "Admin Room (Rocket.Chat)" direct_message_room_display_name_suffix: "(DM Rocket.Chat)" @@ -78,6 +79,7 @@ en: authentication_failed: "Authentication failed!" connect_without_rocketchat_server_id: "You have to provide an id to connect to a Rocket.Chat server. It can contain any alphanumeric character and `_`. For example `connect https://rocketchat.example.com my_token rocketchat_example`" connect_with_invalid_rocketchat_server_id: "The provided Rocket.Chat server ID `${rocketchat_server_id}` is not valid, it can only contain lowercase alphanumeric characters. The maximum length is ${max_rocketchat_server_id_length} characters." + rocketchat_login_needed: "Could not send message to Rocket.Chat, you first have to login (via admin room)." internal: "An internal error occurred" no_rocketchat_server: "No Rocket.Chat server found when querying ${rocketchat_url} (version information is missing from the response)" other_user_joined: "Another user join the admin room, leaving, please create a new admin room." diff --git a/src/bin/matrix-rocketchat.rs b/src/bin/matrix-rocketchat.rs index 5740bd2..4e6fe84 100644 --- a/src/bin/matrix-rocketchat.rs +++ b/src/bin/matrix-rocketchat.rs @@ -20,6 +20,7 @@ use std::process; use clap::{App, Arg}; use iron::Listening; use matrix_rocketchat::errors::*; +use matrix_rocketchat::server::StartupNotification; use matrix_rocketchat::{Config, Server}; use slog::{Drain, FnValue, Level, LevelFilter, Record}; @@ -39,13 +40,24 @@ fn run() -> Result { .author("Andreas Studer ") .about("An application service to bridge Matrix and Rocket.Chat.") .arg(Arg::with_name("config").short("c").long("config").help("Path to config file").takes_value(true)) + .arg( + Arg::with_name("skip-login-notification") + .short("s") + .long("skip-login-notification") + .help("Do not notify users that they have to re-login") + .takes_value(false), + ) .get_matches(); + let mut startup_notification = StartupNotification::On; let config_path = matches.value_of("config").unwrap_or("config.yaml").to_string(); + if matches.is_present("skip-login-notification") { + startup_notification = StartupNotification::Off; + } let config = Config::read_from_file(&config_path).chain_err(|| ErrorKind::ReadFileError(config_path))?; let log = build_logger(&config); let threads = num_cpus::get() * 8; - Server::new(&config, log).run(threads) + Server::new(&config, log).run(threads, startup_notification) } fn build_logger(config: &Config) -> slog::Logger { diff --git a/src/matrix-rocketchat/api/matrix/mod.rs b/src/matrix-rocketchat/api/matrix/mod.rs index 25b7899..ca7fbbb 100644 --- a/src/matrix-rocketchat/api/matrix/mod.rs +++ b/src/matrix-rocketchat/api/matrix/mod.rs @@ -7,7 +7,7 @@ use ruma_client_api::unversioned::get_supported_versions::{ use ruma_client_api::Endpoint; use ruma_events::room::member::MemberEvent; use ruma_events::room::message::MessageType; -use ruma_identifiers::{RoomAliasId, RoomId, UserId}; +use ruma_identifiers::{EventId, RoomAliasId, RoomId, UserId}; use serde_json; use slog::Logger; @@ -54,6 +54,8 @@ pub trait MatrixApi: Send + Sync + MatrixApiClone { fn leave_room(&self, room_id: RoomId, user_id: UserId) -> Result<()>; /// Set the canonical alias for a room. fn put_canonical_room_alias(&self, room_id: RoomId, matrix_room_alias_id: Option) -> Result<()>; + /// Delete/redact an event. + fn redact_event(&self, room_id: RoomId, event_id: EventId, reason: Option) -> Result<()>; /// Register a user. fn register(&self, user_id_local_part: String) -> Result<()>; /// Send a text message to a room. @@ -77,28 +79,28 @@ pub trait MatrixApi: Send + Sync + MatrixApiClone { /// `MatrixApi` trait to not be object safe. pub trait MatrixApiClone { /// Clone the object inside the trait and return it in box. - fn clone_box(&self) -> Box; + fn clone_box(&self) -> Box; } impl MatrixApiClone for T where T: 'static + MatrixApi + Clone, { - fn clone_box(&self) -> Box { + fn clone_box(&self) -> Box { Box::new(self.clone()) } } -impl Clone for Box { - fn clone(&self) -> Box { +impl Clone for Box { + fn clone(&self) -> Box { self.clone_box() } } -impl MatrixApi { +impl dyn MatrixApi { /// Creates a new Matrix API depending on the version of the homeserver. /// It returns a `MatrixApi` trait, because for each version a different API is created. - pub fn new(config: &Config, logger: Logger) -> Result> { + pub fn new(config: &Config, logger: Logger) -> Result> { let url = config.hs_url.clone() + &GetSupportedVersionsEndpoint::request_path(()); let params = HashMap::new(); @@ -126,7 +128,7 @@ impl MatrixApi { MatrixApi::get_max_supported_version_api(&supported_versions.versions, config, logger) } - fn get_max_supported_version_api(versions: &[String], config: &Config, logger: Logger) -> Result> { + fn get_max_supported_version_api(versions: &[String], config: &Config, logger: Logger) -> Result> { for version in versions.iter().rev() { if version.starts_with("r0") { let matrix_api = r0::MatrixApi::new(config, logger); diff --git a/src/matrix-rocketchat/api/matrix/r0.rs b/src/matrix-rocketchat/api/matrix/r0.rs index 6df5fb9..ea6198f 100644 --- a/src/matrix-rocketchat/api/matrix/r0.rs +++ b/src/matrix-rocketchat/api/matrix/r0.rs @@ -16,6 +16,7 @@ use ruma_client_api::r0::membership::join_room_by_id::{self, Endpoint as JoinRoo use ruma_client_api::r0::membership::leave_room::{self, Endpoint as LeaveRoomEndpoint}; use ruma_client_api::r0::profile::get_display_name::{self, Endpoint as GetDisplayNameEndpoint}; use ruma_client_api::r0::profile::set_display_name::{self, Endpoint as SetDisplayNameEndpoint}; +use ruma_client_api::r0::redact::redact_event::{self, Endpoint as RedactEndpoint}; use ruma_client_api::r0::room::create_room::{self, Endpoint as CreateRoomEndpoint, RoomPreset}; use ruma_client_api::r0::send::send_message_event::{self, Endpoint as SendMessageEventEndpoint}; use ruma_client_api::r0::send::send_state_event_for_empty_key::{self, Endpoint as SendStateEventForEmptyKeyEndpoint}; @@ -285,7 +286,7 @@ impl super::MatrixApi for MatrixApi { let room_create: Value = serde_json::from_str(&body).chain_err(|| { ErrorKind::InvalidJSON(format!( - "Could not deserialize response from Matrix get_state_events_for_empty_key API endpoint: `{}`", + "Could not deserialize response from Matrix get_room_creator/get_state_events_for_empty_key API endpoint: `{}`", body )) })?; @@ -334,7 +335,7 @@ impl super::MatrixApi for MatrixApi { let room_topic_response: Value = serde_json::from_str(&body).chain_err(|| { ErrorKind::InvalidJSON(format!( - "Could not deserialize response from Matrix get_state_events_for_empty_key API endpoint: `{}`", + "Could not deserialize response from Matrix get_room_topic/get_state_events_for_empty_key API endpoint: `{}`", body )) })?; @@ -421,6 +422,23 @@ impl super::MatrixApi for MatrixApi { Ok(()) } + fn redact_event(&self, room_id: RoomId, event_id: EventId, reason: Option) -> Result<()> { + let txn_id = EventId::new(&self.base_url).chain_err(|| ErrorKind::EventIdGenerationFailed)?.to_string(); + let path_params = redact_event::PathParams { room_id, event_id, txn_id }; + let endpoint = self.base_url.clone() + &RedactEndpoint::request_path(path_params); + let params = self.params_hash(); + + let body_params = redact_event::BodyParams { reason }; + let payload = serde_json::to_string(&body_params).chain_err(|| body_params_error!("redact event"))?; + + let (body, status_code) = RestApi::call_matrix(&RedactEndpoint::method(), &endpoint, payload, ¶ms)?; + if !status_code.is_success() { + return Err(build_error(&endpoint, &body, &status_code)); + } + + Ok(()) + } + fn register(&self, user_id_local_part: String) -> Result<()> { let endpoint = self.base_url.clone() + &RegisterEndpoint::request_path(()); let params = self.params_hash(); diff --git a/src/matrix-rocketchat/api/rest_api.rs b/src/matrix-rocketchat/api/rest_api.rs index 8cfc810..35b9410 100644 --- a/src/matrix-rocketchat/api/rest_api.rs +++ b/src/matrix-rocketchat/api/rest_api.rs @@ -59,12 +59,12 @@ impl RestApi { } /// Call a Rocket.Chat API endpoint - pub fn call_rocketchat>(endpoint: &Endpoint) -> Result<(String, StatusCode)> { + pub fn call_rocketchat>(endpoint: &dyn Endpoint) -> Result<(String, StatusCode)> { RestApi::call(&endpoint.method(), &endpoint.url(), endpoint.payload()?, &endpoint.query_params(), endpoint.headers()?) } /// Get a file that was uploaded to Rocket.Chat - pub fn get_rocketchat_file>(endpoint: &Endpoint) -> Result { + pub fn get_rocketchat_file>(endpoint: &dyn Endpoint) -> Result { RestApi::call_raw( &endpoint.method(), &endpoint.url(), diff --git a/src/matrix-rocketchat/api/rocketchat/mod.rs b/src/matrix-rocketchat/api/rocketchat/mod.rs index 9845af1..076f42a 100644 --- a/src/matrix-rocketchat/api/rocketchat/mod.rs +++ b/src/matrix-rocketchat/api/rocketchat/mod.rs @@ -145,7 +145,7 @@ pub trait RocketchatApi { /// Get information like user_id, status, etc. about a user fn users_info(&self, username: &str) -> Result; /// Set credentials that are used for all API calls that need authentication - fn with_credentials(self: Box, user_id: String, auth_token: String) -> Box; + fn with_credentials(self: Box, user_id: String, auth_token: String) -> Box; } /// Response format when querying the Rocket.Chat info endpoint @@ -154,10 +154,10 @@ pub struct GetInfoResponse { version: String, } -impl RocketchatApi { +impl dyn RocketchatApi { /// Creates a new Rocket.Chat API depending on the version of the API. /// It returns a `RocketchatApi` trait, because for each version a different API is created. - pub fn new(base_url: String, logger: Logger) -> Result> { + pub fn new(base_url: String, logger: Logger) -> Result> { let url = base_url.clone() + "/api/info"; let params = HashMap::new(); @@ -190,7 +190,7 @@ impl RocketchatApi { RocketchatApi::get_max_supported_version_api(rocketchat_info.version, base_url, logger) } - fn get_max_supported_version_api(version: String, base_url: String, logger: Logger) -> Result> { + fn get_max_supported_version_api(version: String, base_url: String, logger: Logger) -> Result> { let version_string = version.clone(); let mut versions = version_string.split('.').into_iter(); let major: i32 = versions.next().unwrap_or("0").parse().unwrap_or(0); diff --git a/src/matrix-rocketchat/api/rocketchat/v1.rs b/src/matrix-rocketchat/api/rocketchat/v1.rs index 0606276..9c4e683 100644 --- a/src/matrix-rocketchat/api/rocketchat/v1.rs +++ b/src/matrix-rocketchat/api/rocketchat/v1.rs @@ -229,10 +229,12 @@ pub struct LoginEndpoint<'a> { } /// Payload of the login endpoint -#[derive(Serialize)] +#[derive(Serialize, Deserialize)] pub struct LoginPayload<'a> { - username: &'a str, - password: &'a str, + /// Rocket.Chat username + pub username: &'a str, + /// Rocket.Chat password + pub password: &'a str, } impl<'a> Endpoint for LoginEndpoint<'a> { @@ -868,7 +870,7 @@ impl super::RocketchatApi for RocketchatApi { Ok(users_info_response.user) } - fn with_credentials(mut self: Box, user_id: String, auth_token: String) -> Box { + fn with_credentials(mut self: Box, user_id: String, auth_token: String) -> Box { self.user_id = user_id; self.auth_token = auth_token; self diff --git a/src/matrix-rocketchat/handlers/error_notifier.rs b/src/matrix-rocketchat/handlers/error_notifier.rs index 88ae105..ed3512f 100644 --- a/src/matrix-rocketchat/handlers/error_notifier.rs +++ b/src/matrix-rocketchat/handlers/error_notifier.rs @@ -18,7 +18,7 @@ pub struct ErrorNotifier<'a> { /// Logger context pub logger: &'a Logger, /// Matrix REST API - pub matrix_api: &'a MatrixApi, + pub matrix_api: &'a dyn MatrixApi, } impl<'a> ErrorNotifier<'a> { diff --git a/src/matrix-rocketchat/handlers/iron/rocketchat.rs b/src/matrix-rocketchat/handlers/iron/rocketchat.rs index 8237b78..acb3be7 100644 --- a/src/matrix-rocketchat/handlers/iron/rocketchat.rs +++ b/src/matrix-rocketchat/handlers/iron/rocketchat.rs @@ -15,12 +15,12 @@ pub struct Rocketchat { /// Application service configuration pub config: Config, /// Matrix REST API - pub matrix_api: Box, + pub matrix_api: Box, } impl Rocketchat { /// Rocket.Chat endpoint with middleware - pub fn chain(config: &Config, matrix_api: Box) -> Chain { + pub fn chain(config: &Config, matrix_api: Box) -> Chain { let rocketchat = Rocketchat { config: config.clone(), matrix_api }; let mut chain = Chain::new(rocketchat); chain.link_before(RocketchatToken {}); diff --git a/src/matrix-rocketchat/handlers/iron/rocketchat_login.rs b/src/matrix-rocketchat/handlers/iron/rocketchat_login.rs index 0404c6f..ca7a56e 100644 --- a/src/matrix-rocketchat/handlers/iron/rocketchat_login.rs +++ b/src/matrix-rocketchat/handlers/iron/rocketchat_login.rs @@ -17,7 +17,7 @@ pub struct RocketchatLogin { /// Application service configuration pub config: Config, /// Matrix REST API - pub matrix_api: Box, + pub matrix_api: Box, } impl Handler for RocketchatLogin { diff --git a/src/matrix-rocketchat/handlers/iron/transactions.rs b/src/matrix-rocketchat/handlers/iron/transactions.rs index fc59fc9..dacbee7 100644 --- a/src/matrix-rocketchat/handlers/iron/transactions.rs +++ b/src/matrix-rocketchat/handlers/iron/transactions.rs @@ -17,12 +17,12 @@ use models::{ConnectionPool, Events}; /// to push new events. pub struct Transactions { config: Config, - matrix_api: Box, + matrix_api: Box, } impl Transactions { /// Transactions endpoint with middleware - pub fn chain(config: Config, matrix_api: Box) -> Chain { + pub fn chain(config: Config, matrix_api: Box) -> Chain { let transactions = Transactions { config: config.clone(), matrix_api }; let mut chain = Chain::new(transactions); chain.link_before(AccessToken { config }); diff --git a/src/matrix-rocketchat/handlers/matrix/command_handler.rs b/src/matrix-rocketchat/handlers/matrix/command_handler.rs index 2297ccb..b352ceb 100644 --- a/src/matrix-rocketchat/handlers/matrix/command_handler.rs +++ b/src/matrix-rocketchat/handlers/matrix/command_handler.rs @@ -20,7 +20,7 @@ pub struct CommandHandler<'a> { config: &'a Config, connection: &'a SqliteConnection, logger: &'a Logger, - matrix_api: &'a MatrixApi, + matrix_api: &'a dyn MatrixApi, admin_room: &'a Room<'a>, } @@ -30,7 +30,7 @@ impl<'a> CommandHandler<'a> { config: &'a Config, connection: &'a SqliteConnection, logger: &'a Logger, - matrix_api: &'a MatrixApi, + matrix_api: &'a dyn MatrixApi, admin_room: &'a Room<'a>, ) -> CommandHandler<'a> { CommandHandler { config, connection, logger, matrix_api, admin_room } @@ -107,8 +107,6 @@ impl<'a> CommandHandler<'a> { let new_user_on_rocketchat_server = NewUserOnRocketchatServer { matrix_user_id: event.sender.clone(), rocketchat_server_id: server.id, - rocketchat_user_id: None, - rocketchat_auth_token: None, }; UserOnRocketchatServer::upsert(self.connection, &new_user_on_rocketchat_server)?; @@ -210,8 +208,8 @@ impl<'a> CommandHandler<'a> { fn list_rocketchat_rooms(&self, event: &MessageEvent, server: &RocketchatServer) -> Result<()> { let user_on_rocketchat_server = UserOnRocketchatServer::find(self.connection, &event.sender, server.id.clone())?; let rocketchat_api = RocketchatApi::new(server.rocketchat_url.clone(), self.logger.clone())?.with_credentials( - user_on_rocketchat_server.rocketchat_user_id.unwrap_or_default(), - user_on_rocketchat_server.rocketchat_auth_token.unwrap_or_default(), + user_on_rocketchat_server.rocketchat_user_id.clone().unwrap_or_default(), + user_on_rocketchat_server.rocketchat_auth_token().unwrap_or_default(), ); let bot_user_id = self.config.matrix_bot_user_id()?; let list = self.build_rocketchat_rooms_list(rocketchat_api.as_ref(), &server.id, &event.sender)?; @@ -227,7 +225,7 @@ impl<'a> CommandHandler<'a> { let user_on_rocketchat_server = UserOnRocketchatServer::find(self.connection, &event.sender, server.id.clone())?; let rocketchat_api = RocketchatApi::new(server.rocketchat_url.clone(), self.logger.clone())?.with_credentials( 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().unwrap_or_default(), ); let channels = rocketchat_api.channels_list()?; @@ -300,7 +298,7 @@ impl<'a> CommandHandler<'a> { let user_on_rocketchat_server = UserOnRocketchatServer::find(self.connection, &event.sender, server.id.clone())?; let rocketchat_api = RocketchatApi::new(server.rocketchat_url.clone(), self.logger.clone())?.with_credentials( 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().unwrap_or_default(), ); let rocketchat_room = @@ -371,7 +369,7 @@ impl<'a> CommandHandler<'a> { fn build_rocketchat_rooms_list( &self, - rocketchat_api: &RocketchatApi, + rocketchat_api: &dyn RocketchatApi, rocketchat_server_id: &str, user_id: &UserId, ) -> Result { diff --git a/src/matrix-rocketchat/handlers/matrix/dispatcher.rs b/src/matrix-rocketchat/handlers/matrix/dispatcher.rs index e43fff1..588133d 100644 --- a/src/matrix-rocketchat/handlers/matrix/dispatcher.rs +++ b/src/matrix-rocketchat/handlers/matrix/dispatcher.rs @@ -16,7 +16,7 @@ pub struct Dispatcher<'a> { config: &'a Config, connection: &'a SqliteConnection, logger: &'a Logger, - matrix_api: Box, + matrix_api: Box, } impl<'a> Dispatcher<'a> { @@ -25,7 +25,7 @@ impl<'a> Dispatcher<'a> { config: &'a Config, connection: &'a SqliteConnection, logger: &'a Logger, - matrix_api: Box, + matrix_api: Box, ) -> Dispatcher<'a> { Dispatcher { config, connection, logger, matrix_api } } diff --git a/src/matrix-rocketchat/handlers/matrix/forwarder.rs b/src/matrix-rocketchat/handlers/matrix/forwarder.rs index cf4c6b2..caeab7b 100644 --- a/src/matrix-rocketchat/handlers/matrix/forwarder.rs +++ b/src/matrix-rocketchat/handlers/matrix/forwarder.rs @@ -13,12 +13,12 @@ use models::{RocketchatServer, UserOnRocketchatServer}; pub struct Forwarder<'a> { connection: &'a SqliteConnection, logger: &'a Logger, - matrix_api: &'a MatrixApi, + matrix_api: &'a dyn MatrixApi, } impl<'a> Forwarder<'a> { /// Create a new `Forwarder`. - pub fn new(connection: &'a SqliteConnection, logger: &'a Logger, matrix_api: &'a MatrixApi) -> Forwarder<'a> { + pub fn new(connection: &'a SqliteConnection, logger: &'a Logger, matrix_api: &'a dyn MatrixApi) -> Forwarder<'a> { Forwarder { connection, logger, matrix_api } } @@ -35,32 +35,52 @@ impl<'a> Forwarder<'a> { let rocketchat_api = RocketchatApi::new(server.rocketchat_url, self.logger.clone())?.with_credentials( 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().unwrap_or_default(), ); - match event.content { - MessageEventContent::Text(ref content) => { - rocketchat_api.chat_post_message(&content.body, channel_id)?; - } + let result = match event.content { + MessageEventContent::Text(ref content) => rocketchat_api.chat_post_message(&content.body, channel_id), MessageEventContent::Image(ref content) => { let mt = content.clone().info.chain_err(|| ErrorKind::MissingMimeType)?.mimetype; - self.forward_file_to_rocketchat(rocketchat_api.as_ref(), &content.url, Some(mt), &content.body, channel_id)?; + self.forward_file_to_rocketchat(rocketchat_api.as_ref(), &content.url, Some(mt), &content.body, channel_id) } MessageEventContent::File(ref content) => { let mt = content.clone().info.chain_err(|| ErrorKind::MissingMimeType)?.mimetype; - self.forward_file_to_rocketchat(rocketchat_api.as_ref(), &content.url, Some(mt), &content.body, channel_id)?; + self.forward_file_to_rocketchat(rocketchat_api.as_ref(), &content.url, Some(mt), &content.body, channel_id) } MessageEventContent::Audio(ref content) => { let mt = content.clone().info.chain_err(|| ErrorKind::MissingMimeType)?.mimetype; - self.forward_file_to_rocketchat(rocketchat_api.as_ref(), &content.url, mt, &content.body, channel_id)?; + self.forward_file_to_rocketchat(rocketchat_api.as_ref(), &content.url, mt, &content.body, channel_id) } MessageEventContent::Video(ref content) => { let mt = content.clone().info.chain_err(|| ErrorKind::MissingMimeType)?.mimetype; - self.forward_file_to_rocketchat(rocketchat_api.as_ref(), &content.url, mt, &content.body, channel_id)?; + self.forward_file_to_rocketchat(rocketchat_api.as_ref(), &content.url, mt, &content.body, channel_id) } MessageEventContent::Emote(_) | MessageEventContent::Location(_) | MessageEventContent::Notice(_) => { - info!(self.logger, "Not forwarding message, forwarding emote, location or notice messages is not implemented.") + info!(self.logger, "Not forwarding message, forwarding emote, location or notice messages is not implemented."); + return Ok(()); + } + }; + + if let Err(result) = result { + match result.error_chain.kind() { + ErrorKind::RocketchatAuthenticationFailed(_) => { + let reason = t!(["errors", "rocketchat_login_needed"]).l(DEFAULT_LANGUAGE); + let room_id = match &event.room_id{ + Some(room_id) => room_id.clone(), + None => { + warn!(self.logger, "Room ID missing"); + return Ok(()) + } + }; + self.matrix_api.redact_event(room_id, event.event_id.clone(), Some(reason))?; + //TODO: Send message to admin room + } + _ => { + return Err(result); + } } + return Ok(()); } user_on_rocketchat_server.set_last_message_sent(self.connection) @@ -68,7 +88,7 @@ impl<'a> Forwarder<'a> { fn forward_file_to_rocketchat( &self, - rocketchat_api: &RocketchatApi, + rocketchat_api: &dyn RocketchatApi, url: &str, mimetype: Option, body: &str, diff --git a/src/matrix-rocketchat/handlers/matrix/membership_handler.rs b/src/matrix-rocketchat/handlers/matrix/membership_handler.rs index 4e6927e..b70befa 100644 --- a/src/matrix-rocketchat/handlers/matrix/membership_handler.rs +++ b/src/matrix-rocketchat/handlers/matrix/membership_handler.rs @@ -20,7 +20,7 @@ pub struct MembershipHandler<'a> { config: &'a Config, conn: &'a SqliteConnection, logger: &'a Logger, - matrix_api: &'a MatrixApi, + matrix_api: &'a dyn MatrixApi, room: &'a Room<'a>, } @@ -30,7 +30,7 @@ impl<'a> MembershipHandler<'a> { config: &'a Config, conn: &'a SqliteConnection, logger: &'a Logger, - matrix_api: &'a MatrixApi, + matrix_api: &'a dyn MatrixApi, room: &'a Room<'a>, ) -> MembershipHandler<'a> { MembershipHandler { config, conn, logger, matrix_api, room } diff --git a/src/matrix-rocketchat/handlers/matrix/message_handler.rs b/src/matrix-rocketchat/handlers/matrix/message_handler.rs index 0691d62..4ce564f 100644 --- a/src/matrix-rocketchat/handlers/matrix/message_handler.rs +++ b/src/matrix-rocketchat/handlers/matrix/message_handler.rs @@ -13,7 +13,7 @@ pub struct MessageHandler<'a> { config: &'a Config, connection: &'a SqliteConnection, logger: &'a Logger, - matrix_api: Box, + matrix_api: Box, } impl<'a> MessageHandler<'a> { @@ -22,7 +22,7 @@ impl<'a> MessageHandler<'a> { config: &'a Config, connection: &'a SqliteConnection, logger: &'a Logger, - matrix_api: Box, + matrix_api: Box, ) -> MessageHandler<'a> { MessageHandler { config, connection, logger, matrix_api } } diff --git a/src/matrix-rocketchat/handlers/rocketchat/forwarder.rs b/src/matrix-rocketchat/handlers/rocketchat/forwarder.rs index eba2664..e4c82ea 100644 --- a/src/matrix-rocketchat/handlers/rocketchat/forwarder.rs +++ b/src/matrix-rocketchat/handlers/rocketchat/forwarder.rs @@ -27,7 +27,7 @@ pub struct Forwarder<'a> { /// Logger context logger: &'a Logger, /// Matrix REST API - matrix_api: &'a MatrixApi, + matrix_api: &'a dyn MatrixApi, /// Manages virtual users that the application service uses virtual_user: &'a VirtualUser<'a>, } @@ -38,7 +38,7 @@ impl<'a> Forwarder<'a> { config: &'a Config, connection: &'a SqliteConnection, logger: &'a Logger, - matrix_api: &'a MatrixApi, + matrix_api: &'a dyn MatrixApi, virtual_user: &'a VirtualUser, ) -> Forwarder<'a> { Forwarder { config, connection, logger, matrix_api, virtual_user } @@ -190,7 +190,7 @@ impl<'a> Forwarder<'a> { let rocketchat_api = RocketchatApi::new(server.rocketchat_url.clone(), self.logger.clone())?.with_credentials( receiver.rocketchat_user_id.clone().unwrap_or_default(), - receiver.rocketchat_auth_token.clone().unwrap_or_default(), + receiver.rocketchat_auth_token().unwrap_or_default(), ); if rocketchat_api.dm_list()?.iter().any(|dm| dm.id == message.channel_id) { @@ -269,7 +269,7 @@ impl<'a> Forwarder<'a> { let rocketchat_api = RocketchatApi::new(server.rocketchat_url.clone(), self.logger.clone())?.with_credentials( user.rocketchat_user_id.clone().unwrap_or_default(), - user.rocketchat_auth_token.clone().unwrap_or_default(), + user.rocketchat_auth_token().unwrap_or_default(), ); let files = rocketchat_api.attachments(&message.message_id)?; diff --git a/src/matrix-rocketchat/i18n.rs b/src/matrix-rocketchat/i18n.rs index eabc8aa..4f9d5a5 100644 --- a/src/matrix-rocketchat/i18n.rs +++ b/src/matrix-rocketchat/i18n.rs @@ -92,7 +92,6 @@ macro_rules! i18n_languages { } } -/// A list of languages that are supported by the application bridge i18n_languages!(en); /// Language that is used if no language is specified diff --git a/src/matrix-rocketchat/models/rocketchat_room.rs b/src/matrix-rocketchat/models/rocketchat_room.rs index 25740c9..b064762 100644 --- a/src/matrix-rocketchat/models/rocketchat_room.rs +++ b/src/matrix-rocketchat/models/rocketchat_room.rs @@ -19,7 +19,7 @@ pub struct RocketchatRoom<'a> { /// Logger context logger: &'a Logger, /// API to call the Matrix homeserver - matrix_api: &'a MatrixApi, + matrix_api: &'a dyn MatrixApi, } impl<'a> RocketchatRoom<'a> { @@ -27,7 +27,7 @@ impl<'a> RocketchatRoom<'a> { pub fn new( config: &'a Config, logger: &'a Logger, - matrix_api: &'a MatrixApi, + matrix_api: &'a dyn MatrixApi, id: String, server_id: &'a str, ) -> RocketchatRoom<'a> { @@ -38,10 +38,10 @@ impl<'a> RocketchatRoom<'a> { pub fn from_name( config: &'a Config, logger: &'a Logger, - matrix_api: &'a MatrixApi, + matrix_api: &'a dyn MatrixApi, name: &'a str, server_id: &'a str, - rocketchat_api: &'a RocketchatApi, + rocketchat_api: &'a dyn RocketchatApi, ) -> Result> { let mut rocketchat_rooms = rocketchat_api.channels_list()?; let groups = rocketchat_api.groups_list()?; @@ -67,7 +67,7 @@ impl<'a> RocketchatRoom<'a> { /// homeserver and manages the rooms virtual users. pub fn bridge( &self, - rocketchat_api: &RocketchatApi, + rocketchat_api: &dyn RocketchatApi, name: &Option, userlist: &[String], creator_id: &UserId, diff --git a/src/matrix-rocketchat/models/rocketchat_server.rs b/src/matrix-rocketchat/models/rocketchat_server.rs index 3aa4853..35a7f28 100644 --- a/src/matrix-rocketchat/models/rocketchat_server.rs +++ b/src/matrix-rocketchat/models/rocketchat_server.rs @@ -119,7 +119,7 @@ impl RocketchatServer { config: &Config, connection: &SqliteConnection, logger: &Logger, - matrix_api: &MatrixApi, + matrix_api: &dyn MatrixApi, credentials: &Credentials, admin_room_id: Option, ) -> Result<()> { @@ -146,7 +146,7 @@ impl RocketchatServer { pub fn logged_in_users_on_rocketchat_server(&self, connection: &SqliteConnection) -> Result> { let users_on_rocketchat_server: Vec = users_on_rocketchat_servers::table .filter(users_on_rocketchat_servers::rocketchat_server_id.eq(&self.id)) - .filter(users_on_rocketchat_servers::rocketchat_auth_token.is_not_null()) + .filter(users_on_rocketchat_servers::rocketchat_user_id.is_not_null()) .load(connection) .chain_err(|| ErrorKind::DBSelectError)?; Ok(users_on_rocketchat_server) diff --git a/src/matrix-rocketchat/models/room.rs b/src/matrix-rocketchat/models/room.rs index 31f6a9d..2a2425d 100644 --- a/src/matrix-rocketchat/models/room.rs +++ b/src/matrix-rocketchat/models/room.rs @@ -33,18 +33,18 @@ pub struct Room<'a> { /// Logger context logger: &'a Logger, /// API to call the Matrix homeserver - matrix_api: &'a MatrixApi, + matrix_api: &'a dyn MatrixApi, } impl<'a> Room<'a> { /// Create a new room model, to interact with Matrix rooms. - pub fn new(config: &'a Config, logger: &'a Logger, matrix_api: &'a MatrixApi, id: RoomId) -> Room<'a> { + pub fn new(config: &'a Config, logger: &'a Logger, matrix_api: &'a dyn MatrixApi, id: RoomId) -> Room<'a> { Room { config, logger, matrix_api, id } } /// Create a room on the Matrix homeserver with the power levels for a bridged room. pub fn create( - matrix_api: &MatrixApi, + matrix_api: &dyn MatrixApi, alias: Option, display_name: &Option, creator_id: &UserId, @@ -61,7 +61,7 @@ impl<'a> Room<'a> { pub fn get_dm( config: &'a Config, logger: &'a Logger, - matrix_api: &'a MatrixApi, + matrix_api: &'a dyn MatrixApi, channel_id: String, sender_id: &UserId, receiver_id: &UserId, @@ -250,7 +250,7 @@ impl<'a> Room<'a> { let user_on_rocketchat_server = UserOnRocketchatServer::find(conn, user_matrix_id, server_id)?; let rocketchat_user_id = user_on_rocketchat_server.rocketchat_user_id.clone().unwrap_or_default(); - let rocketchat_auth_token = user_on_rocketchat_server.rocketchat_auth_token.clone().unwrap_or_default(); + let rocketchat_auth_token = user_on_rocketchat_server.rocketchat_auth_token().unwrap_or_default(); let rocketchat_api = RocketchatApi::new(server.rocketchat_url.clone(), self.logger.clone())? .with_credentials(rocketchat_user_id, rocketchat_auth_token); @@ -286,7 +286,7 @@ impl<'a> Room<'a> { /// Join all users that are in a Rocket.Chat room to the Matrix room. pub fn join_all_rocketchat_users( &self, - rocketchat_api: &RocketchatApi, + rocketchat_api: &dyn RocketchatApi, usernames: &[String], rocketchat_server_id: &str, ) -> Result<()> { diff --git a/src/matrix-rocketchat/models/schema.rs b/src/matrix-rocketchat/models/schema.rs index cdd29ca..d7002ac 100644 --- a/src/matrix-rocketchat/models/schema.rs +++ b/src/matrix-rocketchat/models/schema.rs @@ -16,7 +16,6 @@ table! { matrix_user_id -> Text, rocketchat_server_id -> Text, rocketchat_user_id -> Nullable, - rocketchat_auth_token -> Nullable, created_at -> Timestamp, updated_at -> Timestamp, } diff --git a/src/matrix-rocketchat/models/user_on_rocketchat_server.rs b/src/matrix-rocketchat/models/user_on_rocketchat_server.rs index 0dc886d..b0479d2 100644 --- a/src/matrix-rocketchat/models/user_on_rocketchat_server.rs +++ b/src/matrix-rocketchat/models/user_on_rocketchat_server.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; +use std::sync::RwLock; use std::time::{SystemTime, UNIX_EPOCH}; use diesel; @@ -8,6 +10,11 @@ use ruma_identifiers::UserId; use errors::*; use models::schema::users_on_rocketchat_servers; +lazy_static! { + /// User credentials + static ref CREDENTIALS: RwLock> = { RwLock::new(HashMap::new()) }; +} + /// A user on a Rocket.Chat server. #[derive(Associations, Debug, Identifiable, Queryable)] #[primary_key(matrix_user_id, rocketchat_server_id)] @@ -21,8 +28,6 @@ pub struct UserOnRocketchatServer { pub rocketchat_server_id: String, /// The users unique id on the Rocket.Chat server. pub rocketchat_user_id: Option, - /// The token to identify reuqests from the Rocket.Chat server - pub rocketchat_auth_token: Option, /// created timestamp pub created_at: String, /// updated timestamp @@ -37,10 +42,6 @@ pub struct NewUserOnRocketchatServer { pub matrix_user_id: UserId, /// The unique id for the Rocket.Chat server pub rocketchat_server_id: String, - /// The users unique id on the Rocket.Chat server. - pub rocketchat_user_id: Option, - /// The token to identify reuqests from the Rocket.Chat server - pub rocketchat_auth_token: Option, } impl UserOnRocketchatServer { @@ -54,20 +55,11 @@ impl UserOnRocketchatServer { .load(connection) .chain_err(|| ErrorKind::DBSelectError)?; - match users_on_rocketchat_server.into_iter().next() { - Some(mut existing_user_on_rocketchat_server) => { - existing_user_on_rocketchat_server.set_credentials( - connection, - user_on_rocketchat_server.rocketchat_user_id.clone(), - user_on_rocketchat_server.rocketchat_auth_token.clone(), - )?; - } - None => { - diesel::insert_into(users_on_rocketchat_servers::table) - .values(user_on_rocketchat_server) - .execute(connection) - .chain_err(|| ErrorKind::DBInsertError)?; - } + if users_on_rocketchat_server.into_iter().next().is_none() { + diesel::insert_into(users_on_rocketchat_servers::table) + .values(user_on_rocketchat_server) + .execute(connection) + .chain_err(|| ErrorKind::DBInsertError)?; } UserOnRocketchatServer::find( @@ -85,9 +77,10 @@ impl UserOnRocketchatServer { rocketchat_server_id: String, ) -> Result { let user_on_rocketchat_server = users_on_rocketchat_servers::table - .find((matrix_user_id, rocketchat_server_id)) + .find((matrix_user_id, rocketchat_server_id.clone())) .first(connection) .chain_err(|| ErrorKind::DBSelectError)?; + Ok(user_on_rocketchat_server) } @@ -102,10 +95,11 @@ impl UserOnRocketchatServer { .filter( users_on_rocketchat_servers::matrix_user_id .eq(matrix_user_id) - .and(users_on_rocketchat_servers::rocketchat_server_id.eq(rocketchat_server_id)), + .and(users_on_rocketchat_servers::rocketchat_server_id.eq(rocketchat_server_id.clone())), ) .load(connection) .chain_err(|| ErrorKind::DBSelectError)?; + Ok(user_on_rocketchat_server.into_iter().next()) } @@ -119,10 +113,11 @@ impl UserOnRocketchatServer { .filter( users_on_rocketchat_servers::matrix_user_id .eq_any(matrix_user_ids) - .and(users_on_rocketchat_servers::rocketchat_server_id.eq(rocketchat_server_id)), + .and(users_on_rocketchat_servers::rocketchat_server_id.eq(rocketchat_server_id.clone())), ) .load(connection) .chain_err(|| ErrorKind::DBSelectError)?; + Ok(users_on_rocketchat_server) } @@ -133,36 +128,53 @@ impl UserOnRocketchatServer { rocketchat_server_id: String, rocketchat_user_id: String, ) -> Result> { - let users_on_rocketchat_servers = users_on_rocketchat_servers::table + let users_on_rocketchat_servers: Vec = 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)), + .eq(rocketchat_server_id.clone()) + .and(users_on_rocketchat_servers::rocketchat_user_id.eq(rocketchat_user_id.clone())), ) .load(connection) .chain_err(|| ErrorKind::DBSelectError)?; + Ok(users_on_rocketchat_servers.into_iter().next()) } - /// Update the users credentials. + /// Set the users credentials. pub fn set_credentials( &mut self, connection: &SqliteConnection, rocketchat_user_id: Option, rocketchat_auth_token: Option, ) -> Result<()> { + let mut credentials = CREDENTIALS.write().unwrap_or_else(|poisoned_lock| poisoned_lock.into_inner()); + + match rocketchat_auth_token { + Some(rocketchat_auth_token) => { + credentials + .insert((self.matrix_user_id.clone(), self.rocketchat_server_id.clone()), rocketchat_auth_token.clone()); + } + None => { + credentials.remove(&(self.matrix_user_id.clone(), self.rocketchat_server_id.clone())); + } + } + self.rocketchat_user_id = rocketchat_user_id.clone(); - self.rocketchat_auth_token = rocketchat_auth_token.clone(); + diesel::update(users_on_rocketchat_servers::table.find((&self.matrix_user_id, self.rocketchat_server_id.clone()))) - .set(( - users_on_rocketchat_servers::rocketchat_user_id.eq(rocketchat_user_id), - users_on_rocketchat_servers::rocketchat_auth_token.eq(rocketchat_auth_token), - )) + .set(users_on_rocketchat_servers::rocketchat_user_id.eq(rocketchat_user_id)) .execute(connection) .chain_err(|| ErrorKind::DBUpdateError)?; + Ok(()) } + /// Returns the users Rocket.Chat authentication token. + pub fn rocketchat_auth_token(&self) -> Option { + let credentials = CREDENTIALS.read().unwrap_or_else(|poisoned_lock| poisoned_lock.into_inner()); + credentials.get(&(self.matrix_user_id.clone(), self.rocketchat_server_id.clone())).map(|s| s.to_string()) + } + /// Update last message sent. pub fn set_last_message_sent(&mut self, connection: &SqliteConnection) -> Result<()> { let last_message_sent = @@ -178,6 +190,8 @@ impl UserOnRocketchatServer { /// Returns true if the user is logged in on the Rocket.Chat server via the application /// serivce, and false otherwise. pub fn is_logged_in(&self) -> bool { - self.rocketchat_auth_token.is_some() + let credentials = CREDENTIALS.read().unwrap_or_else(|poisoned_lock| poisoned_lock.into_inner()); + let auth_token: Option<&String> = credentials.get(&(self.matrix_user_id.clone(), self.rocketchat_server_id.clone())); + auth_token.is_some() } } diff --git a/src/matrix-rocketchat/models/virtual_user.rs b/src/matrix-rocketchat/models/virtual_user.rs index 3686ba5..fed79a6 100644 --- a/src/matrix-rocketchat/models/virtual_user.rs +++ b/src/matrix-rocketchat/models/virtual_user.rs @@ -14,12 +14,12 @@ pub struct VirtualUser<'a> { /// Logger context logger: &'a Logger, /// API to call the Matrix homeserver - matrix_api: &'a MatrixApi, + matrix_api: &'a dyn MatrixApi, } impl<'a> VirtualUser<'a> { /// Create a new virtual users model, to interact with Matrix virtual users. - pub fn new(config: &'a Config, logger: &'a Logger, matrix_api: &'a MatrixApi) -> VirtualUser<'a> { + pub fn new(config: &'a Config, logger: &'a Logger, matrix_api: &'a dyn MatrixApi) -> VirtualUser<'a> { VirtualUser { config, logger, matrix_api } } diff --git a/src/matrix-rocketchat/server.rs b/src/matrix-rocketchat/server.rs index c079145..8dbdfe0 100644 --- a/src/matrix-rocketchat/server.rs +++ b/src/matrix-rocketchat/server.rs @@ -10,8 +10,9 @@ use api::MatrixApi; use config::Config; use errors::*; use handlers::iron::{Rocketchat, RocketchatLogin, Transactions, Welcome}; +use i18n::*; use log::IronLogger; -use models::ConnectionPool; +use models::{ConnectionPool, Room}; embed_migrations!("migrations"); @@ -23,6 +24,15 @@ pub struct Server<'a> { logger: Logger, } +/// Enable/Disable notifications when the application service is started +#[derive(PartialEq)] +pub enum StartupNotification { + /// Do send notifications when the server is started + On, + /// Do not send notifications when the server is started + Off, +} + impl<'a> Server<'a> { /// Create a new `Server` with a given configuration. pub fn new(config: &Config, logger: Logger) -> Server { @@ -30,13 +40,17 @@ impl<'a> Server<'a> { } /// Runs the application service bridge. - pub fn run(&self, threads: usize) -> Result { + pub fn run(&self, threads: usize, startup_notification: StartupNotification) -> Result { self.prepare_database()?; let connection_pool = ConnectionPool::create(&self.config.database_url)?; let matrix_api = MatrixApi::new(self.config, self.logger.clone())?; self.setup_bot_user(matrix_api.as_ref())?; + if startup_notification == StartupNotification::On { + self.send_login_notifications(&matrix_api)?; + } + let router = self.setup_routes(matrix_api); let mut chain = Chain::new(router); chain.link_before(Write::::one(connection_pool)); @@ -62,7 +76,21 @@ impl<'a> Server<'a> { listener.chain_err(|| ErrorKind::ServerStartupError).map_err(Error::from) } - fn setup_routes(&self, matrix_api: Box) -> Router { + fn send_login_notifications(&self, matrix_api: &Box) -> Result<()> { + let bot_user_id = self.config.matrix_bot_user_id()?; + let room_ids = matrix_api.get_joined_rooms(bot_user_id.clone())?; + let msg = t!(["admin_room", "re_login"]).l(DEFAULT_LANGUAGE); + for room_id in room_ids { + let room = Room::new(self.config, &self.logger, matrix_api.as_ref(), room_id); + if room.is_admin_room()? { + matrix_api.send_text_message(room.id, bot_user_id.clone(), msg.clone())?; + } + } + + Ok(()) + } + + fn setup_routes(&self, matrix_api: Box) -> Router { debug!(self.logger, "Setting up routes"); let mut router = Router::new(); router.get("/", Welcome {}, "welcome"); @@ -78,7 +106,7 @@ impl<'a> Server<'a> { embedded_migrations::run(&connection).map_err(Error::from) } - fn setup_bot_user(&self, matrix_api: &MatrixApi) -> Result<()> { + fn setup_bot_user(&self, matrix_api: &dyn MatrixApi) -> Result<()> { let matrix_bot_user_id = self.config.matrix_bot_user_id()?; debug!(self.logger, "Setting up bot user {}", matrix_bot_user_id); if matrix_api.get_display_name(matrix_bot_user_id.clone())?.is_some() { diff --git a/tests/admin_commands_list.rs b/tests/admin_commands_list.rs index 5b07c72..ade3993 100644 --- a/tests/admin_commands_list.rs +++ b/tests/admin_commands_list.rs @@ -7,10 +7,9 @@ extern crate ruma_identifiers; use std::collections::HashMap; use std::convert::TryFrom; -use std::sync::{Arc, Mutex}; use iron::status; -use matrix_rocketchat::api::rocketchat::v1::{CHANNELS_LIST_JOINED_PATH, CHANNELS_LIST_PATH, LOGIN_PATH, ME_PATH}; +use matrix_rocketchat::api::rocketchat::v1::{CHANNELS_LIST_JOINED_PATH, CHANNELS_LIST_PATH, ME_PATH}; use matrix_rocketchat::api::MatrixApi; use matrix_rocketchat_test::{default_timeout, handlers, helpers, MessageForwarder, Test, DEFAULT_LOGGER}; use ruma_client_api::r0::send::send_message_event::Endpoint as SendMessageEventEndpoint; @@ -173,11 +172,6 @@ fn the_user_gets_a_message_when_the_me_endpoint_returns_an_error() { let mut matrix_router = test.default_matrix_routes(); matrix_router.put(SendMessageEventEndpoint::router_path(), message_forwarder, "send_message_event"); let mut rocketchat_router = test.default_rocketchat_routes(); - rocketchat_router.post( - LOGIN_PATH, - handlers::RocketchatLogin { successful: true, rocketchat_user_id: Arc::new(Mutex::new(None)) }, - "login", - ); rocketchat_router.get( ME_PATH, handlers::RocketchatErrorResponder { status: status::InternalServerError, message: "Spec Error".to_string() }, @@ -226,11 +220,6 @@ fn the_user_gets_a_message_when_the_me_response_cannot_be_deserialized() { let mut matrix_router = test.default_matrix_routes(); matrix_router.put(SendMessageEventEndpoint::router_path(), message_forwarder, "send_message_event"); let mut rocketchat_router = test.default_rocketchat_routes(); - rocketchat_router.post( - LOGIN_PATH, - handlers::RocketchatLogin { successful: true, rocketchat_user_id: Arc::new(Mutex::new(None)) }, - "login", - ); rocketchat_router.get(ME_PATH, handlers::InvalidJsonResponse { status: status::Ok }, "me"); let channels = test.channel_list(); diff --git a/tests/admin_commands_login.rs b/tests/admin_commands_login.rs index 2a90029..819f514 100644 --- a/tests/admin_commands_login.rs +++ b/tests/admin_commands_login.rs @@ -10,11 +10,10 @@ extern crate serde_json; use std::collections::HashMap; use std::convert::TryFrom; -use std::sync::{Arc, Mutex}; use http::Method; use iron::status; -use matrix_rocketchat::api::rocketchat::v1::LOGIN_PATH; +use matrix_rocketchat::api::rocketchat::v1::{CHAT_POST_MESSAGE_PATH, LOGIN_PATH}; use matrix_rocketchat::api::{MatrixApi, RequestData, RestApi}; use matrix_rocketchat::models::Credentials; use matrix_rocketchat::models::{RocketchatServer, UserOnRocketchatServer}; @@ -50,7 +49,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(); - assert_eq!(user_on_rocketchat_server.rocketchat_auth_token.unwrap(), "spec_auth_token"); + assert_eq!(user_on_rocketchat_server.rocketchat_auth_token().unwrap(), "spec_auth_token"); let message_received_by_matrix = receiver.recv_timeout(default_timeout()).unwrap(); assert!(message_received_by_matrix.contains("You are logged in.")); @@ -62,18 +61,7 @@ fn wrong_password_when_logging_in_via_chat_message() { let (message_forwarder, receiver) = MessageForwarder::new(); let mut matrix_router = test.default_matrix_routes(); matrix_router.put(SendMessageEventEndpoint::router_path(), message_forwarder, "send_message_event"); - let mut rocketchat_router = test.default_rocketchat_routes(); - rocketchat_router.post( - LOGIN_PATH, - handlers::RocketchatLogin { successful: false, rocketchat_user_id: Arc::new(Mutex::new(None)) }, - "login", - ); - let test = test - .with_matrix_routes(matrix_router) - .with_custom_rocketchat_routes(rocketchat_router) - .with_rocketchat_mock() - .with_connected_admin_room() - .run(); + let test = test.with_matrix_routes(matrix_router).with_rocketchat_mock().with_connected_admin_room().run(); helpers::send_room_message_from_matrix( &test.config.as_url, @@ -148,20 +136,50 @@ fn sucessfully_login_via_rest_api() { let user_on_rocketchat_server = UserOnRocketchatServer::find(&connection, &UserId::try_from("@spec_user:localhost").unwrap(), rocketchat_server.id) .unwrap(); - assert_eq!(user_on_rocketchat_server.rocketchat_auth_token.unwrap(), "spec_auth_token"); + assert_eq!(user_on_rocketchat_server.rocketchat_auth_token().unwrap(), "spec_auth_token"); } #[test] -fn wrong_password_when_logging_in_via_rest_api() { - let test = Test::new(); - +fn successfully_login_after_an_application_service_restart() { + let mut test = Test::new(); + let (message_forwarder, receiver) = MessageForwarder::new(); let mut rocketchat_router = test.default_rocketchat_routes(); - rocketchat_router.post( - LOGIN_PATH, - handlers::RocketchatLogin { successful: false, rocketchat_user_id: Arc::new(Mutex::new(None)) }, - "login", + rocketchat_router.post(CHAT_POST_MESSAGE_PATH, message_forwarder, "post_text_message"); + + test = test + .with_rocketchat_mock() + .with_custom_rocketchat_routes(rocketchat_router) + .with_connected_admin_room() + .with_logged_in_user() + .with_bridged_room(("spec_channel", vec!["spec_user"])) + .run(); + + helpers::send_room_message_from_matrix( + &test.config.as_url, + RoomId::try_from("!spec_channel_id:localhost").unwrap(), + UserId::try_from("@spec_user:localhost").unwrap(), + "spec message".to_string(), + ); + + let first_message_received_by_rocketchat = receiver.recv_timeout(default_timeout()).unwrap(); + assert!(first_message_received_by_rocketchat.contains("spec message")); + + test.restart_application_service(); + + helpers::send_room_message_from_matrix( + &test.config.as_url, + RoomId::try_from("!spec_channel_id:localhost").unwrap(), + UserId::try_from("@spec_user:localhost").unwrap(), + "next message".to_string(), ); - let test = test.with_custom_rocketchat_routes(rocketchat_router).with_rocketchat_mock().with_connected_admin_room().run(); + + let second_message_received_by_rocketchat = receiver.recv_timeout(default_timeout()).unwrap(); + println!("SECOND: {}", second_message_received_by_rocketchat); +} + +#[test] +fn wrong_password_when_logging_in_via_rest_api() { + let test = Test::new().with_rocketchat_mock().with_connected_admin_room().run(); let login_request = Credentials { user_id: UserId::try_from("@spec_user:localhost").unwrap(), diff --git a/tests/admin_commands_unkown.rs b/tests/admin_commands_unkown.rs index 5dff22b..97f3565 100644 --- a/tests/admin_commands_unkown.rs +++ b/tests/admin_commands_unkown.rs @@ -26,8 +26,11 @@ fn unknown_commands_from_the_admin_room_are_ignored() { "bogus command".to_string(), ); + // welcome message + receiver.recv_timeout(default_timeout()).unwrap(); + // we don't get a message, because the command is ignored and no error occurs - receiver.recv_timeout(default_timeout()).is_err(); + assert!(receiver.recv_timeout(default_timeout()).is_err()); } #[test] @@ -46,8 +49,11 @@ fn unknown_content_types_from_the_admin_room_are_ignored() { "emote message".to_string(), ); + // welcome message + receiver.recv_timeout(default_timeout()).unwrap(); + // we don't get a message, because unknown content types are ignored and no error occurs - receiver.recv_timeout(default_timeout()).is_err(); + assert!(receiver.recv_timeout(default_timeout()).is_err()); } #[test] @@ -66,6 +72,9 @@ fn messages_from_the_bot_user_are_ignored() { "bot message".to_string(), ); + // welcome message + receiver.recv_timeout(default_timeout()).unwrap(); + // we don't get a message, because messages from the bot user are ignored and no error occurs - receiver.recv_timeout(default_timeout()).is_err(); + assert!(receiver.recv_timeout(default_timeout()).is_err()); } diff --git a/tests/admin_room.rs b/tests/admin_room.rs index b19a8fd..cd6c12b 100644 --- a/tests/admin_room.rs +++ b/tests/admin_room.rs @@ -491,7 +491,7 @@ fn unkown_membership_states_are_skipped() { // the user does not get a message, because the event is ignored // so the receiver never gets a message and times out - receiver.recv_timeout(default_timeout()).is_err(); + receiver.recv_timeout(default_timeout()).unwrap_err(); } #[test] diff --git a/tests/forward_rocketchat_direct_message_to_matrix.rs b/tests/forward_rocketchat_direct_message_to_matrix.rs index cb0394e..5439d8e 100644 --- a/tests/forward_rocketchat_direct_message_to_matrix.rs +++ b/tests/forward_rocketchat_direct_message_to_matrix.rs @@ -14,9 +14,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; use iron::{status, Chain}; -use matrix_rocketchat::api::rocketchat::v1::{ - Attachment, File, Message, UserInfo, CHAT_GET_MESSAGE_PATH, DM_LIST_PATH, LOGIN_PATH, ME_PATH, -}; +use matrix_rocketchat::api::rocketchat::v1::{Attachment, File, Message, UserInfo, CHAT_GET_MESSAGE_PATH, DM_LIST_PATH}; use matrix_rocketchat::api::rocketchat::WebhookMessage; use matrix_rocketchat::api::MatrixApi; use matrix_rocketchat::models::{Room, UserOnRocketchatServer}; @@ -468,19 +466,14 @@ fn do_use_two_different_dm_rooms_when_both_users_are_on_matrix() { let direct_messages_list_handler = handlers::RocketchatDirectMessagesList { direct_messages: direct_messages, status: status::Ok }; rocketchat_router.get(DM_LIST_PATH, direct_messages_list_handler, "direct_messages_list"); - let login_user_id = Arc::new(Mutex::new(Some("spec_user_id".to_string()))); - rocketchat_router.post( - LOGIN_PATH, - handlers::RocketchatLogin { successful: true, rocketchat_user_id: Arc::clone(&login_user_id) }, - "login", - ); - let me_username = Arc::new(Mutex::new("spec_user".to_string())); - rocketchat_router.get(ME_PATH, handlers::RocketchatMe { username: Arc::clone(&me_username) }, "me"); + let mut rocketchat_users = HashMap::new(); + rocketchat_users.insert("other_user".to_string(), ("other_user_id".to_string(), "secret".to_string())); let test = test .with_matrix_routes(matrix_router) .with_rocketchat_mock() .with_custom_rocketchat_routes(rocketchat_router) + .with_rocketchat_users(rocketchat_users) .with_connected_admin_room() .with_logged_in_user() .run(); @@ -520,13 +513,6 @@ fn do_use_two_different_dm_rooms_when_both_users_are_on_matrix() { let other_user_sender_direct_message_received_by_matrix = receiver.recv_timeout(default_timeout()).unwrap(); assert!(other_user_sender_direct_message_received_by_matrix.contains("Hey there")); - { - let mut new_login_user_id = login_user_id.lock().unwrap(); - *new_login_user_id = Some("other_user_id".to_string()); - let mut new_me_username = me_username.lock().unwrap(); - *new_me_username = "other_user".to_string(); - } - let matrix_api = MatrixApi::new(&test.config, DEFAULT_LOGGER.clone()).unwrap(); matrix_api.register("other_user".to_string()).unwrap(); diff --git a/tests/matrix-rocketchat-test/handlers.rs b/tests/matrix-rocketchat-test/handlers.rs index 8b6c021..364ce53 100644 --- a/tests/matrix-rocketchat-test/handlers.rs +++ b/tests/matrix-rocketchat-test/handlers.rs @@ -15,7 +15,7 @@ use iron::prelude::*; use iron::url::percent_encoding::percent_decode; use iron::url::Url; use iron::{status, BeforeMiddleware, Chain, Handler}; -use matrix_rocketchat::api::rocketchat::v1::Message as RocketchatMessage; +use matrix_rocketchat::api::rocketchat::v1::{LoginPayload, Message as RocketchatMessage}; use matrix_rocketchat::api::rocketchat::{Channel, User}; use matrix_rocketchat::errors::{MatrixErrorResponse, RocketchatErrorResponse}; use persistent::Write; @@ -53,48 +53,41 @@ impl Handler for RocketchatInfo { } pub struct RocketchatLogin { - pub successful: bool, - pub rocketchat_user_id: Arc>>, + pub users: Arc>>, } impl Handler for RocketchatLogin { - fn handle(&self, _request: &mut Request) -> IronResult { + fn handle(&self, request: &mut Request) -> IronResult { debug!(DEFAULT_LOGGER, "Rocket.Chat mock server got login request"); + let request_payload = extract_payload(request); + let login_payload: LoginPayload = serde_json::from_str(&request_payload).unwrap(); - let (status, payload) = match self.successful { - true => { - let user_id: String = self - .rocketchat_user_id - .lock() - .unwrap() - .clone() - .unwrap_or(thread_rng().gen_ascii_chars().take(10).collect()); - ( - status::Ok, - r#"{ + let users_map = self.users.lock().unwrap(); + if let Some((user_id, password)) = users_map.get(login_payload.username) { + if password == login_payload.password { + let payload = r#"{ "status": "success", "data": { "authToken": "spec_auth_token", "userId": "USER_ID" } }"# - .replace("USER_ID", &user_id), - ) + .replace("USER_ID", &user_id); + return Ok(Response::with((status::Ok, payload))); } - false => ( - status::Unauthorized, - r#"{ + } + + let payload = r#"{ "status": "error", "message": "Unauthorized" }"# - .to_string(), - ), - }; + .to_string(); - Ok(Response::with((status, payload))) + Ok(Response::with((status::Unauthorized, payload))) } } +//TODO: Fix this handler to use the header value and lookup the user in the users list instead of hard-code it in the struct pub struct RocketchatMe { pub username: Arc>, } @@ -483,7 +476,7 @@ impl MatrixRegister { pub fn with_forwarder() -> (Chain, Receiver) { let (message_forwarder, receiver) = MessageForwarder::new(); let mut chain = Chain::new(MatrixRegister {}); - chain.link_before(message_forwarder);; + chain.link_before(message_forwarder); (chain, receiver) } } @@ -519,7 +512,7 @@ impl MatrixSetDisplayName { pub fn with_forwarder() -> (Chain, Receiver) { let (message_forwarder, receiver) = MessageForwarder::new(); let mut chain = Chain::new(MatrixSetDisplayName {}); - chain.link_before(message_forwarder);; + chain.link_before(message_forwarder); (chain, receiver) } } @@ -718,7 +711,7 @@ impl SendRoomState { pub fn with_forwarder() -> (Chain, Receiver) { let (message_forwarder, receiver) = MessageForwarder::new(); let mut chain = Chain::new(SendRoomState {}); - chain.link_before(message_forwarder);; + chain.link_before(message_forwarder); (chain, receiver) } } @@ -853,7 +846,7 @@ impl MatrixCreateContentHandler { pub fn with_forwarder(uploaded_files: Arc>>) -> (Chain, Receiver) { let (message_forwarder, receiver) = MessageForwarder::new(); let mut chain = Chain::new(MatrixCreateContentHandler { uploaded_files: uploaded_files }); - chain.link_before(message_forwarder);; + chain.link_before(message_forwarder); (chain, receiver) } } @@ -1054,7 +1047,7 @@ impl MatrixJoinRoom { pub fn with_forwarder(as_url: String, send_inviter: bool) -> (Chain, Receiver) { let (message_forwarder, receiver) = MessageForwarder::new(); let mut chain = Chain::new(MatrixJoinRoom { as_url: as_url, send_inviter: send_inviter }); - chain.link_before(message_forwarder);; + chain.link_before(message_forwarder); (chain, receiver) } } @@ -1123,7 +1116,7 @@ impl MatrixInviteUser { pub fn with_forwarder(as_url: String) -> (Chain, Receiver) { let (message_forwarder, receiver) = MessageForwarder::new(); let mut chain = Chain::new(MatrixInviteUser { as_url: as_url }); - chain.link_before(message_forwarder);; + chain.link_before(message_forwarder); (chain, receiver) } } @@ -1169,7 +1162,7 @@ impl MatrixLeaveRoom { pub fn with_forwarder(as_url: String) -> (Chain, Receiver) { let (message_forwarder, receiver) = MessageForwarder::new(); let mut chain = Chain::new(MatrixLeaveRoom { as_url: as_url }); - chain.link_before(message_forwarder);; + chain.link_before(message_forwarder); (chain, receiver) } } @@ -1270,7 +1263,7 @@ impl MatrixConditionalErrorResponder { }; let mut chain = Chain::new(conditional_error_responder); - chain.link_after(message_forwarder);; + chain.link_after(message_forwarder); (chain, receiver) } } @@ -1535,7 +1528,7 @@ fn add_alias_to_room(request: &mut Request, room_id: RoomId, room_alias: RoomAli let aliases = room_alias_map.get_mut(&room_id).unwrap(); - debug!(DEFAULT_LOGGER, "Matrix mock server adds alias {} to room {}", room_alias, room_id);; + debug!(DEFAULT_LOGGER, "Matrix mock server adds alias {} to room {}", room_alias, room_id); aliases.push(room_alias); Ok(()) } diff --git a/tests/matrix-rocketchat-test/helpers.rs b/tests/matrix-rocketchat-test/helpers.rs index 38451fb..d4622ec 100644 --- a/tests/matrix-rocketchat-test/helpers.rs +++ b/tests/matrix-rocketchat-test/helpers.rs @@ -286,8 +286,7 @@ pub fn logout_user_from_rocketchat_server_on_bridge( user_id: &UserId, ) { let mut user_on_rocketchat_server = UserOnRocketchatServer::find(connection, &user_id, rocketchat_server_id).unwrap(); - let rocketchat_user_id = user_on_rocketchat_server.rocketchat_user_id.clone(); - user_on_rocketchat_server.set_credentials(connection, rocketchat_user_id, None).unwrap(); + user_on_rocketchat_server.set_credentials(connection, None, None).unwrap(); } pub fn add_room_alias_id(config: &Config, room_id: RoomId, room_alias_id: RoomAliasId, user_id: UserId, access_token: &str) { diff --git a/tests/matrix-rocketchat-test/lib.rs b/tests/matrix-rocketchat-test/lib.rs index 0fd22d4..ccb5296 100644 --- a/tests/matrix-rocketchat-test/lib.rs +++ b/tests/matrix-rocketchat-test/lib.rs @@ -51,6 +51,7 @@ use matrix_rocketchat::api::rocketchat::v1::{ }; use matrix_rocketchat::api::MatrixApi; use matrix_rocketchat::models::ConnectionPool; +use matrix_rocketchat::server::StartupNotification; use matrix_rocketchat::{Config, Server}; use persistent::Write; use r2d2::Pool; @@ -205,6 +206,8 @@ pub struct Test { pub rocketchat_listening: Option, /// The URL of the Rocket.Chat mock server pub rocketchat_mock_url: Option, + /// A list of Users that are registered on the Rocket.Chat mock server + pub rocketchat_users: Arc>>, /// Temp directory to store data during the test, it has to be part of the struct so that it /// does not get dropped until the test is over pub temp_dir: TempDir, @@ -239,6 +242,7 @@ impl Test { rocketchat_mock_router: None, rocketchat_listening: None, rocketchat_mock_url: None, + rocketchat_users: Arc::new(Mutex::new(default_rocketchat_users())), temp_dir: temp_dir, with_admin_room: false, with_connected_admin_room: false, @@ -304,6 +308,14 @@ impl Test { self } + pub fn with_rocketchat_users(self, users: HashMap) -> Test { + for (k, v) in users { + self.rocketchat_users.lock().unwrap().insert(k, v); + } + + self + } + /// Run the application service so that a test can interact with it. pub fn run(mut self) -> Test { self.run_matrix_homeserver_mock(); @@ -344,6 +356,17 @@ impl Test { self } + // Restart the application service while a test is running. + pub fn restart_application_service(&mut self) { + if let Some(ref mut listening) = self.as_listening { + info!(DEFAULT_LOGGER, "Closing AS {:?}", listening); + listening.close().unwrap() + }; + + self.config.as_address = get_free_socket_addr(); + self.run_application_service(); + } + fn run_matrix_homeserver_mock(&mut self) { let (hs_tx, hs_rx) = channel::(); let hs_socket_addr = get_free_socket_addr(); @@ -414,16 +437,17 @@ impl Test { thread::spawn(move || { debug!(DEFAULT_LOGGER, "config: {:?}", server_config); - let listening = match Server::new(&server_config, DEFAULT_LOGGER.clone()).run(IRON_THREADS) { - Ok(listening) => listening, - Err(err) => { - error!(DEFAULT_LOGGER, "error: {}", err); - for err in err.error_chain.iter().skip(1) { - error!(DEFAULT_LOGGER, "caused by: {}", err); + let listening = + match Server::new(&server_config, DEFAULT_LOGGER.clone()).run(IRON_THREADS, StartupNotification::Off) { + Ok(listening) => listening, + Err(err) => { + error!(DEFAULT_LOGGER, "error: {}", err); + for err in err.error_chain.iter().skip(1) { + error!(DEFAULT_LOGGER, "caused by: {}", err); + } + return; } - return; - } - }; + }; as_tx.send(listening).unwrap() }); @@ -586,12 +610,7 @@ impl Test { router.get("/api/info", handlers::RocketchatInfo { version: DEFAULT_ROCKETCHAT_VERSION }, "info"); - let login_user_id = Arc::new(Mutex::new(Some("spec_user_id".to_string()))); - router.post( - LOGIN_PATH, - handlers::RocketchatLogin { successful: true, rocketchat_user_id: Arc::clone(&login_user_id) }, - "login", - ); + router.post(LOGIN_PATH, handlers::RocketchatLogin { users: Arc::clone(&self.rocketchat_users) }, "login"); let me_username = Arc::new(Mutex::new("spec_user".to_string())); router.get(ME_PATH, handlers::RocketchatMe { username: Arc::clone(&me_username) }, "me"); @@ -724,3 +743,10 @@ pub fn extract_payload(request: &mut Request) -> String { fn file_line_logger_format(info: &Record) -> String { format!("{}:{} {}", info.file(), info.line(), info.module()) } + +fn default_rocketchat_users() -> HashMap { + let mut users = HashMap::new(); + users.insert("spec_user".to_string(), ("spec_user_id".to_string(), "secret".to_string())); + + users +} diff --git a/tests/startup.rs b/tests/startup.rs index b5f9cd2..5687799 100644 --- a/tests/startup.rs +++ b/tests/startup.rs @@ -15,6 +15,7 @@ use std::thread; use iron::{status, Iron, Listening}; use matrix_rocketchat::errors::*; +use matrix_rocketchat::server::StartupNotification; use matrix_rocketchat::Server; use matrix_rocketchat_test::{default_matrix_api_versions, handlers, DEFAULT_LOGGER, IRON_THREADS, TEMP_DIR_NAME}; use router::Router; @@ -51,7 +52,8 @@ fn starup_fails_when_server_cannot_bind_to_address() { let running_server_log = log.clone(); let (running_server_tx, running_server_rx) = channel::>(); thread::spawn(move || { - let running_server_result = Server::new(&running_server_config, running_server_log).run(IRON_THREADS); + let running_server_result = + Server::new(&running_server_config, running_server_log).run(IRON_THREADS, StartupNotification::Off); homeserver_mock_listen.close().unwrap(); running_server_tx.send(running_server_result).unwrap(); }); @@ -60,7 +62,7 @@ fn starup_fails_when_server_cannot_bind_to_address() { let (failed_server_tx, failed_server_rx) = channel::>(); thread::spawn(move || { - let failed_server_result = Server::new(&config, log).run(IRON_THREADS); + let failed_server_result = Server::new(&config, log).run(IRON_THREADS, StartupNotification::Off); failed_server_tx.send(failed_server_result).unwrap(); }); let failed_server_result = failed_server_rx.recv_timeout(matrix_rocketchat_test::default_timeout()).unwrap(); @@ -173,7 +175,7 @@ fn start_servers(matrix_router: Router) -> Result { let mut config = matrix_rocketchat_test::build_test_config(&temp_dir); config.hs_url = format!("http://{}:{}", homeserver_mock_socket_addr.ip(), homeserver_mock_socket_addr.port()); let log = DEFAULT_LOGGER.clone(); - let server_result = Server::new(&config, log).run(IRON_THREADS); + let server_result = Server::new(&config, log).run(IRON_THREADS, StartupNotification::Off); server_tx.send(server_result).unwrap(); });