Skip to content

Commit

Permalink
Add invites to matrix mock server
Browse files Browse the repository at this point in the history
  • Loading branch information
exul committed Oct 4, 2017
1 parent c2048bd commit c15b197
Show file tree
Hide file tree
Showing 20 changed files with 324 additions and 241 deletions.
1 change: 1 addition & 0 deletions assets/translations.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ en:
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 and `_`. The maximum length is ${max_rocketchat_server_id_length} characters."
internal: "An internal error occurred"
inviter_unknown: "The invite didn't contain a sender, the admin room could not be validated"
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."
only_room_creator_can_invite_bot_user: "Only the room creator can invite the Rocket.Chat bot user, please create a new room and invite the Rocket.Chat user to create an admin room."
Expand Down
2 changes: 1 addition & 1 deletion src/matrix-rocketchat/api/matrix/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ pub trait MatrixApi: Send + Sync + MatrixApiClone {
/// Forget a room.
fn forget_room(&self, matrix_room_id: RoomId) -> Result<()>;
/// Get the room id based on the room alias.
fn get_room_alias(&self, matrix_room_alias_id: RoomAliasId, sender_id: Option<UserId>) -> Result<Option<RoomId>>;
fn get_room_alias(&self, matrix_room_alias_id: RoomAliasId) -> Result<Option<RoomId>>;
/// Get a rooms canonical alias.
fn get_room_canonical_alias(&self, matrix_room_id: RoomId, sender_id: Option<UserId>) -> Result<Option<RoomAliasId>>;
/// Get the `user_id` of the user that created the room.
Expand Down
9 changes: 2 additions & 7 deletions src/matrix-rocketchat/api/matrix/r0.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,17 +120,12 @@ impl super::MatrixApi for MatrixApi {
Ok(())
}

fn get_room_alias(&self, matrix_room_alias_id: RoomAliasId, sender_id: Option<UserId>) -> Result<Option<RoomId>> {
fn get_room_alias(&self, matrix_room_alias_id: RoomAliasId) -> Result<Option<RoomId>> {
// the ruma client api path params cannot be used here, because they are not url encoded
let encoded_room_alias = url::form_urlencoded::byte_serialize(matrix_room_alias_id.to_string().as_bytes())
.collect::<String>();
let endpoint = self.base_url.clone() + &format!("/_matrix/client/r0/directory/room/{}", &encoded_room_alias);
let user_id;
let mut params = self.params_hash();
if let Some(matrix_user_id) = sender_id {
user_id = matrix_user_id.to_string();
params.insert("user_id", &user_id);
}
let params = self.params_hash();

let (body, status_code) = RestApi::call_matrix(GetAliasEndpoint::method(), &endpoint, "{}", &params)?;
if status_code == StatusCode::NotFound {
Expand Down
34 changes: 10 additions & 24 deletions src/matrix-rocketchat/db/room.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ use ruma_identifiers::{RoomAliasId, RoomId, UserId};

use api::{MatrixApi, RocketchatApi};
use config::Config;
use db::UserOnRocketchatServer;
use errors::*;
use super::RocketchatServer;

Expand All @@ -25,7 +24,7 @@ impl Room {
) -> Result<bool> {
let room_alias_id = Room::build_room_alias_id(config, rocketchat_server_id, rocketchat_channel_id)?;

match matrix_api.get_room_alias(room_alias_id, None)? {
match matrix_api.get_room_alias(room_alias_id)? {
Some(matrix_room_id) => {
let is_user_in_room = Room::user_ids(matrix_api, matrix_room_id, None)?.iter().any(|id| id == matrix_user_id);
Ok(is_user_in_room)
Expand Down Expand Up @@ -100,7 +99,7 @@ impl Room {

match channel_id {
Some(channel_id) => {
Room::matrix_id_from_rocketchat_channel_id(config, matrix_api, rocketchat_server_id, &channel_id, None)
Room::matrix_id_from_rocketchat_channel_id(config, matrix_api, rocketchat_server_id, &channel_id)
}
None => Ok(None),
}
Expand All @@ -112,30 +111,16 @@ impl Room {
matrix_api: &MatrixApi,
rocketchat_server_id: &str,
rocketchat_channel_id: &str,
sender_id: Option<UserId>,
) -> Result<Option<RoomId>> {
let room_alias_id = Room::build_room_alias_id(config, rocketchat_server_id, rocketchat_channel_id)?;
matrix_api.get_room_alias(room_alias_id, sender_id)
matrix_api.get_room_alias(room_alias_id)
}

/// Check if the room is a direct message room.
pub fn is_direct_message_room(
conn: &SqliteConnection,
matrix_api: &MatrixApi,
room_id: RoomId,
rocketchat_server_id: String,
sender_id: String,
) -> Result<bool> {
match UserOnRocketchatServer::find_by_rocketchat_user_id(conn, rocketchat_server_id, sender_id.clone(), true)? {
Some(sender_matrix_user_id) => {
let alias = matrix_api
.get_room_canonical_alias(room_id, Some(sender_matrix_user_id.matrix_user_id.clone()))?
.map(|alias| alias.to_string())
.unwrap_or_default();
Ok(alias.contains(&sender_id))
}
None => Ok(false),
}
pub fn is_direct_message_room(matrix_api: &MatrixApi, room_id: RoomId) -> Result<bool> {
let alias =
matrix_api.get_room_canonical_alias(room_id, None)?.map(|alias| alias.alias().to_string()).unwrap_or_default();
Ok(alias.ends_with("#dm"))
}

/// Checks if a room is an admin room.
Expand All @@ -145,11 +130,12 @@ impl Room {
return Ok(false);
}

let virtual_user_prefix = format!("@{}", config.sender_localpart);
let matrix_bot_user_id = config.matrix_bot_user_id()?;
let matrix_user_ids = Room::user_ids(matrix_api, matrix_room_id.clone(), None)?;
let bot_user_in_room = matrix_user_ids.iter().any(|id| id == &matrix_bot_user_id);
let room_creator = matrix_api.get_room_creator(matrix_room_id)?;
Ok(room_creator != matrix_bot_user_id && bot_user_in_room)
let room_creator = matrix_api.get_room_creator(matrix_room_id)?.to_string();
Ok(!room_creator.starts_with(&virtual_user_prefix) && bot_user_in_room)
}

/// Check if a room is bridged to Rocket.Chat
Expand Down
7 changes: 6 additions & 1 deletion src/matrix-rocketchat/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -305,8 +305,13 @@ error_chain!{
display("Room {} has more then two members and cannot be used as admin room", room_id)
}

InviterUnknown(room_id: RoomId) {
description("Inviter for join event was not found")
display("Could not determine if the admin room {} is valid, because the inviter is unknown", room_id)
}

OnlyRoomCreatorCanInviteBotUser(inviter_id: UserId, room_id: RoomId, creator_id: UserId) {
description("Error when getting the connection pool from the request")
description("Only the room creator can invite the bot user")
display("The bot user was invited by the user {} but the room {} was created by {}, bot user is leaving", inviter_id, room_id, creator_id)
}

Expand Down
1 change: 0 additions & 1 deletion src/matrix-rocketchat/handlers/events/command_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,6 @@ impl<'a> CommandHandler<'a> {
self.matrix_api,
&rocketchat_server.id,
&channel.id,
None,
)? {
Some(matrix_room_id) => {
room_handler.bridge_existing_room(matrix_room_id.clone(), event.user_id.clone(), channel_name.to_string())?;
Expand Down
57 changes: 39 additions & 18 deletions src/matrix-rocketchat/handlers/events/room_handler.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use std::collections::HashMap;
use std::convert::TryFrom;

use diesel::sqlite::SqliteConnection;
Expand All @@ -16,6 +17,7 @@ use handlers::ErrorNotifier;
use handlers::rocketchat::VirtualUserHandler;
use i18n::*;
use log;
use serde_json::{self, Value};
use super::CommandHandler;

/// Handles room events
Expand Down Expand Up @@ -60,7 +62,20 @@ impl<'a> RoomHandler<'a> {
let msg = format!("Received join event for bot user {} and room {}", matrix_bot_user_id, event.room_id);
debug!(self.logger, msg);

self.handle_bot_join(event.room_id.clone(), matrix_bot_user_id)?;
let unsigned: HashMap<String, Value> = serde_json::from_value(event.unsigned.clone().unwrap_or_default())
.unwrap_or_default();
let inviter_id = match unsigned.get("prev_sender") {
Some(prev_sender) => {
let raw_id: String = serde_json::from_value(prev_sender.clone()).unwrap_or("".to_string());
let inviter_id = UserId::try_from(&raw_id).chain_err(|| ErrorKind::InvalidUserId(raw_id))?;
Some(inviter_id)
}
None => None,
};

//let raw_inviter = event.unsigned.clone().unwrap_or(Value::Object(Map::new()))["prev_sender"].to_string();

self.handle_bot_join(event.room_id.clone(), matrix_bot_user_id, inviter_id)?;
}
MembershipState::Join => {
let msg = format!("Received join event for user {} and room {}", &state_key, &event.room_id);
Expand Down Expand Up @@ -141,10 +156,12 @@ impl<'a> RoomHandler<'a> {
return Ok(());
}

self.matrix_api.join(matrix_room_id, invited_user_id)
self.matrix_api.join(matrix_room_id, invited_user_id)?;

Ok(())
}

fn handle_bot_join(&self, matrix_room_id: RoomId, matrix_bot_user_id: UserId) -> Result<()> {
fn handle_bot_join(&self, matrix_room_id: RoomId, matrix_bot_user_id: UserId, inviter_id: Option<UserId>) -> Result<()> {
let is_admin_room = match Room::is_admin_room(self.matrix_api, self.config, matrix_room_id.clone()) {
Ok(is_admin_room) => is_admin_room,
Err(err) => {
Expand All @@ -158,30 +175,37 @@ impl<'a> RoomHandler<'a> {
};

if is_admin_room {
let user_ids: Vec<UserId> = Room::user_ids(self.matrix_api, matrix_room_id.clone(), None)?
.into_iter()
.filter(|id| id != &matrix_bot_user_id)
.collect();
let inviter_id = user_ids.first().expect("The user that sent the invitation has to be in the room");

self.setup_admin_room(matrix_room_id.clone(), matrix_bot_user_id.clone(), inviter_id)?;
}

// leave direct message room, the bot only joined it to be able to read the room alias
if Room::is_direct_message_room(self.matrix_api, matrix_room_id.clone())? {
self.leave_and_forget_room(matrix_room_id.clone(), matrix_bot_user_id)?;
}

Ok(())
}

fn setup_admin_room(&self, matrix_room_id: RoomId, matrix_bot_user_id: UserId, inviter_id: &UserId) -> Result<()> {
fn setup_admin_room(&self, matrix_room_id: RoomId, matrix_bot_user_id: UserId, inviter_id: Option<UserId>) -> Result<()> {
debug!(self.logger, "Setting up a new admin room with id {}", matrix_room_id);

if let Err(err) = self.is_admin_room_valid(matrix_room_id.clone(), inviter_id) {
let inviter_idd = match inviter_id {
Some(inviter_id) => inviter_id,
None => {
let err = ErrorKind::InviterUnknown(matrix_room_id.clone());
bail_error!(err, t!(["errors", "inviter_unknown"]));
}
};

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

Expand All @@ -193,7 +217,7 @@ impl<'a> RoomHandler<'a> {
}

self.connection.transaction(|| {
let invitation_submitter = User::find_or_create_by_matrix_user_id(self.connection, inviter_id.clone())?;
let invitation_submitter = User::find_or_create_by_matrix_user_id(self.connection, inviter_idd.clone())?;
match CommandHandler::build_help_message(
self.connection,
self.matrix_api,
Expand Down Expand Up @@ -247,11 +271,8 @@ impl<'a> RoomHandler<'a> {
}

fn admin_room_language(&self, matrix_room_id: RoomId) -> Result<String> {
let matrix_bot_user_id = self.config.matrix_bot_user_id()?;
let user_ids: Vec<UserId> =
Room::user_ids(self.matrix_api, matrix_room_id, None)?.into_iter().filter(|id| id != &matrix_bot_user_id).collect();
let user_id = user_ids.first().expect("An admin room always contains another user");
let user = User::find(self.connection, user_id)?;
let user_id = self.matrix_api.get_room_creator(matrix_room_id.clone())?;
let user = User::find(self.connection, &user_id)?;
Ok(user.language.clone())
}

Expand Down
27 changes: 16 additions & 11 deletions src/matrix-rocketchat/handlers/rocketchat/forwarder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,18 @@ impl<'a> Forwarder<'a> {
return Ok(());
}

let is_direct_message_room = message.channel_id.contains(&message.user_id);
let channel_id = if is_direct_message_room {
format!("{}#dm", message.channel_id)
} else {
message.channel_id.clone()
};

let matrix_room_id = match Room::matrix_id_from_rocketchat_channel_id(
self.config,
self.matrix_api,
&rocketchat_server.id,
&message.channel_id,
Some(user_on_rocketchat_server.matrix_user_id.clone()),
&channel_id,
)? {
Some(matrix_room_id) => matrix_room_id,
None => {
Expand All @@ -77,14 +83,7 @@ impl<'a> Forwarder<'a> {
}
};

if Room::is_direct_message_room(
self.connection,
self.matrix_api,
matrix_room_id.clone(),
rocketchat_server.id.clone(),
message.user_id.clone(),
)?
{
if is_direct_message_room {
if Room::direct_message_room_matrix_user(
self.config,
self.matrix_api,
Expand Down Expand Up @@ -182,13 +181,19 @@ impl<'a> Forwarder<'a> {
let room_display_name_suffix =
t!(["defaults", "direct_message_room_display_name_suffix"]).l(&direct_message_receiver.language);
let room_display_name = format!("{} {}", message.user_name, room_display_name_suffix);
let dm_channel_id = format!("{}#dm", direct_message_channel.id.clone());
let matrix_room_id = room_handler.create_room(
direct_message_channel.id.clone(),
dm_channel_id,
rocketchat_server.id.clone(),
direct_message_sender.matrix_user_id.clone(),
user_on_rocketchat_server.matrix_user_id.clone(),
Some(room_display_name),
)?;

// invite the bot user into the direct message room to be able to read the room alias
// the bot will leave as soon as the AS gets the join event
let invitee_id = self.config.matrix_bot_user_id()?;
self.matrix_api.invite(matrix_room_id.clone(), invitee_id.clone(), direct_message_sender.matrix_user_id.clone())?;
debug!(self.logger, "Direct message room {} successfully created", &matrix_room_id);

Ok(Some(matrix_room_id))
Expand Down

0 comments on commit c15b197

Please sign in to comment.