Skip to main content

matrix_sdk_crypto/identities/
room_identity_state.rs

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