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    events::{
20        room::member::{MembershipState, SyncRoomMemberEvent},
21        SyncStateEvent,
22    },
23    OwnedUserId, UserId,
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
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: Box<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.deref() {
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    // `Box` the `SyncRoomMemberEvent` to reduce the size of this variant.
284    SyncRoomMemberEvent(Box<SyncRoomMemberEvent>),
285}
286
287/// What we know about the states of users in this room.
288/// Only stores users who _not_ in the Pinned stated.
289#[derive(Debug)]
290struct KnownStates {
291    known_states: HashMap<OwnedUserId, IdentityState>,
292}
293
294impl KnownStates {
295    fn from_identities(
296        member_identities: impl IntoIterator<Item = UserIdentity>,
297        room: &dyn RoomIdentityProvider,
298    ) -> Self {
299        let mut known_states = HashMap::new();
300        for user_identity in member_identities {
301            let state = room.state_of(&user_identity);
302            if state != IdentityState::Pinned {
303                known_states.insert(user_identity.user_id().to_owned(), state);
304            }
305        }
306        Self { known_states }
307    }
308
309    /// Return the known state of the supplied user, or IdentityState::Pinned if
310    /// we don't know.
311    fn get(&self, user_id: &UserId) -> IdentityState {
312        self.known_states.get(user_id).cloned().unwrap_or(IdentityState::Pinned)
313    }
314
315    /// Set the supplied user's state to the state given. If identity_state is
316    /// IdentityState::Pinned, forget this user.
317    fn set(&mut self, user_id: &UserId, identity_state: &IdentityState) {
318        if let IdentityState::Pinned = identity_state {
319            self.known_states.remove(user_id);
320        } else {
321            self.known_states.insert(user_id.to_owned(), identity_state.clone());
322        }
323    }
324}
325
326#[cfg(test)]
327mod tests {
328    use std::{
329        collections::HashMap,
330        sync::{Arc, Mutex},
331    };
332
333    use matrix_sdk_common::BoxFuture;
334    use matrix_sdk_test::{async_test, event_factory::EventFactory};
335    use ruma::{
336        device_id, events::room::member::MembershipState, owned_user_id, user_id, OwnedUserId,
337        UserId,
338    };
339
340    use super::{IdentityState, RoomIdentityChange, RoomIdentityProvider, RoomIdentityState};
341    use crate::{
342        identities::user::testing::own_identity_wrapped,
343        store::{types::IdentityUpdates, Store},
344        IdentityStatusChange, OtherUserIdentity, OtherUserIdentityData, OwnUserIdentityData,
345        UserIdentity,
346    };
347
348    #[async_test]
349    async fn test_unpinning_a_pinned_identity_in_the_room_notifies() {
350        // Given someone in the room is pinned
351        let user_id = user_id!("@u:s.co");
352        let mut room = FakeRoom::new();
353        room.member(other_user_identity(user_id).await, IdentityState::Pinned);
354        let mut state = RoomIdentityState::new(room.clone()).await;
355
356        // When their identity changes to unpinned
357        let updates =
358            identity_change(&mut room, user_id, IdentityState::PinViolation, false, false).await;
359        let update = state.process_change(updates).await;
360
361        // Then we emit an update saying they became unpinned
362        assert_eq!(
363            update,
364            vec![IdentityStatusChange {
365                user_id: user_id.to_owned(),
366                changed_to: IdentityState::PinViolation
367            }]
368        );
369    }
370
371    #[async_test]
372    async fn test_verifying_a_pinned_identity_in_the_room_notifies() {
373        // Given someone in the room is pinned
374        let user_id = user_id!("@u:s.co");
375        let mut room = FakeRoom::new();
376        room.member(other_user_identity(user_id).await, IdentityState::Pinned);
377        let mut state = RoomIdentityState::new(room.clone()).await;
378
379        // When their identity changes to verified
380        let updates =
381            identity_change(&mut room, user_id, IdentityState::Verified, false, false).await;
382        let update = state.process_change(updates).await;
383
384        // Then we emit an update
385        assert_eq!(
386            update,
387            vec![IdentityStatusChange {
388                user_id: user_id.to_owned(),
389                changed_to: IdentityState::Verified
390            }]
391        );
392    }
393
394    #[async_test]
395    async fn test_pinning_an_unpinned_identity_in_the_room_notifies() {
396        // Given someone in the room is unpinned
397        let user_id = user_id!("@u:s.co");
398        let mut room = FakeRoom::new();
399        room.member(other_user_identity(user_id).await, IdentityState::PinViolation);
400        let mut state = RoomIdentityState::new(room.clone()).await;
401
402        // When their identity changes to pinned
403        let updates =
404            identity_change(&mut room, user_id, IdentityState::Pinned, false, false).await;
405        let update = state.process_change(updates).await;
406
407        // Then we emit an update saying they became pinned
408        assert_eq!(
409            update,
410            vec![IdentityStatusChange {
411                user_id: user_id.to_owned(),
412                changed_to: IdentityState::Pinned
413            }]
414        );
415    }
416
417    #[async_test]
418    async fn test_unpinned_identity_becoming_verification_violating_in_the_room_notifies() {
419        // Given someone in the room is unpinned
420        let user_id = user_id!("@u:s.co");
421        let mut room = FakeRoom::new();
422        room.member(other_user_identity(user_id).await, IdentityState::PinViolation);
423        let mut state = RoomIdentityState::new(room.clone()).await;
424
425        // When their identity changes to verification violation
426        let updates =
427            identity_change(&mut room, user_id, IdentityState::VerificationViolation, false, false)
428                .await;
429        let update = state.process_change(updates).await;
430
431        // Then we emit an update saying they became verification violating
432        assert_eq!(
433            update,
434            vec![IdentityStatusChange {
435                user_id: user_id.to_owned(),
436                changed_to: IdentityState::VerificationViolation
437            }]
438        );
439    }
440
441    #[async_test]
442    async fn test_unpinning_an_identity_not_in_the_room_does_nothing() {
443        // Given an empty room
444        let user_id = user_id!("@u:s.co");
445        let mut room = FakeRoom::new();
446        let mut state = RoomIdentityState::new(room.clone()).await;
447
448        // When a new unpinned user identity appears but they are not in the 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            olm::PrivateCrossSigningIdentity,
1106            store::{CryptoStoreWrapper, MemoryStore},
1107            verification::VerificationMachine,
1108            Account,
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 =
1115            Arc::new(Mutex::new(PrivateCrossSigningIdentity::for_account(&account)));
1116
1117        let other_user_identity_data =
1118            OtherUserIdentityData::from_private(&*private_identity.lock().await).await;
1119
1120        UserIdentity::Other(OtherUserIdentity {
1121            inner: other_user_identity_data,
1122            own_identity: None,
1123            verification_machine: VerificationMachine::new(
1124                account.clone(),
1125                Arc::new(Mutex::new(PrivateCrossSigningIdentity::new(
1126                    account.user_id().to_owned(),
1127                ))),
1128                Arc::new(CryptoStoreWrapper::new(
1129                    account.user_id(),
1130                    account.device_id(),
1131                    MemoryStore::new(),
1132                )),
1133            ),
1134        })
1135    }
1136
1137    /// Create an own `UserIdentity` for use in tests
1138    async fn own_user_identity(user_id: &UserId) -> UserIdentity {
1139        use std::sync::Arc;
1140
1141        use ruma::owned_device_id;
1142        use tokio::sync::Mutex;
1143
1144        use crate::{
1145            olm::PrivateCrossSigningIdentity,
1146            store::{CryptoStoreWrapper, MemoryStore},
1147            verification::VerificationMachine,
1148            Account,
1149        };
1150
1151        let device_id = owned_device_id!("DEV123");
1152        let account = Account::with_device_id(user_id, &device_id);
1153
1154        let private_identity =
1155            Arc::new(Mutex::new(PrivateCrossSigningIdentity::for_account(&account)));
1156
1157        let own_user_identity_data =
1158            OwnUserIdentityData::from_private(&*private_identity.lock().await).await;
1159
1160        let cross_signing_identity = PrivateCrossSigningIdentity::new(account.user_id().to_owned());
1161        let verification_machine = VerificationMachine::new(
1162            account.clone(),
1163            Arc::new(Mutex::new(cross_signing_identity.clone())),
1164            Arc::new(CryptoStoreWrapper::new(
1165                account.user_id(),
1166                account.device_id(),
1167                MemoryStore::new(),
1168            )),
1169        );
1170
1171        UserIdentity::Own(own_identity_wrapped(
1172            own_user_identity_data,
1173            verification_machine.clone(),
1174            Store::new(
1175                account.static_data().clone(),
1176                Arc::new(Mutex::new(cross_signing_identity)),
1177                Arc::new(CryptoStoreWrapper::new(
1178                    user_id!("@u:s.co"),
1179                    device_id!("DEV7"),
1180                    MemoryStore::new(),
1181                )),
1182                verification_machine,
1183            ),
1184        ))
1185    }
1186}