feat(sdk): Add method to set whether a room is DM and store all targets

This ensures also that we use, for user verification, only a DM room with
no members other then ourself and the user to be verified.
This commit is contained in:
Julian Sparber
2022-04-27 12:53:51 +02:00
committed by GitHub
parent e5c2ba4bd8
commit 910bf531fe
6 changed files with 88 additions and 22 deletions

View File

@@ -497,11 +497,12 @@ impl BaseClient {
);
if let Some(room) = changes.room_infos.get_mut(room_id) {
room.base_info.dm_target = Some(user_id.clone());
room.base_info.dm_targets.insert(user_id.clone());
} else if let Some(room) = self.store.get_room(room_id) {
let mut info = room.clone_info();
info.base_info.dm_target = Some(user_id.clone());
changes.add_room(info);
if info.base_info.dm_targets.insert(user_id.clone()) {
changes.add_room(info);
}
}
}
}

View File

@@ -1,7 +1,7 @@
mod members;
mod normal;
use std::cmp::max;
use std::{cmp::max, collections::HashSet};
pub use members::RoomMember;
pub use normal::{Room, RoomInfo, RoomType};
@@ -29,9 +29,9 @@ pub struct BaseRoomInfo {
pub(crate) canonical_alias: Option<OwnedRoomAliasId>,
/// The `m.room.create` event content of this room.
pub(crate) create: Option<RoomCreateEventContent>,
/// The user id this room is sharing the direct message with, if the room is
/// a direct message.
pub(crate) dm_target: Option<OwnedUserId>,
/// A list of user ids this room is considered as direct message, if this
/// room is a DM.
pub(crate) dm_targets: HashSet<OwnedUserId>,
/// The `m.room.encryption` event content that enabled E2EE in this room.
pub(crate) encryption: Option<RoomEncryptionEventContent>,
/// The guest access policy of this room.
@@ -183,7 +183,7 @@ impl Default for BaseRoomInfo {
avatar_url: None,
canonical_alias: None,
create: None,
dm_target: None,
dm_targets: Default::default(),
encryption: None,
guest_access: GuestAccess::Forbidden,
history_visibility: HistoryVisibility::WorldReadable,

View File

@@ -12,7 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use std::sync::{Arc, RwLock as SyncRwLock};
use std::{
collections::HashSet,
sync::{Arc, RwLock as SyncRwLock},
};
use dashmap::DashSet;
use futures_channel::mpsc;
@@ -184,17 +187,17 @@ impl Room {
/// Is this room considered a direct message.
pub fn is_direct(&self) -> bool {
self.inner.read().unwrap().base_info.dm_target.is_some()
!self.inner.read().unwrap().base_info.dm_targets.is_empty()
}
/// If this room is a direct message, get the member that we're sharing the
/// If this room is a direct message, get the members that we're sharing the
/// room with.
///
/// *Note*: The member list might have been modified in the meantime and
/// the target might not even be in the room anymore. This setting should
/// the targets might not even be in the room anymore. This setting should
/// only be considered as guidance.
pub fn direct_target(&self) -> Option<OwnedUserId> {
self.inner.read().unwrap().base_info.dm_target.clone()
pub fn direct_targets(&self) -> HashSet<OwnedUserId> {
self.inner.read().unwrap().base_info.dm_targets.clone()
}
/// Is the room encrypted.

View File

@@ -465,6 +465,15 @@ impl OtherUserIdentity {
let content = self.inner.verification_request_content(methods.clone()).await;
let room = if let Some(room) = self.direct_message_room.read().await.as_ref() {
// Make sure that the user, to be verified, is still in the room
if !room
.active_members()
.await?
.iter()
.any(|member| member.user_id() == self.inner.user_id())
{
room.invite_user_by_id(self.inner.user_id()).await?;
}
room.clone()
} else if let Some(room) =
self.client.create_dm_room(self.inner.user_id().to_owned()).await?

View File

@@ -404,11 +404,12 @@ impl Client {
#[cfg(feature = "encryption")]
fn get_dm_room(&self, user_id: &UserId) -> Option<room::Joined> {
let rooms = self.joined_rooms();
let room_pairs: Vec<_> =
rooms.iter().map(|r| (r.room_id().to_owned(), r.direct_target())).collect();
trace!(rooms = ?room_pairs, "Finding direct room");
let room = rooms.into_iter().find(|r| r.direct_target().as_deref() == Some(user_id));
// Find the room we share with the `user_id` and only with `user_id`
let room = rooms.into_iter().find(|r| {
let targets = r.direct_targets();
targets.len() == 1 && targets.contains(user_id)
});
trace!(?room, "Found room");
room

View File

@@ -1,4 +1,4 @@
use std::{ops::Deref, sync::Arc};
use std::{collections::BTreeMap, ops::Deref, sync::Arc};
use futures_core::stream::Stream;
use matrix_sdk_base::{
@@ -8,6 +8,7 @@ use matrix_sdk_base::{
use matrix_sdk_common::locks::Mutex;
use ruma::{
api::client::{
config::set_global_account_data,
filter::{LazyLoadOptions, RoomEventFilter},
membership::{get_member_events, join_room_by_id, leave_room},
message::get_message_events::{self, v3::Direction},
@@ -16,10 +17,11 @@ use ruma::{
},
assign,
events::{
direct::DirectEvent,
room::{history_visibility::HistoryVisibility, MediaSource},
tag::{TagInfo, TagName},
AnyRoomAccountDataEvent, AnyStateEvent, AnySyncStateEvent, RedactContent,
RedactedEventContent, RoomAccountDataEvent, RoomAccountDataEventContent,
AnyRoomAccountDataEvent, AnyStateEvent, AnySyncStateEvent, GlobalAccountDataEventType,
RedactContent, RedactedEventContent, RoomAccountDataEvent, RoomAccountDataEventContent,
RoomAccountDataEventType, StateEventContent, StateEventType, StaticEventContent,
SyncStateEvent,
},
@@ -30,7 +32,7 @@ use ruma::{
use crate::{
media::{MediaFormat, MediaRequest},
room::RoomType,
BaseRoom, Client, HttpError, HttpResult, Result, RoomMember,
BaseRoom, Client, Error, HttpError, HttpResult, Result, RoomMember,
};
/// A struct containing methods that are common for Joined, Invited and Left
@@ -817,6 +819,56 @@ impl Common {
let request = delete_tag::v3::Request::new(&user_id, self.inner.room_id(), tag.as_ref());
self.client.send(request, None).await
}
/// Sets whether this room is a DM.
///
/// When setting this room as DM, it will be marked as DM for all active
/// members of the room. When unsetting this room as DM, it will be
/// unmarked as DM for all users, not just the members.
///
/// # Arguments
/// * `is_direct` - Whether to mark this room as direct.
pub async fn set_is_direct(&self, is_direct: bool) -> Result<()> {
let user_id = self
.client
.user_id()
.await
.ok_or_else(|| Error::from(HttpError::AuthenticationRequired))?;
let mut content = self
.client
.store()
.get_account_data_event(GlobalAccountDataEventType::Direct)
.await?
.map(|e| e.deserialize_as::<DirectEvent>())
.transpose()?
.map(|e| e.content)
.unwrap_or_else(|| ruma::events::direct::DirectEventContent(BTreeMap::new()));
let this_room_id = self.inner.room_id();
if is_direct {
let room_members = self.active_members().await?;
for member in room_members {
let entry = content.entry(member.user_id().to_owned()).or_default();
if !entry.iter().any(|room_id| room_id == this_room_id) {
entry.push(this_room_id.to_owned());
}
}
} else {
for (_, list) in content.iter_mut() {
list.retain(|room_id| *room_id != this_room_id);
}
// Remove user ids that don't have any room marked as DM
content.retain(|_, list| !list.is_empty());
}
let request = set_global_account_data::v3::Request::new(&content, &user_id)?;
self.client.send(request, None).await?;
Ok(())
}
}
/// Options for [`messages`][Common::messages].