Fixes element-hq/element-meta/issues/2525 - Display a warning when a user's pinned identity changes

This commit is contained in:
Stefan Ceriu
2024-10-01 08:41:15 +03:00
parent 70652ed7be
commit 794d0eead1
30 changed files with 678 additions and 131 deletions
@@ -895,6 +895,22 @@ class ClientProxy: ClientProxyProtocol {
await client.encryption().curve25519Key()
}
func pinUserIdentity(_ userID: String) async -> Result<Void, ClientProxyError> {
MXLog.info("Pinning current identity for user: \(userID)")
do {
guard let userIdentity = try await client.encryption().getUserIdentity(userId: userID) else {
MXLog.error("Failed retrieving identity for user: \(userID)")
return .failure(.failedRetrievingUserIdentity)
}
return try await .success(userIdentity.pin())
} catch {
MXLog.error("Failed pinning current identity for user: \(error)")
return .failure(.sdkError(error))
}
}
func resetIdentity() async -> Result<IdentityResetHandle?, ClientProxyError> {
do {
return try await .success(client.encryption().resetIdentity())
@@ -35,6 +35,7 @@ enum ClientProxyError: Error {
case invalidServerName
case failedUploadingMedia(Error, MatrixErrorCode)
case roomPreviewIsPrivate
case failedRetrievingUserIdentity
}
enum SlidingSyncConstants {
@@ -196,5 +197,6 @@ protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol {
func ed25519Base64() async -> String?
func curve25519Base64() async -> String?
func pinUserIdentity(_ userID: String) async -> Result<Void, ClientProxyError>
func resetIdentity() async -> Result<IdentityResetHandle?, ClientProxyError>
}
@@ -295,10 +295,12 @@ class ElementCallService: NSObject, ElementCallServiceProtocol, PKPushRegistryDe
roomProxy
.actionsPublisher
.map { action -> (Bool, [String]) in
.compactMap { action -> (Bool, [String])? in
switch action {
case .roomInfoUpdate:
return (roomProxy.hasOngoingCall, roomProxy.activeRoomCallParticipants)
default:
return nil
}
}
.removeDuplicates { $0 == $1 }
@@ -58,6 +58,8 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol {
private var roomInfoObservationToken: TaskHandle?
// periphery:ignore - required for instance retention in the rust codebase
private var typingNotificationObservationToken: TaskHandle?
// periphery:ignore - required for instance retention in the rust codebase
private var identityStatusChangesObservationToken: TaskHandle?
private var subscribedForUpdates = false
@@ -70,6 +72,11 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol {
var typingMembersPublisher: CurrentValuePublisher<[String], Never> {
typingMembersSubject.asCurrentValuePublisher()
}
private let identityStatusChangesSubject = CurrentValueSubject<[IdentityStatusChange], Never>([])
var identityStatusChangesPublisher: CurrentValuePublisher<[IdentityStatusChange], Never> {
identityStatusChangesSubject.asCurrentValuePublisher()
}
private let actionsSubject = PassthroughSubject<JoinedRoomProxyAction, Never>()
var actionsPublisher: AnyPublisher<JoinedRoomProxyAction, Never> {
@@ -193,6 +200,8 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol {
subscribeToRoomInfoUpdates()
subscribeToIdentityStatusChanges()
subscribeToTypingNotifications()
}
@@ -708,6 +717,16 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol {
typingMembersSubject.send(typingMembers)
})
}
private func subscribeToIdentityStatusChanges() {
identityStatusChangesObservationToken = room.subscribeToIdentityStatusChanges(listener: RoomIdentityStatusChangeListener { [weak self] changes in
guard let self else { return }
MXLog.info("Received identity status changes: \(changes)")
identityStatusChangesSubject.send(changes)
})
}
}
private final class RoomInfoUpdateListener: RoomInfoListener {
@@ -733,3 +752,15 @@ private final class RoomTypingNotificationUpdateListener: TypingNotificationsLis
onUpdateClosure(typingUserIds)
}
}
private final class RoomIdentityStatusChangeListener: IdentityStatusChangeListener {
private let onUpdateClosure: ([IdentityStatusChange]) -> Void
init(_ onUpdateClosure: @escaping ([IdentityStatusChange]) -> Void) {
self.onUpdateClosure = onUpdateClosure
}
func call(identityStatusChange: [IdentityStatusChange]) {
onUpdateClosure(identityStatusChange)
}
}
@@ -16,12 +16,24 @@ final class RoomMemberProxy: RoomMemberProxyProtocol {
}
var userID: String { member.userId }
var displayName: String? { member.displayName }
var disambiguatedDisplayName: String? {
guard let displayName else {
return nil
}
return member.isNameAmbiguous ? "\(displayName) (\(userID))" : displayName
}
var avatarURL: URL? { member.avatarUrl.flatMap(URL.init(string:)) }
var membership: MembershipState { member.membership }
var isIgnored: Bool { member.isIgnored }
var powerLevel: Int { Int(member.powerLevel) }
var role: RoomMemberRole { member.suggestedRoleForPowerLevel }
}
@@ -11,13 +11,18 @@ import MatrixRustSDK
// sourcery: AutoMockable
protocol RoomMemberProxyProtocol: AnyObject {
var userID: String { get }
var displayName: String? { get }
var disambiguatedDisplayName: String? { get }
var avatarURL: URL? { get }
var membership: MembershipState { get }
var isIgnored: Bool { get }
var powerLevel: Int { get }
var role: RoomMemberRole { get }
}
@@ -56,7 +56,7 @@ protocol InvitedRoomProxyProtocol: RoomProxyProtocol {
func acceptInvitation() async -> Result<Void, RoomProxyError>
}
enum JoinedRoomProxyAction {
enum JoinedRoomProxyAction: Equatable {
case roomInfoUpdate
}
@@ -73,6 +73,8 @@ protocol JoinedRoomProxyProtocol: RoomProxyProtocol {
var typingMembersPublisher: CurrentValuePublisher<[String], Never> { get }
var identityStatusChangesPublisher: CurrentValuePublisher<[IdentityStatusChange], Never> { get }
var actionsPublisher: AnyPublisher<JoinedRoomProxyAction, Never> { get }
var timeline: TimelineProxyProtocol { get }
@@ -19,7 +19,7 @@ enum EncryptionAuthenticity: Hashable {
case unknownDevice(color: Color)
case unsignedDevice(color: Color)
case unverifiedIdentity(color: Color)
case previouslyVerified(color: Color)
case verificationViolation(color: Color)
case sentInClear(color: Color)
var message: String {
@@ -32,7 +32,7 @@ enum EncryptionAuthenticity: Hashable {
L10n.eventShieldReasonUnsignedDevice
case .unverifiedIdentity:
L10n.eventShieldReasonUnverifiedIdentity
case .previouslyVerified:
case .verificationViolation:
L10n.eventShieldReasonPreviouslyVerified
case .sentInClear:
L10n.eventShieldReasonSentInClear
@@ -45,7 +45,7 @@ enum EncryptionAuthenticity: Hashable {
.unknownDevice(let color),
.unsignedDevice(let color),
.unverifiedIdentity(let color),
.previouslyVerified(let color),
.verificationViolation(let color),
.sentInClear(let color):
color
}
@@ -54,7 +54,7 @@ enum EncryptionAuthenticity: Hashable {
var icon: KeyPath<CompoundIcons, Image> {
switch self {
case .notGuaranteed: \.info
case .unknownDevice, .unsignedDevice, .unverifiedIdentity, .previouslyVerified: \.helpSolid
case .unknownDevice, .unsignedDevice, .unverifiedIdentity, .verificationViolation: \.helpSolid
case .sentInClear: \.lockOff
}
}
@@ -82,8 +82,8 @@ extension EncryptionAuthenticity {
self = .unsignedDevice(color: color)
case .unverifiedIdentity:
self = .unverifiedIdentity(color: color)
case .previouslyVerified:
self = .previouslyVerified(color: color)
case .verificationViolation:
self = .verificationViolation(color: color)
case .sentInClear:
self = .sentInClear(color: color)
}