matrix_sdk_crypto/identities/
room_identity_state.rs

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