matrix_sdk_crypto/identities/
room_identity_state.rs

1// Copyright 2024 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::collections::HashMap;
16
17use matrix_sdk_common::BoxFuture;
18use ruma::{
19    events::{
20        room::member::{MembershipState, SyncRoomMemberEvent},
21        SyncStateEvent,
22    },
23    OwnedUserId, UserId,
24};
25
26use super::UserIdentity;
27use crate::store::IdentityUpdates;
28
29/// Something that can answer questions about the membership of a room and the
30/// identities of users.
31///
32/// This is implemented by `matrix_sdk::Room` and is a trait here so we can
33/// supply a mock when needed.
34pub trait RoomIdentityProvider: core::fmt::Debug {
35    /// Is the user with the supplied ID a member of this room?
36    fn is_member<'a>(&'a self, user_id: &'a UserId) -> BoxFuture<'a, bool>;
37
38    /// Return a list of the [`UserIdentity`] of all members of this room
39    fn member_identities(&self) -> BoxFuture<'_, Vec<UserIdentity>>;
40
41    /// Return the [`UserIdentity`] of the user with the supplied ID (even if
42    /// they are not a member of this room) or None if this user does not
43    /// exist.
44    fn user_identity<'a>(&'a self, user_id: &'a UserId) -> BoxFuture<'a, Option<UserIdentity>>;
45
46    /// Return the [`IdentityState`] of the supplied user identity.
47    /// Normally only overridden in tests.
48    fn state_of(&self, user_identity: &UserIdentity) -> IdentityState {
49        if user_identity.is_verified() {
50            IdentityState::Verified
51        } else if user_identity.has_verification_violation() {
52            IdentityState::VerificationViolation
53        } else if let UserIdentity::Other(u) = user_identity {
54            if u.identity_needs_user_approval() {
55                IdentityState::PinViolation
56            } else {
57                IdentityState::Pinned
58            }
59        } else {
60            IdentityState::Pinned
61        }
62    }
63}
64
65/// The state of the identities in a given room - whether they are:
66///
67/// * in pin violation (the identity changed after we accepted their identity),
68/// * verified (we manually did the emoji dance),
69/// * previously verified (we did the emoji dance and then their identity
70///   changed),
71/// * otherwise, they are pinned.
72#[derive(Debug)]
73pub struct RoomIdentityState<R: RoomIdentityProvider> {
74    room: R,
75    known_states: KnownStates,
76}
77
78impl<R: RoomIdentityProvider> RoomIdentityState<R> {
79    /// Create a new RoomIdentityState using the provided room to check whether
80    /// users are members.
81    pub async fn new(room: R) -> Self {
82        let known_states = KnownStates::from_identities(room.member_identities().await, &room);
83        Self { room, known_states }
84    }
85
86    /// Provide the current state of the room: a list of all the non-pinned
87    /// identities and their status.
88    pub fn current_state(&self) -> Vec<IdentityStatusChange> {
89        self.known_states
90            .known_states
91            .iter()
92            .map(|(user_id, state)| IdentityStatusChange {
93                user_id: user_id.clone(),
94                changed_to: state.clone(),
95            })
96            .collect()
97    }
98
99    /// Deal with an incoming event - either someone's identity changed, or some
100    /// changes happened to a room's membership.
101    ///
102    /// Returns the changes (if any) to the list of valid/invalid identities in
103    /// the room.
104    pub async fn process_change(&mut self, item: RoomIdentityChange) -> Vec<IdentityStatusChange> {
105        match item {
106            RoomIdentityChange::IdentityUpdates(identity_updates) => {
107                self.process_identity_changes(identity_updates).await
108            }
109            RoomIdentityChange::SyncRoomMemberEvent(sync_room_member_event) => {
110                self.process_membership_change(sync_room_member_event).await
111            }
112        }
113    }
114
115    async fn process_identity_changes(
116        &mut self,
117        identity_updates: IdentityUpdates,
118    ) -> Vec<IdentityStatusChange> {
119        let mut ret = vec![];
120
121        for user_identity in identity_updates.new.values().chain(identity_updates.changed.values())
122        {
123            let user_id = user_identity.user_id();
124            if self.room.is_member(user_id).await {
125                let update = self.update_user_state(user_id, user_identity);
126                if let Some(identity_status_change) = update {
127                    ret.push(identity_status_change);
128                }
129            }
130        }
131
132        ret
133    }
134
135    async fn process_membership_change(
136        &mut self,
137        sync_room_member_event: SyncRoomMemberEvent,
138    ) -> Vec<IdentityStatusChange> {
139        // Ignore redacted events - memberships should come through as new events, not
140        // redactions.
141        if let SyncStateEvent::Original(event) = sync_room_member_event {
142            // Ignore invalid user IDs
143            let user_id: Result<&UserId, _> = event.state_key.as_str().try_into();
144            if let Ok(user_id) = user_id {
145                // Ignore non-existent users, and changes to our own identity
146                if let Some(user_identity @ UserIdentity::Other(_)) =
147                    self.room.user_identity(user_id).await
148                {
149                    // Don't notify on membership changes of verified or pinned identities
150                    if matches!(
151                        self.room.state_of(&user_identity),
152                        IdentityState::Verified | IdentityState::Pinned
153                    ) {
154                        return vec![];
155                    }
156
157                    match event.content.membership {
158                        MembershipState::Join | MembershipState::Invite => {
159                            // They are joining the room - check whether we need to display a
160                            // warning to the user
161                            if let Some(update) = self.update_user_state(user_id, &user_identity) {
162                                return vec![update];
163                            }
164                        }
165                        MembershipState::Leave | MembershipState::Ban => {
166                            // They are leaving the room - treat that as if they are becoming
167                            // Pinned, which means the UI will remove any banner it was displaying
168                            // for them.
169
170                            if let Some(update) =
171                                self.update_user_state_to(user_id, IdentityState::Pinned)
172                            {
173                                return vec![update];
174                            }
175                        }
176                        MembershipState::Knock => {
177                            // No need to do anything when someone is knocking
178                        }
179                        _ => {}
180                    }
181                }
182            }
183        }
184
185        // We didn't find a relevant update, so return an empty list
186        vec![]
187    }
188
189    fn update_user_state(
190        &mut self,
191        user_id: &UserId,
192        user_identity: &UserIdentity,
193    ) -> Option<IdentityStatusChange> {
194        if let UserIdentity::Other(_) = &user_identity {
195            self.update_user_state_to(user_id, self.room.state_of(user_identity))
196        } else {
197            // Ignore updates to our own identity
198            None
199        }
200    }
201
202    /// Updates our internal state for this user to the supplied `new_state`. If
203    /// the state changed it returns the change information we will surface to
204    /// the UI.
205    fn update_user_state_to(
206        &mut self,
207        user_id: &UserId,
208        new_state: IdentityState,
209    ) -> Option<IdentityStatusChange> {
210        let old_state = self.known_states.get(user_id);
211
212        if old_state == new_state {
213            return None;
214        }
215
216        Some(self.set_state(user_id, new_state))
217    }
218
219    fn set_state(&mut self, user_id: &UserId, new_state: IdentityState) -> IdentityStatusChange {
220        // Remember the new state of the user
221        self.known_states.set(user_id, &new_state);
222
223        // And return the update
224        IdentityStatusChange { user_id: user_id.to_owned(), changed_to: new_state }
225    }
226}
227
228/// A change in the status of the identity of a member of the room. Returned by
229/// [`RoomIdentityState::process_change`] to indicate that something significant
230/// changed in this room and we should either show or hide a warning.
231///
232/// Examples of "significant" changes:
233/// - pinned->unpinned
234/// - verification violation->verified
235///
236/// Examples of "insignificant" changes:
237/// - pinned->verified
238/// - verified->pinned
239#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
240pub struct IdentityStatusChange {
241    /// The user ID of the user whose identity status changed
242    pub user_id: OwnedUserId,
243
244    /// The new state of the identity of the user
245    pub changed_to: IdentityState,
246}
247
248/// The state of an identity - verified, pinned etc.
249#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
250#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
251pub enum IdentityState {
252    /// The user is verified with us
253    Verified,
254
255    /// Either this is the first identity we have seen for this user, or the
256    /// user has acknowledged a change of identity explicitly e.g. by
257    /// clicking OK on a notification.
258    Pinned,
259
260    /// The user's identity has changed since it was pinned. The user should be
261    /// notified about this and given the opportunity to acknowledge the
262    /// change, which will make the new identity pinned.
263    /// When the user acknowledges the change, the app should call
264    /// [`crate::OtherUserIdentity::pin_current_master_key`].
265    PinViolation,
266
267    /// The user's identity has changed, and before that it was verified. This
268    /// is a serious problem. The user can either verify again to make this
269    /// identity verified, or withdraw verification
270    /// [`UserIdentity::withdraw_verification`] to make it pinned.
271    VerificationViolation,
272}
273
274/// The type of update that can be received by
275/// [`RoomIdentityState::process_change`] - either a change of someone's
276/// identity, or a change of room membership.
277#[derive(Debug)]
278pub enum RoomIdentityChange {
279    /// Someone's identity changed
280    IdentityUpdates(IdentityUpdates),
281
282    /// Someone joined or left a room
283    SyncRoomMemberEvent(SyncRoomMemberEvent),
284}
285
286/// What we know about the states of users in this room.
287/// Only stores users who _not_ in the Pinned stated.
288#[derive(Debug)]
289struct KnownStates {
290    known_states: HashMap<OwnedUserId, IdentityState>,
291}
292
293impl KnownStates {
294    fn from_identities(
295        member_identities: impl IntoIterator<Item = UserIdentity>,
296        room: &dyn RoomIdentityProvider,
297    ) -> Self {
298        let mut known_states = HashMap::new();
299        for user_identity in member_identities {
300            let state = room.state_of(&user_identity);
301            if state != IdentityState::Pinned {
302                known_states.insert(user_identity.user_id().to_owned(), state);
303            }
304        }
305        Self { known_states }
306    }
307
308    /// Return the known state of the supplied user, or IdentityState::Pinned if
309    /// we don't know.
310    fn get(&self, user_id: &UserId) -> IdentityState {
311        self.known_states.get(user_id).cloned().unwrap_or(IdentityState::Pinned)
312    }
313
314    /// Set the supplied user's state to the state given. If identity_state is
315    /// IdentityState::Pinned, forget this user.
316    fn set(&mut self, user_id: &UserId, identity_state: &IdentityState) {
317        if let IdentityState::Pinned = identity_state {
318            self.known_states.remove(user_id);
319        } else {
320            self.known_states.insert(user_id.to_owned(), identity_state.clone());
321        }
322    }
323}
324
325#[cfg(test)]
326mod tests {
327    use std::{
328        collections::HashMap,
329        sync::{Arc, Mutex},
330    };
331
332    use matrix_sdk_common::BoxFuture;
333    use matrix_sdk_test::async_test;
334    use ruma::{
335        device_id,
336        events::{
337            room::member::{
338                MembershipState, RoomMemberEventContent, RoomMemberUnsigned, SyncRoomMemberEvent,
339            },
340            OriginalSyncStateEvent,
341        },
342        owned_event_id, owned_user_id, user_id, MilliSecondsSinceUnixEpoch, OwnedUserId, UInt,
343        UserId,
344    };
345
346    use super::{IdentityState, RoomIdentityChange, RoomIdentityProvider, RoomIdentityState};
347    use crate::{
348        identities::user::testing::own_identity_wrapped,
349        store::{IdentityUpdates, Store},
350        IdentityStatusChange, OtherUserIdentity, OtherUserIdentityData, OwnUserIdentityData,
351        UserIdentity,
352    };
353
354    #[async_test]
355    async fn test_unpinning_a_pinned_identity_in_the_room_notifies() {
356        // Given someone in the room is pinned
357        let user_id = user_id!("@u:s.co");
358        let mut room = FakeRoom::new();
359        room.member(other_user_identity(user_id).await, IdentityState::Pinned);
360        let mut state = RoomIdentityState::new(room.clone()).await;
361
362        // When their identity changes to unpinned
363        let updates =
364            identity_change(&mut room, user_id, IdentityState::PinViolation, false, false).await;
365        let update = state.process_change(updates).await;
366
367        // Then we emit an update saying they became unpinned
368        assert_eq!(
369            update,
370            vec![IdentityStatusChange {
371                user_id: user_id.to_owned(),
372                changed_to: IdentityState::PinViolation
373            }]
374        );
375    }
376
377    #[async_test]
378    async fn test_verifying_a_pinned_identity_in_the_room_notifies() {
379        // Given someone in the room is pinned
380        let user_id = user_id!("@u:s.co");
381        let mut room = FakeRoom::new();
382        room.member(other_user_identity(user_id).await, IdentityState::Pinned);
383        let mut state = RoomIdentityState::new(room.clone()).await;
384
385        // When their identity changes to verified
386        let updates =
387            identity_change(&mut room, user_id, IdentityState::Verified, false, false).await;
388        let update = state.process_change(updates).await;
389
390        // Then we emit an update
391        assert_eq!(
392            update,
393            vec![IdentityStatusChange {
394                user_id: user_id.to_owned(),
395                changed_to: IdentityState::Verified
396            }]
397        );
398    }
399
400    #[async_test]
401    async fn test_pinning_an_unpinned_identity_in_the_room_notifies() {
402        // Given someone in the room is unpinned
403        let user_id = user_id!("@u:s.co");
404        let mut room = FakeRoom::new();
405        room.member(other_user_identity(user_id).await, IdentityState::PinViolation);
406        let mut state = RoomIdentityState::new(room.clone()).await;
407
408        // When their identity changes to pinned
409        let updates =
410            identity_change(&mut room, user_id, IdentityState::Pinned, false, false).await;
411        let update = state.process_change(updates).await;
412
413        // Then we emit an update saying they became pinned
414        assert_eq!(
415            update,
416            vec![IdentityStatusChange {
417                user_id: user_id.to_owned(),
418                changed_to: IdentityState::Pinned
419            }]
420        );
421    }
422
423    #[async_test]
424    async fn test_unpinned_identity_becoming_verification_violating_in_the_room_notifies() {
425        // Given someone in the room is unpinned
426        let user_id = user_id!("@u:s.co");
427        let mut room = FakeRoom::new();
428        room.member(other_user_identity(user_id).await, IdentityState::PinViolation);
429        let mut state = RoomIdentityState::new(room.clone()).await;
430
431        // When their identity changes to verification violation
432        let updates =
433            identity_change(&mut room, user_id, IdentityState::VerificationViolation, false, false)
434                .await;
435        let update = state.process_change(updates).await;
436
437        // Then we emit an update saying they became verification violating
438        assert_eq!(
439            update,
440            vec![IdentityStatusChange {
441                user_id: user_id.to_owned(),
442                changed_to: IdentityState::VerificationViolation
443            }]
444        );
445    }
446
447    #[async_test]
448    async fn test_unpinning_an_identity_not_in_the_room_does_nothing() {
449        // Given an empty room
450        let user_id = user_id!("@u:s.co");
451        let mut room = FakeRoom::new();
452        let mut state = RoomIdentityState::new(room.clone()).await;
453
454        // When a new unpinned user identity appears but they are not in the room
455        let updates =
456            identity_change(&mut room, user_id, IdentityState::PinViolation, true, false).await;
457        let update = state.process_change(updates).await;
458
459        // Then we emit no update
460        assert_eq!(update, vec![]);
461    }
462
463    #[async_test]
464    async fn test_pinning_an_identity_not_in_the_room_does_nothing() {
465        // Given an empty room
466        let user_id = user_id!("@u:s.co");
467        let mut room = FakeRoom::new();
468        let mut state = RoomIdentityState::new(room.clone()).await;
469
470        // When a new pinned user appears but is not in the room
471        let updates = identity_change(&mut room, user_id, IdentityState::Pinned, true, false).await;
472        let update = state.process_change(updates).await;
473
474        // Then we emit no update
475        assert_eq!(update, []);
476    }
477
478    #[async_test]
479    async fn test_pinning_an_already_pinned_identity_in_the_room_does_nothing() {
480        // Given someone in the room is pinned
481        let user_id = user_id!("@u:s.co");
482        let mut room = FakeRoom::new();
483        room.member(other_user_identity(user_id).await, IdentityState::Pinned);
484        let mut state = RoomIdentityState::new(room.clone()).await;
485
486        // When we are told they are pinned
487        let updates =
488            identity_change(&mut room, user_id, IdentityState::Pinned, false, false).await;
489        let update = state.process_change(updates).await;
490
491        // Then we emit no update
492        assert_eq!(update, []);
493    }
494
495    #[async_test]
496    async fn test_unpinning_an_already_unpinned_identity_in_the_room_does_nothing() {
497        // Given someone in the room is unpinned
498        let user_id = user_id!("@u:s.co");
499        let mut room = FakeRoom::new();
500        room.member(other_user_identity(user_id).await, IdentityState::PinViolation);
501        let mut state = RoomIdentityState::new(room.clone()).await;
502
503        // When we are told they are unpinned
504        let updates =
505            identity_change(&mut room, user_id, IdentityState::PinViolation, false, false).await;
506        let update = state.process_change(updates).await;
507
508        // Then we emit no update
509        assert_eq!(update, []);
510    }
511
512    #[async_test]
513    async fn test_a_pinned_identity_joining_the_room_does_nothing() {
514        // Given an empty room and we know of a user who is pinned
515        let user_id = user_id!("@u:s.co");
516        let mut room = FakeRoom::new();
517        room.non_member(other_user_identity(user_id).await, IdentityState::Pinned);
518        let mut state = RoomIdentityState::new(room.clone()).await;
519
520        // When the pinned user joins the room
521        let updates = room_change(user_id, MembershipState::Join);
522        let update = state.process_change(updates).await;
523
524        // Then we emit no update because they are pinned
525        assert_eq!(update, []);
526    }
527
528    #[async_test]
529    async fn test_a_verified_identity_joining_the_room_does_nothing() {
530        // Given an empty room and we know of a user who is verified
531        let user_id = user_id!("@u:s.co");
532        let mut room = FakeRoom::new();
533        room.non_member(other_user_identity(user_id).await, IdentityState::Verified);
534        let mut state = RoomIdentityState::new(room).await;
535
536        // When the verified user joins the room
537        let updates = room_change(user_id, MembershipState::Join);
538        let update = state.process_change(updates).await;
539
540        // Then we emit no update because they are verified
541        assert_eq!(update, []);
542    }
543
544    #[async_test]
545    async fn test_an_unpinned_identity_joining_the_room_notifies() {
546        // Given an empty room and we know of a user who is unpinned
547        let user_id = user_id!("@u:s.co");
548        let mut room = FakeRoom::new();
549        room.non_member(other_user_identity(user_id).await, IdentityState::PinViolation);
550        let mut state = RoomIdentityState::new(room.clone()).await;
551
552        // When the unpinned user joins the room
553        let updates = room_change(user_id, MembershipState::Join);
554        let update = state.process_change(updates).await;
555
556        // Then we emit an update saying they became unpinned
557        assert_eq!(
558            update,
559            vec![IdentityStatusChange {
560                user_id: user_id.to_owned(),
561                changed_to: IdentityState::PinViolation
562            }]
563        );
564    }
565
566    #[async_test]
567    async fn test_a_pinned_identity_invited_to_the_room_does_nothing() {
568        // Given an empty room and we know of a user who is pinned
569        let user_id = user_id!("@u:s.co");
570        let mut room = FakeRoom::new();
571        room.non_member(other_user_identity(user_id).await, IdentityState::Pinned);
572        let mut state = RoomIdentityState::new(room.clone()).await;
573
574        // When the pinned user is invited to the room
575        let updates = room_change(user_id, MembershipState::Invite);
576        let update = state.process_change(updates).await;
577
578        // Then we emit no update because they are pinned
579        assert_eq!(update, []);
580    }
581
582    #[async_test]
583    async fn test_an_unpinned_identity_invited_to_the_room_notifies() {
584        // Given an empty room and we know of a user who is unpinned
585        let user_id = user_id!("@u:s.co");
586        let mut room = FakeRoom::new();
587        room.non_member(other_user_identity(user_id).await, IdentityState::PinViolation);
588        let mut state = RoomIdentityState::new(room.clone()).await;
589
590        // When the unpinned user is invited to the room
591        let updates = room_change(user_id, MembershipState::Invite);
592        let update = state.process_change(updates).await;
593
594        // Then we emit an update saying they became unpinned
595        assert_eq!(
596            update,
597            vec![IdentityStatusChange {
598                user_id: user_id.to_owned(),
599                changed_to: IdentityState::PinViolation
600            }]
601        );
602    }
603
604    #[async_test]
605    async fn test_a_verification_violating_identity_invited_to_the_room_notifies() {
606        // Given an empty room and we know of a user who is unpinned
607        let user_id = user_id!("@u:s.co");
608        let mut room = FakeRoom::new();
609        room.non_member(other_user_identity(user_id).await, IdentityState::VerificationViolation);
610        let mut state = RoomIdentityState::new(room).await;
611
612        // When the user is invited to the room
613        let updates = room_change(user_id, MembershipState::Invite);
614        let update = state.process_change(updates).await;
615
616        // Then we emit an update saying they became verification violation
617        assert_eq!(
618            update,
619            vec![IdentityStatusChange {
620                user_id: user_id.to_owned(),
621                changed_to: IdentityState::VerificationViolation
622            }]
623        );
624    }
625
626    #[async_test]
627    async fn test_own_identity_becoming_unpinned_is_ignored() {
628        // Given I am pinned
629        let user_id = user_id!("@u:s.co");
630        let mut room = FakeRoom::new();
631        room.member(own_user_identity(user_id).await, IdentityState::Pinned);
632        let mut state = RoomIdentityState::new(room.clone()).await;
633
634        // When I become unpinned
635        let updates =
636            identity_change(&mut room, user_id, IdentityState::PinViolation, false, true).await;
637        let update = state.process_change(updates).await;
638
639        // Then we do nothing because own identities are ignored
640        assert_eq!(update, vec![]);
641    }
642
643    #[async_test]
644    async fn test_own_identity_becoming_pinned_is_ignored() {
645        // Given I am unpinned
646        let user_id = user_id!("@u:s.co");
647        let mut room = FakeRoom::new();
648        room.member(own_user_identity(user_id).await, IdentityState::PinViolation);
649        let mut state = RoomIdentityState::new(room.clone()).await;
650
651        // When I become unpinned
652        let updates = identity_change(&mut room, user_id, IdentityState::Pinned, false, true).await;
653        let update = state.process_change(updates).await;
654
655        // Then we do nothing because own identities are ignored
656        assert_eq!(update, vec![]);
657    }
658
659    #[async_test]
660    async fn test_own_pinned_identity_joining_room_is_ignored() {
661        // Given an empty room and we know of a user who is pinned
662        let user_id = user_id!("@u:s.co");
663        let mut room = FakeRoom::new();
664        room.non_member(own_user_identity(user_id).await, IdentityState::Pinned);
665        let mut state = RoomIdentityState::new(room.clone()).await;
666
667        // When the pinned user joins the room
668        let updates = room_change(user_id, MembershipState::Join);
669        let update = state.process_change(updates).await;
670
671        // Then we emit no update because this is our own identity
672        assert_eq!(update, []);
673    }
674
675    #[async_test]
676    async fn test_own_unpinned_identity_joining_room_is_ignored() {
677        // Given an empty room and we know of a user who is unpinned
678        let user_id = user_id!("@u:s.co");
679        let mut room = FakeRoom::new();
680        room.non_member(own_user_identity(user_id).await, IdentityState::PinViolation);
681        let mut state = RoomIdentityState::new(room.clone()).await;
682
683        // When the unpinned user joins the room
684        let updates = room_change(user_id, MembershipState::Join);
685        let update = state.process_change(updates).await;
686
687        // Then we emit no update because this is our own identity
688        assert_eq!(update, vec![]);
689    }
690
691    #[async_test]
692    async fn test_a_pinned_identity_leaving_the_room_does_nothing() {
693        // Given a pinned user is in the room
694        let user_id = user_id!("@u:s.co");
695        let mut room = FakeRoom::new();
696        room.member(other_user_identity(user_id).await, IdentityState::Pinned);
697        let mut state = RoomIdentityState::new(room.clone()).await;
698
699        // When the pinned user leaves the room
700        let updates = room_change(user_id, MembershipState::Leave);
701        let update = state.process_change(updates).await;
702
703        // Then we emit no update because they are pinned
704        assert_eq!(update, []);
705    }
706
707    #[async_test]
708    async fn test_a_verified_identity_leaving_the_room_does_nothing() {
709        // Given a verified user is in the room
710        let user_id = user_id!("@u:s.co");
711        let mut room = FakeRoom::new();
712        room.member(other_user_identity(user_id).await, IdentityState::Verified);
713        let mut state = RoomIdentityState::new(room).await;
714
715        // When the user leaves the room
716        let updates = room_change(user_id, MembershipState::Leave);
717        let update = state.process_change(updates).await;
718
719        // Then we emit no update because they are verified
720        assert_eq!(update, []);
721    }
722
723    #[async_test]
724    async fn test_an_unpinned_identity_leaving_the_room_notifies() {
725        // Given an unpinned user is in the room
726        let user_id = user_id!("@u:s.co");
727        let mut room = FakeRoom::new();
728        room.member(other_user_identity(user_id).await, IdentityState::PinViolation);
729        let mut state = RoomIdentityState::new(room.clone()).await;
730
731        // When the unpinned user leaves the room
732        let updates = room_change(user_id, MembershipState::Leave);
733        let update = state.process_change(updates).await;
734
735        // Then we emit an update saying they became pinned
736        assert_eq!(
737            update,
738            vec![IdentityStatusChange {
739                user_id: user_id.to_owned(),
740                changed_to: IdentityState::Pinned
741            }]
742        );
743    }
744
745    #[async_test]
746    async fn test_a_verification_violating_identity_leaving_the_room_notifies() {
747        // Given an unpinned user is in the room
748        let user_id = user_id!("@u:s.co");
749        let mut room = FakeRoom::new();
750        room.member(other_user_identity(user_id).await, IdentityState::VerificationViolation);
751        let mut state = RoomIdentityState::new(room).await;
752
753        // When the user leaves the room
754        let updates = room_change(user_id, MembershipState::Leave);
755        let update = state.process_change(updates).await;
756
757        // Then we emit an update saying they became pinned
758        assert_eq!(
759            update,
760            vec![IdentityStatusChange {
761                user_id: user_id.to_owned(),
762                changed_to: IdentityState::Pinned
763            }]
764        );
765    }
766
767    #[async_test]
768    async fn test_a_pinned_identity_being_banned_does_nothing() {
769        // Given a pinned user is in the room
770        let user_id = user_id!("@u:s.co");
771        let mut room = FakeRoom::new();
772        room.member(other_user_identity(user_id).await, IdentityState::Pinned);
773        let mut state = RoomIdentityState::new(room.clone()).await;
774
775        // When the pinned user is banned
776        let updates = room_change(user_id, MembershipState::Ban);
777        let update = state.process_change(updates).await;
778
779        // Then we emit no update because they are pinned
780        assert_eq!(update, []);
781    }
782
783    #[async_test]
784    async fn test_an_unpinned_identity_being_banned_notifies() {
785        // Given an unpinned user is in the room
786        let user_id = user_id!("@u:s.co");
787        let mut room = FakeRoom::new();
788        room.member(other_user_identity(user_id).await, IdentityState::PinViolation);
789        let mut state = RoomIdentityState::new(room.clone()).await;
790
791        // When the unpinned user is banned
792        let updates = room_change(user_id, MembershipState::Ban);
793        let update = state.process_change(updates).await;
794
795        // Then we emit an update saying they became unpinned
796        assert_eq!(
797            update,
798            vec![IdentityStatusChange {
799                user_id: user_id.to_owned(),
800                changed_to: IdentityState::Pinned
801            }]
802        );
803    }
804
805    #[async_test]
806    async fn test_multiple_simultaneous_identity_updates_are_all_notified() {
807        // Given several people in the room with different states
808        let user1 = user_id!("@u1:s.co");
809        let user2 = user_id!("@u2:s.co");
810        let user3 = user_id!("@u3:s.co");
811        let mut room = FakeRoom::new();
812        room.member(other_user_identity(user1).await, IdentityState::Pinned);
813        room.member(other_user_identity(user2).await, IdentityState::PinViolation);
814        room.member(other_user_identity(user3).await, IdentityState::Pinned);
815        let mut state = RoomIdentityState::new(room.clone()).await;
816
817        // When they all change state simultaneously
818        let updates = identity_changes(
819            &mut room,
820            &[
821                IdentityChangeSpec {
822                    user_id: user1.to_owned(),
823                    changed_to: IdentityState::PinViolation,
824                    new: false,
825                    own: false,
826                },
827                IdentityChangeSpec {
828                    user_id: user2.to_owned(),
829                    changed_to: IdentityState::Pinned,
830                    new: false,
831                    own: false,
832                },
833                IdentityChangeSpec {
834                    user_id: user3.to_owned(),
835                    changed_to: IdentityState::PinViolation,
836                    new: false,
837                    own: false,
838                },
839            ],
840        )
841        .await;
842        let update = state.process_change(updates).await;
843
844        // Then we emit updates for each of them
845        assert_eq!(
846            update,
847            vec![
848                IdentityStatusChange {
849                    user_id: user1.to_owned(),
850                    changed_to: IdentityState::PinViolation
851                },
852                IdentityStatusChange {
853                    user_id: user2.to_owned(),
854                    changed_to: IdentityState::Pinned
855                },
856                IdentityStatusChange {
857                    user_id: user3.to_owned(),
858                    changed_to: IdentityState::PinViolation
859                }
860            ]
861        );
862    }
863
864    #[async_test]
865    async fn test_multiple_changes_are_notified() {
866        // Given someone in the room is pinned
867        let user_id = user_id!("@u:s.co");
868        let mut room = FakeRoom::new();
869        room.member(other_user_identity(user_id).await, IdentityState::Pinned);
870        let mut state = RoomIdentityState::new(room.clone()).await;
871
872        // When they change state multiple times
873        let update1 = state
874            .process_change(
875                identity_change(&mut room, user_id, IdentityState::PinViolation, false, false)
876                    .await,
877            )
878            .await;
879        let update2 = state
880            .process_change(
881                identity_change(&mut room, user_id, IdentityState::PinViolation, false, false)
882                    .await,
883            )
884            .await;
885        let update3 = state
886            .process_change(
887                identity_change(&mut room, user_id, IdentityState::Pinned, false, false).await,
888            )
889            .await;
890        let update4 = state
891            .process_change(
892                identity_change(&mut room, user_id, IdentityState::PinViolation, false, false)
893                    .await,
894            )
895            .await;
896
897        // Then we emit updates each time
898        assert_eq!(
899            update1,
900            vec![IdentityStatusChange {
901                user_id: user_id.to_owned(),
902                changed_to: IdentityState::PinViolation
903            }]
904        );
905        // (Except update2 where nothing changed)
906        assert_eq!(update2, vec![]);
907        assert_eq!(
908            update3,
909            vec![IdentityStatusChange {
910                user_id: user_id.to_owned(),
911                changed_to: IdentityState::Pinned
912            }]
913        );
914        assert_eq!(
915            update4,
916            vec![IdentityStatusChange {
917                user_id: user_id.to_owned(),
918                changed_to: IdentityState::PinViolation
919            }]
920        );
921    }
922
923    #[async_test]
924    async fn test_current_state_of_all_pinned_room_is_empty() {
925        // Given everyone in the room is pinned
926        let user1 = user_id!("@u1:s.co");
927        let user2 = user_id!("@u2:s.co");
928        let mut room = FakeRoom::new();
929        room.member(other_user_identity(user1).await, IdentityState::Pinned);
930        room.member(other_user_identity(user2).await, IdentityState::Pinned);
931        let state = RoomIdentityState::new(room).await;
932        assert!(state.current_state().is_empty());
933    }
934
935    #[async_test]
936    async fn test_current_state_contains_all_nonpinned_users() {
937        // Given some people are unpinned
938        let user1 = user_id!("@u1:s.co");
939        let user2 = user_id!("@u2:s.co");
940        let user3 = user_id!("@u3:s.co");
941        let user4 = user_id!("@u4:s.co");
942        let user5 = user_id!("@u5:s.co");
943        let user6 = user_id!("@u6:s.co");
944        let mut room = FakeRoom::new();
945        room.member(other_user_identity(user1).await, IdentityState::Pinned);
946        room.member(other_user_identity(user2).await, IdentityState::PinViolation);
947        room.member(other_user_identity(user3).await, IdentityState::Pinned);
948        room.member(other_user_identity(user4).await, IdentityState::PinViolation);
949        room.member(other_user_identity(user5).await, IdentityState::Verified);
950        room.member(other_user_identity(user6).await, IdentityState::VerificationViolation);
951        let mut state = RoomIdentityState::new(room).await.current_state();
952        state.sort_by_key(|change| change.user_id.to_owned());
953        assert_eq!(
954            state,
955            vec![
956                IdentityStatusChange {
957                    user_id: owned_user_id!("@u2:s.co"),
958                    changed_to: IdentityState::PinViolation
959                },
960                IdentityStatusChange {
961                    user_id: owned_user_id!("@u4:s.co"),
962                    changed_to: IdentityState::PinViolation
963                },
964                IdentityStatusChange {
965                    user_id: owned_user_id!("@u5:s.co"),
966                    changed_to: IdentityState::Verified
967                },
968                IdentityStatusChange {
969                    user_id: owned_user_id!("@u6:s.co"),
970                    changed_to: IdentityState::VerificationViolation
971                }
972            ]
973        );
974    }
975
976    #[derive(Debug)]
977    struct Membership {
978        is_member: bool,
979        user_identity: UserIdentity,
980        identity_state: IdentityState,
981    }
982
983    #[derive(Clone, Debug)]
984    struct FakeRoom {
985        users: Arc<Mutex<HashMap<OwnedUserId, Membership>>>,
986    }
987
988    impl FakeRoom {
989        fn new() -> Self {
990            Self { users: Default::default() }
991        }
992
993        fn member(&mut self, user_identity: UserIdentity, identity_state: IdentityState) {
994            self.users.lock().unwrap().insert(
995                user_identity.user_id().to_owned(),
996                Membership { is_member: true, user_identity, identity_state },
997            );
998        }
999
1000        fn non_member(&mut self, user_identity: UserIdentity, identity_state: IdentityState) {
1001            self.users.lock().unwrap().insert(
1002                user_identity.user_id().to_owned(),
1003                Membership { is_member: false, user_identity, identity_state },
1004            );
1005        }
1006
1007        fn update_state(&self, user_id: &UserId, changed_to: &IdentityState) {
1008            self.users
1009                .lock()
1010                .unwrap()
1011                .entry(user_id.to_owned())
1012                .and_modify(|m| m.identity_state = changed_to.clone());
1013        }
1014    }
1015
1016    impl RoomIdentityProvider for FakeRoom {
1017        fn is_member<'a>(&'a self, user_id: &'a UserId) -> BoxFuture<'a, bool> {
1018            Box::pin(async {
1019                self.users.lock().unwrap().get(user_id).map(|m| m.is_member).unwrap_or(false)
1020            })
1021        }
1022
1023        fn member_identities(&self) -> BoxFuture<'_, Vec<UserIdentity>> {
1024            Box::pin(async {
1025                self.users
1026                    .lock()
1027                    .unwrap()
1028                    .values()
1029                    .filter_map(|m| if m.is_member { Some(m.user_identity.clone()) } else { None })
1030                    .collect()
1031            })
1032        }
1033
1034        fn user_identity<'a>(&'a self, user_id: &'a UserId) -> BoxFuture<'a, Option<UserIdentity>> {
1035            Box::pin(async {
1036                self.users.lock().unwrap().get(user_id).map(|m| m.user_identity.clone())
1037            })
1038        }
1039
1040        fn state_of(&self, user_identity: &UserIdentity) -> IdentityState {
1041            self.users
1042                .lock()
1043                .unwrap()
1044                .get(user_identity.user_id())
1045                .map(|m| m.identity_state.clone())
1046                .unwrap_or(IdentityState::Pinned)
1047        }
1048    }
1049
1050    fn room_change(user_id: &UserId, new_state: MembershipState) -> RoomIdentityChange {
1051        let event = SyncRoomMemberEvent::Original(OriginalSyncStateEvent {
1052            content: RoomMemberEventContent::new(new_state),
1053            event_id: owned_event_id!("$1"),
1054            sender: owned_user_id!("@admin:b.c"),
1055            origin_server_ts: MilliSecondsSinceUnixEpoch(UInt::new(2123).unwrap()),
1056            unsigned: RoomMemberUnsigned::new(),
1057            state_key: user_id.to_owned(),
1058        });
1059        RoomIdentityChange::SyncRoomMemberEvent(event)
1060    }
1061
1062    async fn identity_change(
1063        room: &mut FakeRoom,
1064        user_id: &UserId,
1065        changed_to: IdentityState,
1066        new: bool,
1067        own: bool,
1068    ) -> RoomIdentityChange {
1069        identity_changes(
1070            room,
1071            &[IdentityChangeSpec { user_id: user_id.to_owned(), changed_to, new, own }],
1072        )
1073        .await
1074    }
1075
1076    struct IdentityChangeSpec {
1077        user_id: OwnedUserId,
1078        changed_to: IdentityState,
1079        new: bool,
1080        own: bool,
1081    }
1082
1083    async fn identity_changes(
1084        room: &mut FakeRoom,
1085        changes: &[IdentityChangeSpec],
1086    ) -> RoomIdentityChange {
1087        let mut updates = IdentityUpdates::default();
1088
1089        for change in changes {
1090            let user_identity = if change.own {
1091                own_user_identity(&change.user_id).await
1092            } else {
1093                other_user_identity(&change.user_id).await
1094            };
1095
1096            room.update_state(user_identity.user_id(), &change.changed_to);
1097            if change.new {
1098                updates.new.insert(user_identity.user_id().to_owned(), user_identity);
1099            } else {
1100                updates.changed.insert(user_identity.user_id().to_owned(), user_identity);
1101            }
1102        }
1103        RoomIdentityChange::IdentityUpdates(updates)
1104    }
1105
1106    /// Create an other `UserIdentity` for use in tests
1107    async fn other_user_identity(user_id: &UserId) -> UserIdentity {
1108        use std::sync::Arc;
1109
1110        use ruma::owned_device_id;
1111        use tokio::sync::Mutex;
1112
1113        use crate::{
1114            olm::PrivateCrossSigningIdentity,
1115            store::{CryptoStoreWrapper, MemoryStore},
1116            verification::VerificationMachine,
1117            Account,
1118        };
1119
1120        let device_id = owned_device_id!("DEV123");
1121        let account = Account::with_device_id(user_id, &device_id);
1122
1123        let private_identity =
1124            Arc::new(Mutex::new(PrivateCrossSigningIdentity::with_account(&account).await.0));
1125
1126        let other_user_identity_data =
1127            OtherUserIdentityData::from_private(&*private_identity.lock().await).await;
1128
1129        UserIdentity::Other(OtherUserIdentity {
1130            inner: other_user_identity_data,
1131            own_identity: None,
1132            verification_machine: VerificationMachine::new(
1133                account.clone(),
1134                Arc::new(Mutex::new(PrivateCrossSigningIdentity::new(
1135                    account.user_id().to_owned(),
1136                ))),
1137                Arc::new(CryptoStoreWrapper::new(
1138                    account.user_id(),
1139                    account.device_id(),
1140                    MemoryStore::new(),
1141                )),
1142            ),
1143        })
1144    }
1145
1146    /// Create an own `UserIdentity` for use in tests
1147    async fn own_user_identity(user_id: &UserId) -> UserIdentity {
1148        use std::sync::Arc;
1149
1150        use ruma::owned_device_id;
1151        use tokio::sync::Mutex;
1152
1153        use crate::{
1154            olm::PrivateCrossSigningIdentity,
1155            store::{CryptoStoreWrapper, MemoryStore},
1156            verification::VerificationMachine,
1157            Account,
1158        };
1159
1160        let device_id = owned_device_id!("DEV123");
1161        let account = Account::with_device_id(user_id, &device_id);
1162
1163        let private_identity =
1164            Arc::new(Mutex::new(PrivateCrossSigningIdentity::with_account(&account).await.0));
1165
1166        let own_user_identity_data =
1167            OwnUserIdentityData::from_private(&*private_identity.lock().await).await;
1168
1169        let cross_signing_identity = PrivateCrossSigningIdentity::new(account.user_id().to_owned());
1170        let verification_machine = VerificationMachine::new(
1171            account.clone(),
1172            Arc::new(Mutex::new(cross_signing_identity.clone())),
1173            Arc::new(CryptoStoreWrapper::new(
1174                account.user_id(),
1175                account.device_id(),
1176                MemoryStore::new(),
1177            )),
1178        );
1179
1180        UserIdentity::Own(own_identity_wrapped(
1181            own_user_identity_data,
1182            verification_machine.clone(),
1183            Store::new(
1184                account.static_data().clone(),
1185                Arc::new(Mutex::new(cross_signing_identity)),
1186                Arc::new(CryptoStoreWrapper::new(
1187                    user_id!("@u:s.co"),
1188                    device_id!("DEV7"),
1189                    MemoryStore::new(),
1190                )),
1191                verification_machine,
1192            ),
1193        ))
1194    }
1195}