Skip to main content

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