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::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;
335    use ruma::{
336        device_id,
337        events::{
338            room::member::{
339                MembershipState, RoomMemberEventContent, RoomMemberUnsigned, SyncRoomMemberEvent,
340            },
341            OriginalSyncStateEvent,
342        },
343        owned_event_id, owned_user_id, user_id, MilliSecondsSinceUnixEpoch, OwnedUserId, UInt,
344        UserId,
345    };
346
347    use super::{IdentityState, RoomIdentityChange, RoomIdentityProvider, RoomIdentityState};
348    use crate::{
349        identities::user::testing::own_identity_wrapped,
350        store::{IdentityUpdates, Store},
351        IdentityStatusChange, OtherUserIdentity, OtherUserIdentityData, OwnUserIdentityData,
352        UserIdentity,
353    };
354
355    #[async_test]
356    async fn test_unpinning_a_pinned_identity_in_the_room_notifies() {
357        // Given someone in the room is pinned
358        let user_id = user_id!("@u:s.co");
359        let mut room = FakeRoom::new();
360        room.member(other_user_identity(user_id).await, IdentityState::Pinned);
361        let mut state = RoomIdentityState::new(room.clone()).await;
362
363        // When their identity changes to unpinned
364        let updates =
365            identity_change(&mut room, user_id, IdentityState::PinViolation, false, false).await;
366        let update = state.process_change(updates).await;
367
368        // Then we emit an update saying they became unpinned
369        assert_eq!(
370            update,
371            vec![IdentityStatusChange {
372                user_id: user_id.to_owned(),
373                changed_to: IdentityState::PinViolation
374            }]
375        );
376    }
377
378    #[async_test]
379    async fn test_verifying_a_pinned_identity_in_the_room_notifies() {
380        // Given someone in the room is pinned
381        let user_id = user_id!("@u:s.co");
382        let mut room = FakeRoom::new();
383        room.member(other_user_identity(user_id).await, IdentityState::Pinned);
384        let mut state = RoomIdentityState::new(room.clone()).await;
385
386        // When their identity changes to verified
387        let updates =
388            identity_change(&mut room, user_id, IdentityState::Verified, false, false).await;
389        let update = state.process_change(updates).await;
390
391        // Then we emit an update
392        assert_eq!(
393            update,
394            vec![IdentityStatusChange {
395                user_id: user_id.to_owned(),
396                changed_to: IdentityState::Verified
397            }]
398        );
399    }
400
401    #[async_test]
402    async fn test_pinning_an_unpinned_identity_in_the_room_notifies() {
403        // Given someone in the room is unpinned
404        let user_id = user_id!("@u:s.co");
405        let mut room = FakeRoom::new();
406        room.member(other_user_identity(user_id).await, IdentityState::PinViolation);
407        let mut state = RoomIdentityState::new(room.clone()).await;
408
409        // When their identity changes to pinned
410        let updates =
411            identity_change(&mut room, user_id, IdentityState::Pinned, false, false).await;
412        let update = state.process_change(updates).await;
413
414        // Then we emit an update saying they became pinned
415        assert_eq!(
416            update,
417            vec![IdentityStatusChange {
418                user_id: user_id.to_owned(),
419                changed_to: IdentityState::Pinned
420            }]
421        );
422    }
423
424    #[async_test]
425    async fn test_unpinned_identity_becoming_verification_violating_in_the_room_notifies() {
426        // Given someone in the room is unpinned
427        let user_id = user_id!("@u:s.co");
428        let mut room = FakeRoom::new();
429        room.member(other_user_identity(user_id).await, IdentityState::PinViolation);
430        let mut state = RoomIdentityState::new(room.clone()).await;
431
432        // When their identity changes to verification violation
433        let updates =
434            identity_change(&mut room, user_id, IdentityState::VerificationViolation, false, false)
435                .await;
436        let update = state.process_change(updates).await;
437
438        // Then we emit an update saying they became verification violating
439        assert_eq!(
440            update,
441            vec![IdentityStatusChange {
442                user_id: user_id.to_owned(),
443                changed_to: IdentityState::VerificationViolation
444            }]
445        );
446    }
447
448    #[async_test]
449    async fn test_unpinning_an_identity_not_in_the_room_does_nothing() {
450        // Given an empty room
451        let user_id = user_id!("@u:s.co");
452        let mut room = FakeRoom::new();
453        let mut state = RoomIdentityState::new(room.clone()).await;
454
455        // When a new unpinned user identity appears but they are not in the room
456        let updates =
457            identity_change(&mut room, user_id, IdentityState::PinViolation, true, false).await;
458        let update = state.process_change(updates).await;
459
460        // Then we emit no update
461        assert_eq!(update, vec![]);
462    }
463
464    #[async_test]
465    async fn test_pinning_an_identity_not_in_the_room_does_nothing() {
466        // Given an empty room
467        let user_id = user_id!("@u:s.co");
468        let mut room = FakeRoom::new();
469        let mut state = RoomIdentityState::new(room.clone()).await;
470
471        // When a new pinned user appears but is not in the room
472        let updates = identity_change(&mut room, user_id, IdentityState::Pinned, true, false).await;
473        let update = state.process_change(updates).await;
474
475        // Then we emit no update
476        assert_eq!(update, []);
477    }
478
479    #[async_test]
480    async fn test_pinning_an_already_pinned_identity_in_the_room_does_nothing() {
481        // Given someone in the room is pinned
482        let user_id = user_id!("@u:s.co");
483        let mut room = FakeRoom::new();
484        room.member(other_user_identity(user_id).await, IdentityState::Pinned);
485        let mut state = RoomIdentityState::new(room.clone()).await;
486
487        // When we are told they are pinned
488        let updates =
489            identity_change(&mut room, user_id, IdentityState::Pinned, false, false).await;
490        let update = state.process_change(updates).await;
491
492        // Then we emit no update
493        assert_eq!(update, []);
494    }
495
496    #[async_test]
497    async fn test_unpinning_an_already_unpinned_identity_in_the_room_does_nothing() {
498        // Given someone in the room is unpinned
499        let user_id = user_id!("@u:s.co");
500        let mut room = FakeRoom::new();
501        room.member(other_user_identity(user_id).await, IdentityState::PinViolation);
502        let mut state = RoomIdentityState::new(room.clone()).await;
503
504        // When we are told they are unpinned
505        let updates =
506            identity_change(&mut room, user_id, IdentityState::PinViolation, false, false).await;
507        let update = state.process_change(updates).await;
508
509        // Then we emit no update
510        assert_eq!(update, []);
511    }
512
513    #[async_test]
514    async fn test_a_pinned_identity_joining_the_room_does_nothing() {
515        // Given an empty room and we know of a user who is pinned
516        let user_id = user_id!("@u:s.co");
517        let mut room = FakeRoom::new();
518        room.non_member(other_user_identity(user_id).await, IdentityState::Pinned);
519        let mut state = RoomIdentityState::new(room.clone()).await;
520
521        // When the pinned user joins the room
522        let updates = room_change(user_id, MembershipState::Join);
523        let update = state.process_change(updates).await;
524
525        // Then we emit no update because they are pinned
526        assert_eq!(update, []);
527    }
528
529    #[async_test]
530    async fn test_a_verified_identity_joining_the_room_does_nothing() {
531        // Given an empty room and we know of a user who is verified
532        let user_id = user_id!("@u:s.co");
533        let mut room = FakeRoom::new();
534        room.non_member(other_user_identity(user_id).await, IdentityState::Verified);
535        let mut state = RoomIdentityState::new(room).await;
536
537        // When the verified user joins the room
538        let updates = room_change(user_id, MembershipState::Join);
539        let update = state.process_change(updates).await;
540
541        // Then we emit no update because they are verified
542        assert_eq!(update, []);
543    }
544
545    #[async_test]
546    async fn test_an_unpinned_identity_joining_the_room_notifies() {
547        // Given an empty room and we know of a user who is unpinned
548        let user_id = user_id!("@u:s.co");
549        let mut room = FakeRoom::new();
550        room.non_member(other_user_identity(user_id).await, IdentityState::PinViolation);
551        let mut state = RoomIdentityState::new(room.clone()).await;
552
553        // When the unpinned user joins the room
554        let updates = room_change(user_id, MembershipState::Join);
555        let update = state.process_change(updates).await;
556
557        // Then we emit an update saying they became unpinned
558        assert_eq!(
559            update,
560            vec![IdentityStatusChange {
561                user_id: user_id.to_owned(),
562                changed_to: IdentityState::PinViolation
563            }]
564        );
565    }
566
567    #[async_test]
568    async fn test_a_pinned_identity_invited_to_the_room_does_nothing() {
569        // Given an empty room and we know of a user who is pinned
570        let user_id = user_id!("@u:s.co");
571        let mut room = FakeRoom::new();
572        room.non_member(other_user_identity(user_id).await, IdentityState::Pinned);
573        let mut state = RoomIdentityState::new(room.clone()).await;
574
575        // When the pinned user is invited to the room
576        let updates = room_change(user_id, MembershipState::Invite);
577        let update = state.process_change(updates).await;
578
579        // Then we emit no update because they are pinned
580        assert_eq!(update, []);
581    }
582
583    #[async_test]
584    async fn test_an_unpinned_identity_invited_to_the_room_notifies() {
585        // Given an empty room and we know of a user who is unpinned
586        let user_id = user_id!("@u:s.co");
587        let mut room = FakeRoom::new();
588        room.non_member(other_user_identity(user_id).await, IdentityState::PinViolation);
589        let mut state = RoomIdentityState::new(room.clone()).await;
590
591        // When the unpinned user is invited to the room
592        let updates = room_change(user_id, MembershipState::Invite);
593        let update = state.process_change(updates).await;
594
595        // Then we emit an update saying they became unpinned
596        assert_eq!(
597            update,
598            vec![IdentityStatusChange {
599                user_id: user_id.to_owned(),
600                changed_to: IdentityState::PinViolation
601            }]
602        );
603    }
604
605    #[async_test]
606    async fn test_a_verification_violating_identity_invited_to_the_room_notifies() {
607        // Given an empty room and we know of a user who is unpinned
608        let user_id = user_id!("@u:s.co");
609        let mut room = FakeRoom::new();
610        room.non_member(other_user_identity(user_id).await, IdentityState::VerificationViolation);
611        let mut state = RoomIdentityState::new(room).await;
612
613        // When the user is invited to the room
614        let updates = room_change(user_id, MembershipState::Invite);
615        let update = state.process_change(updates).await;
616
617        // Then we emit an update saying they became verification violation
618        assert_eq!(
619            update,
620            vec![IdentityStatusChange {
621                user_id: user_id.to_owned(),
622                changed_to: IdentityState::VerificationViolation
623            }]
624        );
625    }
626
627    #[async_test]
628    async fn test_own_identity_becoming_unpinned_is_ignored() {
629        // Given I am pinned
630        let user_id = user_id!("@u:s.co");
631        let mut room = FakeRoom::new();
632        room.member(own_user_identity(user_id).await, IdentityState::Pinned);
633        let mut state = RoomIdentityState::new(room.clone()).await;
634
635        // When I become unpinned
636        let updates =
637            identity_change(&mut room, user_id, IdentityState::PinViolation, false, true).await;
638        let update = state.process_change(updates).await;
639
640        // Then we do nothing because own identities are ignored
641        assert_eq!(update, vec![]);
642    }
643
644    #[async_test]
645    async fn test_own_identity_becoming_pinned_is_ignored() {
646        // Given I am unpinned
647        let user_id = user_id!("@u:s.co");
648        let mut room = FakeRoom::new();
649        room.member(own_user_identity(user_id).await, IdentityState::PinViolation);
650        let mut state = RoomIdentityState::new(room.clone()).await;
651
652        // When I become unpinned
653        let updates = identity_change(&mut room, user_id, IdentityState::Pinned, false, true).await;
654        let update = state.process_change(updates).await;
655
656        // Then we do nothing because own identities are ignored
657        assert_eq!(update, vec![]);
658    }
659
660    #[async_test]
661    async fn test_own_pinned_identity_joining_room_is_ignored() {
662        // Given an empty room and we know of a user who is pinned
663        let user_id = user_id!("@u:s.co");
664        let mut room = FakeRoom::new();
665        room.non_member(own_user_identity(user_id).await, IdentityState::Pinned);
666        let mut state = RoomIdentityState::new(room.clone()).await;
667
668        // When the pinned user joins the room
669        let updates = room_change(user_id, MembershipState::Join);
670        let update = state.process_change(updates).await;
671
672        // Then we emit no update because this is our own identity
673        assert_eq!(update, []);
674    }
675
676    #[async_test]
677    async fn test_own_unpinned_identity_joining_room_is_ignored() {
678        // Given an empty room and we know of a user who is unpinned
679        let user_id = user_id!("@u:s.co");
680        let mut room = FakeRoom::new();
681        room.non_member(own_user_identity(user_id).await, IdentityState::PinViolation);
682        let mut state = RoomIdentityState::new(room.clone()).await;
683
684        // When the unpinned user joins the room
685        let updates = room_change(user_id, MembershipState::Join);
686        let update = state.process_change(updates).await;
687
688        // Then we emit no update because this is our own identity
689        assert_eq!(update, vec![]);
690    }
691
692    #[async_test]
693    async fn test_a_pinned_identity_leaving_the_room_does_nothing() {
694        // Given a pinned user is in the room
695        let user_id = user_id!("@u:s.co");
696        let mut room = FakeRoom::new();
697        room.member(other_user_identity(user_id).await, IdentityState::Pinned);
698        let mut state = RoomIdentityState::new(room.clone()).await;
699
700        // When the pinned user leaves the room
701        let updates = room_change(user_id, MembershipState::Leave);
702        let update = state.process_change(updates).await;
703
704        // Then we emit no update because they are pinned
705        assert_eq!(update, []);
706    }
707
708    #[async_test]
709    async fn test_a_verified_identity_leaving_the_room_does_nothing() {
710        // Given a verified user is in the room
711        let user_id = user_id!("@u:s.co");
712        let mut room = FakeRoom::new();
713        room.member(other_user_identity(user_id).await, IdentityState::Verified);
714        let mut state = RoomIdentityState::new(room).await;
715
716        // When the user leaves the room
717        let updates = room_change(user_id, MembershipState::Leave);
718        let update = state.process_change(updates).await;
719
720        // Then we emit no update because they are verified
721        assert_eq!(update, []);
722    }
723
724    #[async_test]
725    async fn test_an_unpinned_identity_leaving_the_room_notifies() {
726        // Given an unpinned user is in the room
727        let user_id = user_id!("@u:s.co");
728        let mut room = FakeRoom::new();
729        room.member(other_user_identity(user_id).await, IdentityState::PinViolation);
730        let mut state = RoomIdentityState::new(room.clone()).await;
731
732        // When the unpinned user leaves the room
733        let updates = room_change(user_id, MembershipState::Leave);
734        let update = state.process_change(updates).await;
735
736        // Then we emit an update saying they became pinned
737        assert_eq!(
738            update,
739            vec![IdentityStatusChange {
740                user_id: user_id.to_owned(),
741                changed_to: IdentityState::Pinned
742            }]
743        );
744    }
745
746    #[async_test]
747    async fn test_a_verification_violating_identity_leaving_the_room_notifies() {
748        // Given an unpinned user is in the room
749        let user_id = user_id!("@u:s.co");
750        let mut room = FakeRoom::new();
751        room.member(other_user_identity(user_id).await, IdentityState::VerificationViolation);
752        let mut state = RoomIdentityState::new(room).await;
753
754        // When the user leaves the room
755        let updates = room_change(user_id, MembershipState::Leave);
756        let update = state.process_change(updates).await;
757
758        // Then we emit an update saying they became pinned
759        assert_eq!(
760            update,
761            vec![IdentityStatusChange {
762                user_id: user_id.to_owned(),
763                changed_to: IdentityState::Pinned
764            }]
765        );
766    }
767
768    #[async_test]
769    async fn test_a_pinned_identity_being_banned_does_nothing() {
770        // Given a pinned user is in the room
771        let user_id = user_id!("@u:s.co");
772        let mut room = FakeRoom::new();
773        room.member(other_user_identity(user_id).await, IdentityState::Pinned);
774        let mut state = RoomIdentityState::new(room.clone()).await;
775
776        // When the pinned user is banned
777        let updates = room_change(user_id, MembershipState::Ban);
778        let update = state.process_change(updates).await;
779
780        // Then we emit no update because they are pinned
781        assert_eq!(update, []);
782    }
783
784    #[async_test]
785    async fn test_an_unpinned_identity_being_banned_notifies() {
786        // Given an unpinned user is in the room
787        let user_id = user_id!("@u:s.co");
788        let mut room = FakeRoom::new();
789        room.member(other_user_identity(user_id).await, IdentityState::PinViolation);
790        let mut state = RoomIdentityState::new(room.clone()).await;
791
792        // When the unpinned user is banned
793        let updates = room_change(user_id, MembershipState::Ban);
794        let update = state.process_change(updates).await;
795
796        // Then we emit an update saying they became unpinned
797        assert_eq!(
798            update,
799            vec![IdentityStatusChange {
800                user_id: user_id.to_owned(),
801                changed_to: IdentityState::Pinned
802            }]
803        );
804    }
805
806    #[async_test]
807    async fn test_multiple_simultaneous_identity_updates_are_all_notified() {
808        // Given several people in the room with different states
809        let user1 = user_id!("@u1:s.co");
810        let user2 = user_id!("@u2:s.co");
811        let user3 = user_id!("@u3:s.co");
812        let mut room = FakeRoom::new();
813        room.member(other_user_identity(user1).await, IdentityState::Pinned);
814        room.member(other_user_identity(user2).await, IdentityState::PinViolation);
815        room.member(other_user_identity(user3).await, IdentityState::Pinned);
816        let mut state = RoomIdentityState::new(room.clone()).await;
817
818        // When they all change state simultaneously
819        let updates = identity_changes(
820            &mut room,
821            &[
822                IdentityChangeSpec {
823                    user_id: user1.to_owned(),
824                    changed_to: IdentityState::PinViolation,
825                    new: false,
826                    own: false,
827                },
828                IdentityChangeSpec {
829                    user_id: user2.to_owned(),
830                    changed_to: IdentityState::Pinned,
831                    new: false,
832                    own: false,
833                },
834                IdentityChangeSpec {
835                    user_id: user3.to_owned(),
836                    changed_to: IdentityState::PinViolation,
837                    new: false,
838                    own: false,
839                },
840            ],
841        )
842        .await;
843        let update = state.process_change(updates).await;
844
845        // Then we emit updates for each of them
846        assert_eq!(
847            update,
848            vec![
849                IdentityStatusChange {
850                    user_id: user1.to_owned(),
851                    changed_to: IdentityState::PinViolation
852                },
853                IdentityStatusChange {
854                    user_id: user2.to_owned(),
855                    changed_to: IdentityState::Pinned
856                },
857                IdentityStatusChange {
858                    user_id: user3.to_owned(),
859                    changed_to: IdentityState::PinViolation
860                }
861            ]
862        );
863    }
864
865    #[async_test]
866    async fn test_multiple_changes_are_notified() {
867        // Given someone in the room is pinned
868        let user_id = user_id!("@u:s.co");
869        let mut room = FakeRoom::new();
870        room.member(other_user_identity(user_id).await, IdentityState::Pinned);
871        let mut state = RoomIdentityState::new(room.clone()).await;
872
873        // When they change state multiple times
874        let update1 = state
875            .process_change(
876                identity_change(&mut room, user_id, IdentityState::PinViolation, false, false)
877                    .await,
878            )
879            .await;
880        let update2 = state
881            .process_change(
882                identity_change(&mut room, user_id, IdentityState::PinViolation, false, false)
883                    .await,
884            )
885            .await;
886        let update3 = state
887            .process_change(
888                identity_change(&mut room, user_id, IdentityState::Pinned, false, false).await,
889            )
890            .await;
891        let update4 = state
892            .process_change(
893                identity_change(&mut room, user_id, IdentityState::PinViolation, false, false)
894                    .await,
895            )
896            .await;
897
898        // Then we emit updates each time
899        assert_eq!(
900            update1,
901            vec![IdentityStatusChange {
902                user_id: user_id.to_owned(),
903                changed_to: IdentityState::PinViolation
904            }]
905        );
906        // (Except update2 where nothing changed)
907        assert_eq!(update2, vec![]);
908        assert_eq!(
909            update3,
910            vec![IdentityStatusChange {
911                user_id: user_id.to_owned(),
912                changed_to: IdentityState::Pinned
913            }]
914        );
915        assert_eq!(
916            update4,
917            vec![IdentityStatusChange {
918                user_id: user_id.to_owned(),
919                changed_to: IdentityState::PinViolation
920            }]
921        );
922    }
923
924    #[async_test]
925    async fn test_current_state_of_all_pinned_room_is_empty() {
926        // Given everyone in the room is pinned
927        let user1 = user_id!("@u1:s.co");
928        let user2 = user_id!("@u2:s.co");
929        let mut room = FakeRoom::new();
930        room.member(other_user_identity(user1).await, IdentityState::Pinned);
931        room.member(other_user_identity(user2).await, IdentityState::Pinned);
932        let state = RoomIdentityState::new(room).await;
933        assert!(state.current_state().is_empty());
934    }
935
936    #[async_test]
937    async fn test_current_state_contains_all_nonpinned_users() {
938        // Given some people are unpinned
939        let user1 = user_id!("@u1:s.co");
940        let user2 = user_id!("@u2:s.co");
941        let user3 = user_id!("@u3:s.co");
942        let user4 = user_id!("@u4:s.co");
943        let user5 = user_id!("@u5:s.co");
944        let user6 = user_id!("@u6:s.co");
945        let mut room = FakeRoom::new();
946        room.member(other_user_identity(user1).await, IdentityState::Pinned);
947        room.member(other_user_identity(user2).await, IdentityState::PinViolation);
948        room.member(other_user_identity(user3).await, IdentityState::Pinned);
949        room.member(other_user_identity(user4).await, IdentityState::PinViolation);
950        room.member(other_user_identity(user5).await, IdentityState::Verified);
951        room.member(other_user_identity(user6).await, IdentityState::VerificationViolation);
952        let mut state = RoomIdentityState::new(room).await.current_state();
953        state.sort_by_key(|change| change.user_id.to_owned());
954        assert_eq!(
955            state,
956            vec![
957                IdentityStatusChange {
958                    user_id: owned_user_id!("@u2:s.co"),
959                    changed_to: IdentityState::PinViolation
960                },
961                IdentityStatusChange {
962                    user_id: owned_user_id!("@u4:s.co"),
963                    changed_to: IdentityState::PinViolation
964                },
965                IdentityStatusChange {
966                    user_id: owned_user_id!("@u5:s.co"),
967                    changed_to: IdentityState::Verified
968                },
969                IdentityStatusChange {
970                    user_id: owned_user_id!("@u6:s.co"),
971                    changed_to: IdentityState::VerificationViolation
972                }
973            ]
974        );
975    }
976
977    #[derive(Debug)]
978    struct Membership {
979        is_member: bool,
980        user_identity: UserIdentity,
981        identity_state: IdentityState,
982    }
983
984    #[derive(Clone, Debug)]
985    struct FakeRoom {
986        users: Arc<Mutex<HashMap<OwnedUserId, Membership>>>,
987    }
988
989    impl FakeRoom {
990        fn new() -> Self {
991            Self { users: Default::default() }
992        }
993
994        fn 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: true, user_identity, identity_state },
998            );
999        }
1000
1001        fn non_member(&mut self, user_identity: UserIdentity, identity_state: IdentityState) {
1002            self.users.lock().unwrap().insert(
1003                user_identity.user_id().to_owned(),
1004                Membership { is_member: false, user_identity, identity_state },
1005            );
1006        }
1007
1008        fn update_state(&self, user_id: &UserId, changed_to: &IdentityState) {
1009            self.users
1010                .lock()
1011                .unwrap()
1012                .entry(user_id.to_owned())
1013                .and_modify(|m| m.identity_state = changed_to.clone());
1014        }
1015    }
1016
1017    impl RoomIdentityProvider for FakeRoom {
1018        fn is_member<'a>(&'a self, user_id: &'a UserId) -> BoxFuture<'a, bool> {
1019            Box::pin(async {
1020                self.users.lock().unwrap().get(user_id).map(|m| m.is_member).unwrap_or(false)
1021            })
1022        }
1023
1024        fn member_identities(&self) -> BoxFuture<'_, Vec<UserIdentity>> {
1025            Box::pin(async {
1026                self.users
1027                    .lock()
1028                    .unwrap()
1029                    .values()
1030                    .filter_map(|m| if m.is_member { Some(m.user_identity.clone()) } else { None })
1031                    .collect()
1032            })
1033        }
1034
1035        fn user_identity<'a>(&'a self, user_id: &'a UserId) -> BoxFuture<'a, Option<UserIdentity>> {
1036            Box::pin(async {
1037                self.users.lock().unwrap().get(user_id).map(|m| m.user_identity.clone())
1038            })
1039        }
1040
1041        fn state_of(&self, user_identity: &UserIdentity) -> IdentityState {
1042            self.users
1043                .lock()
1044                .unwrap()
1045                .get(user_identity.user_id())
1046                .map(|m| m.identity_state.clone())
1047                .unwrap_or(IdentityState::Pinned)
1048        }
1049    }
1050
1051    fn room_change(user_id: &UserId, new_state: MembershipState) -> RoomIdentityChange {
1052        let event = SyncRoomMemberEvent::Original(OriginalSyncStateEvent {
1053            content: RoomMemberEventContent::new(new_state),
1054            event_id: owned_event_id!("$1"),
1055            sender: owned_user_id!("@admin:b.c"),
1056            origin_server_ts: MilliSecondsSinceUnixEpoch(UInt::new(2123).unwrap()),
1057            unsigned: RoomMemberUnsigned::new(),
1058            state_key: user_id.to_owned(),
1059        });
1060        RoomIdentityChange::SyncRoomMemberEvent(Box::new(event))
1061    }
1062
1063    async fn identity_change(
1064        room: &mut FakeRoom,
1065        user_id: &UserId,
1066        changed_to: IdentityState,
1067        new: bool,
1068        own: bool,
1069    ) -> RoomIdentityChange {
1070        identity_changes(
1071            room,
1072            &[IdentityChangeSpec { user_id: user_id.to_owned(), changed_to, new, own }],
1073        )
1074        .await
1075    }
1076
1077    struct IdentityChangeSpec {
1078        user_id: OwnedUserId,
1079        changed_to: IdentityState,
1080        new: bool,
1081        own: bool,
1082    }
1083
1084    async fn identity_changes(
1085        room: &mut FakeRoom,
1086        changes: &[IdentityChangeSpec],
1087    ) -> RoomIdentityChange {
1088        let mut updates = IdentityUpdates::default();
1089
1090        for change in changes {
1091            let user_identity = if change.own {
1092                own_user_identity(&change.user_id).await
1093            } else {
1094                other_user_identity(&change.user_id).await
1095            };
1096
1097            room.update_state(user_identity.user_id(), &change.changed_to);
1098            if change.new {
1099                updates.new.insert(user_identity.user_id().to_owned(), user_identity);
1100            } else {
1101                updates.changed.insert(user_identity.user_id().to_owned(), user_identity);
1102            }
1103        }
1104        RoomIdentityChange::IdentityUpdates(updates)
1105    }
1106
1107    /// Create an other `UserIdentity` for use in tests
1108    async fn other_user_identity(user_id: &UserId) -> UserIdentity {
1109        use std::sync::Arc;
1110
1111        use ruma::owned_device_id;
1112        use tokio::sync::Mutex;
1113
1114        use crate::{
1115            olm::PrivateCrossSigningIdentity,
1116            store::{CryptoStoreWrapper, MemoryStore},
1117            verification::VerificationMachine,
1118            Account,
1119        };
1120
1121        let device_id = owned_device_id!("DEV123");
1122        let account = Account::with_device_id(user_id, &device_id);
1123
1124        let private_identity =
1125            Arc::new(Mutex::new(PrivateCrossSigningIdentity::with_account(&account).await.0));
1126
1127        let other_user_identity_data =
1128            OtherUserIdentityData::from_private(&*private_identity.lock().await).await;
1129
1130        UserIdentity::Other(OtherUserIdentity {
1131            inner: other_user_identity_data,
1132            own_identity: None,
1133            verification_machine: VerificationMachine::new(
1134                account.clone(),
1135                Arc::new(Mutex::new(PrivateCrossSigningIdentity::new(
1136                    account.user_id().to_owned(),
1137                ))),
1138                Arc::new(CryptoStoreWrapper::new(
1139                    account.user_id(),
1140                    account.device_id(),
1141                    MemoryStore::new(),
1142                )),
1143            ),
1144        })
1145    }
1146
1147    /// Create an own `UserIdentity` for use in tests
1148    async fn own_user_identity(user_id: &UserId) -> UserIdentity {
1149        use std::sync::Arc;
1150
1151        use ruma::owned_device_id;
1152        use tokio::sync::Mutex;
1153
1154        use crate::{
1155            olm::PrivateCrossSigningIdentity,
1156            store::{CryptoStoreWrapper, MemoryStore},
1157            verification::VerificationMachine,
1158            Account,
1159        };
1160
1161        let device_id = owned_device_id!("DEV123");
1162        let account = Account::with_device_id(user_id, &device_id);
1163
1164        let private_identity =
1165            Arc::new(Mutex::new(PrivateCrossSigningIdentity::with_account(&account).await.0));
1166
1167        let own_user_identity_data =
1168            OwnUserIdentityData::from_private(&*private_identity.lock().await).await;
1169
1170        let cross_signing_identity = PrivateCrossSigningIdentity::new(account.user_id().to_owned());
1171        let verification_machine = VerificationMachine::new(
1172            account.clone(),
1173            Arc::new(Mutex::new(cross_signing_identity.clone())),
1174            Arc::new(CryptoStoreWrapper::new(
1175                account.user_id(),
1176                account.device_id(),
1177                MemoryStore::new(),
1178            )),
1179        );
1180
1181        UserIdentity::Own(own_identity_wrapped(
1182            own_user_identity_data,
1183            verification_machine.clone(),
1184            Store::new(
1185                account.static_data().clone(),
1186                Arc::new(Mutex::new(cross_signing_identity)),
1187                Arc::new(CryptoStoreWrapper::new(
1188                    user_id!("@u:s.co"),
1189                    device_id!("DEV7"),
1190                    MemoryStore::new(),
1191                )),
1192                verification_machine,
1193            ),
1194        ))
1195    }
1196}