From d0890d50e9cc94f5e2c4e871256c9f3b702d3290 Mon Sep 17 00:00:00 2001 From: Mauro Romito Date: Tue, 2 Apr 2024 15:06:28 +0200 Subject: [PATCH 01/14] Prepare for new sprint --- Config/AppVersion.xcconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Config/AppVersion.xcconfig b/Config/AppVersion.xcconfig index 4cf4cbfbe3..5df7a12dbe 100644 --- a/Config/AppVersion.xcconfig +++ b/Config/AppVersion.xcconfig @@ -15,5 +15,5 @@ // // Version -MARKETING_VERSION = 1.11.9 -CURRENT_PROJECT_VERSION = 1.11.9 +MARKETING_VERSION = 1.11.10 +CURRENT_PROJECT_VERSION = 1.11.10 From e266f7f0cbc651cd61ea2802c983fc595e7dfd70 Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 4 Apr 2024 13:01:19 +0200 Subject: [PATCH 02/14] Analytics | Report expected UTDs as HistoricalMessage error --- .../DecryptionFailure+Analytics.swift | 21 +- .../Modules/Analytics/DecryptionFailure.swift | 5 + .../Analytics/DecryptionFailureTracker.swift | 18 +- Riot/Utils/EventFormatter.m | 2 +- RiotTests/DecryptionFailureTrackerTests.swift | 210 ++++++++++++++- RiotTests/FakeUtils.swift | 242 +++++++++++++++++- 6 files changed, 477 insertions(+), 21 deletions(-) diff --git a/Riot/Modules/Analytics/DecryptionFailure+Analytics.swift b/Riot/Modules/Analytics/DecryptionFailure+Analytics.swift index cd129aee35..5ed3c36c2c 100644 --- a/Riot/Modules/Analytics/DecryptionFailure+Analytics.swift +++ b/Riot/Modules/Analytics/DecryptionFailure+Analytics.swift @@ -21,23 +21,34 @@ extension DecryptionFailure { public func toAnalyticsEvent() -> AnalyticsEvent.Error { - let timeToDecryptMillis: Int = if self.timeToDecrypt != nil { - Int(self.timeToDecrypt! * 1000) + let timeToDecryptMillis: Int = if let ttd = self.timeToDecrypt { + Int(ttd * 1000) } else { -1 } + + let isHistoricalEvent = if let localAge = self.eventLocalAgeMillis { + localAge < 0 + } else { false } + + let errorName = if isHistoricalEvent && self.trustOwnIdentityAtTimeOfFailure == false { + AnalyticsEvent.Error.Name.HistoricalMessage + } else { + self.reason.errorName + } + return AnalyticsEvent.Error( context: self.context, cryptoModule: .Rust, cryptoSDK: .Rust, domain: .E2EE, - eventLocalAgeMillis: nil, + eventLocalAgeMillis: self.eventLocalAgeMillis, isFederated: nil, isMatrixDotOrg: nil, - name: self.reason.errorName, + name: errorName, timeToDecryptMillis: timeToDecryptMillis, - userTrustsOwnIdentity: nil, + userTrustsOwnIdentity: self.trustOwnIdentityAtTimeOfFailure, wasVisibleToUser: nil ) } diff --git a/Riot/Modules/Analytics/DecryptionFailure.swift b/Riot/Modules/Analytics/DecryptionFailure.swift index 9e0ca57858..6d3f6c1a83 100644 --- a/Riot/Modules/Analytics/DecryptionFailure.swift +++ b/Riot/Modules/Analytics/DecryptionFailure.swift @@ -47,6 +47,11 @@ import AnalyticsEvents /// UTDs can be permanent or temporary. If temporary, this field will contain the time it took to decrypt the message in milliseconds. If permanent should be nil var timeToDecrypt: TimeInterval? + /// Was the current cross-signing identity trusted at the time of decryption + var trustOwnIdentityAtTimeOfFailure: Bool? + + var eventLocalAgeMillis: Int? + init(failedEventId: String, reason: DecryptionFailureReason, context: String, ts: TimeInterval) { self.failedEventId = failedEventId self.reason = reason diff --git a/Riot/Modules/Analytics/DecryptionFailureTracker.swift b/Riot/Modules/Analytics/DecryptionFailureTracker.swift index 19b8afb19c..503c733ba3 100644 --- a/Riot/Modules/Analytics/DecryptionFailureTracker.swift +++ b/Riot/Modules/Analytics/DecryptionFailureTracker.swift @@ -62,14 +62,14 @@ class DecryptionFailureTracker: NSObject { selector: #selector(eventDidDecrypt(_:)), name: .mxEventDidDecrypt, object: nil) - } @objc - func reportUnableToDecryptError(forEvent event: MXEvent, withRoomState roomState: MXRoomState, myUser userId: String) { + func reportUnableToDecryptError(forEvent event: MXEvent, withRoomState roomState: MXRoomState, mySession: MXSession) { if reportedFailures[event.eventId] != nil || trackedEvents.contains(event.eventId) { return } + guard let userId = mySession.myUserId else { return } // Filter out "expected" UTDs // We cannot decrypt messages sent before the user joined the room @@ -82,6 +82,12 @@ class DecryptionFailureTracker: NSObject { guard let error = event.decryptionError as? NSError else { return } + let eventOrigin = event.originServerTs + let deviceTimestamp = mySession.crypto.deviceCreationTs + // If negative it's an historical event relative to the current session + let eventRelativeAgeMillis = Int(eventOrigin) - Int(deviceTimestamp) + let isSessionVerified = mySession.crypto.crossSigning.canTrustCrossSigning + var reason = DecryptionFailureReason.unspecified if error.code == MXDecryptingErrorUnknownInboundSessionIdCode.rawValue { @@ -92,7 +98,13 @@ class DecryptionFailureTracker: NSObject { let context = String(format: "code: %ld, description: %@", error.code, event.decryptionError.localizedDescription) - reportedFailures[failedEventId] = DecryptionFailure(failedEventId: failedEventId, reason: reason, context: context, ts: self.timeProvider.nowTs()) + let failure = DecryptionFailure(failedEventId: failedEventId, reason: reason, context: context, ts: self.timeProvider.nowTs()) + + failure.eventLocalAgeMillis = Int(exactly: eventRelativeAgeMillis) + failure.trustOwnIdentityAtTimeOfFailure = isSessionVerified + + reportedFailures[failedEventId] = failure + // Start the ticker if needed. There is no need to have a ticker if no failures are tracked if checkFailuresTimer == nil { diff --git a/Riot/Utils/EventFormatter.m b/Riot/Utils/EventFormatter.m index 4494bdbb2d..d53a3e19a0 100644 --- a/Riot/Utils/EventFormatter.m +++ b/Riot/Utils/EventFormatter.m @@ -334,7 +334,7 @@ - (NSAttributedString *)unsafeAttributedStringFromEvent:(MXEvent *)event { // Track e2e failures dispatch_async(dispatch_get_main_queue(), ^{ - [[DecryptionFailureTracker sharedInstance] reportUnableToDecryptErrorForEvent:event withRoomState:roomState myUser:self->mxSession.myUser.userId]; + [[DecryptionFailureTracker sharedInstance] reportUnableToDecryptErrorForEvent:event withRoomState:roomState mySession:self->mxSession]; }); if (event.decryptionError.code == MXDecryptingErrorUnknownInboundSessionIdCode) diff --git a/RiotTests/DecryptionFailureTrackerTests.swift b/RiotTests/DecryptionFailureTrackerTests.swift index 7cd9bf480f..c142514cb0 100644 --- a/RiotTests/DecryptionFailureTrackerTests.swift +++ b/RiotTests/DecryptionFailureTrackerTests.swift @@ -41,10 +41,23 @@ class DecryptionFailureTrackerTests: XCTestCase { } let timeShifter = TimeShifter() + var fakeCrypto: FakeCrypto! + var fakeSession: FakeSession! + var fakeCrossSigning: FakeCrossSigning! + + override func setUp() { + super.setUp() + self.fakeCrypto = FakeCrypto() + self.fakeCrossSigning = FakeCrossSigning() + self.fakeCrypto.crossSigning = self.fakeCrossSigning + self.fakeSession = FakeSession(mockCrypto: self.fakeCrypto) + } + func test_grace_period() { let myUser = "test@example.com"; + fakeSession.mockUserId = myUser; let decryptionFailureTracker = DecryptionFailureTracker(); decryptionFailureTracker.timeProvider = timeShifter; @@ -61,7 +74,7 @@ class DecryptionFailureTrackerTests: XCTestCase { let fakeRoomState = FakeRoomState(); fakeRoomState.mockMembers = FakeRoomMembers(joined: [myUser]) - decryptionFailureTracker.reportUnableToDecryptError(forEvent: fakeEvent, withRoomState: fakeRoomState, myUser: myUser); + decryptionFailureTracker.reportUnableToDecryptError(forEvent: fakeEvent, withRoomState: fakeRoomState, mySession: fakeSession); timeShifter.timestamp = TimeInterval(2) @@ -83,6 +96,7 @@ class DecryptionFailureTrackerTests: XCTestCase { func test_report_ratcheted_key_utd() { let myUser = "test@example.com"; + fakeSession.mockUserId = myUser; let decryptionFailureTracker = DecryptionFailureTracker(); decryptionFailureTracker.timeProvider = timeShifter; @@ -99,7 +113,7 @@ class DecryptionFailureTrackerTests: XCTestCase { let fakeRoomState = FakeRoomState(); fakeRoomState.mockMembers = FakeRoomMembers(joined: [myUser]) - decryptionFailureTracker.reportUnableToDecryptError(forEvent: fakeEvent, withRoomState: fakeRoomState, myUser: myUser); + decryptionFailureTracker.reportUnableToDecryptError(forEvent: fakeEvent, withRoomState: fakeRoomState, mySession: fakeSession); // Pass the max period timeShifter.timestamp = TimeInterval(70) @@ -112,6 +126,7 @@ class DecryptionFailureTrackerTests: XCTestCase { func test_report_unspecified_error() { let myUser = "test@example.com"; + fakeSession.mockUserId = myUser; let decryptionFailureTracker = DecryptionFailureTracker(); decryptionFailureTracker.timeProvider = timeShifter; @@ -128,7 +143,7 @@ class DecryptionFailureTrackerTests: XCTestCase { let fakeRoomState = FakeRoomState(); fakeRoomState.mockMembers = FakeRoomMembers(joined: [myUser]) - decryptionFailureTracker.reportUnableToDecryptError(forEvent: fakeEvent, withRoomState: fakeRoomState, myUser: myUser); + decryptionFailureTracker.reportUnableToDecryptError(forEvent: fakeEvent, withRoomState: fakeRoomState, mySession: fakeSession); // Pass the max period timeShifter.timestamp = TimeInterval(70) @@ -143,6 +158,7 @@ class DecryptionFailureTrackerTests: XCTestCase { func test_do_not_double_report() { let myUser = "test@example.com"; + fakeSession.mockUserId = myUser; let decryptionFailureTracker = DecryptionFailureTracker(); decryptionFailureTracker.timeProvider = timeShifter; @@ -160,7 +176,7 @@ class DecryptionFailureTrackerTests: XCTestCase { let fakeRoomState = FakeRoomState(); fakeRoomState.mockMembers = FakeRoomMembers(joined: [myUser]) - decryptionFailureTracker.reportUnableToDecryptError(forEvent: fakeEvent, withRoomState: fakeRoomState, myUser: myUser); + decryptionFailureTracker.reportUnableToDecryptError(forEvent: fakeEvent, withRoomState: fakeRoomState, mySession: fakeSession); // Pass the max period timeShifter.timestamp = TimeInterval(70) @@ -171,7 +187,7 @@ class DecryptionFailureTrackerTests: XCTestCase { // Try to report again the same event testDelegate.reportedFailure = nil - decryptionFailureTracker.reportUnableToDecryptError(forEvent: fakeEvent, withRoomState: fakeRoomState, myUser: myUser); + decryptionFailureTracker.reportUnableToDecryptError(forEvent: fakeEvent, withRoomState: fakeRoomState, mySession: fakeSession); // Pass the grace period timeShifter.timestamp = TimeInterval(10) @@ -184,6 +200,7 @@ class DecryptionFailureTrackerTests: XCTestCase { func test_ignore_not_member() { let myUser = "test@example.com"; + fakeSession.mockUserId = myUser; let decryptionFailureTracker = DecryptionFailureTracker(); decryptionFailureTracker.timeProvider = timeShifter; @@ -203,7 +220,7 @@ class DecryptionFailureTrackerTests: XCTestCase { fakeMembers.mockMembers[myUser] = MXMembership.ban fakeRoomState.mockMembers = fakeMembers - decryptionFailureTracker.reportUnableToDecryptError(forEvent: fakeEvent, withRoomState: fakeRoomState, myUser: myUser); + decryptionFailureTracker.reportUnableToDecryptError(forEvent: fakeEvent, withRoomState: fakeRoomState, mySession: fakeSession); // Pass the grace period timeShifter.timestamp = TimeInterval(5) @@ -218,6 +235,7 @@ class DecryptionFailureTrackerTests: XCTestCase { func test_notification_center() { let myUser = "test@example.com"; + fakeSession.mockUserId = myUser; let decryptionFailureTracker = DecryptionFailureTracker(); decryptionFailureTracker.timeProvider = timeShifter; @@ -235,7 +253,7 @@ class DecryptionFailureTrackerTests: XCTestCase { let fakeRoomState = FakeRoomState(); fakeRoomState.mockMembers = FakeRoomMembers(joined: [myUser]) - decryptionFailureTracker.reportUnableToDecryptError(forEvent: fakeEvent, withRoomState: fakeRoomState, myUser: myUser); + decryptionFailureTracker.reportUnableToDecryptError(forEvent: fakeEvent, withRoomState: fakeRoomState, mySession: fakeSession); // Shift time below GRACE_PERIOD timeShifter.timestamp = TimeInterval(2) @@ -258,6 +276,7 @@ class DecryptionFailureTrackerTests: XCTestCase { func test_should_report_late_decrypt() { let myUser = "test@example.com"; + fakeSession.mockUserId = myUser; let decryptionFailureTracker = DecryptionFailureTracker(); decryptionFailureTracker.timeProvider = timeShifter; @@ -275,7 +294,7 @@ class DecryptionFailureTrackerTests: XCTestCase { let fakeRoomState = FakeRoomState(); fakeRoomState.mockMembers = FakeRoomMembers(joined: [myUser]) - decryptionFailureTracker.reportUnableToDecryptError(forEvent: fakeEvent, withRoomState: fakeRoomState, myUser: myUser); + decryptionFailureTracker.reportUnableToDecryptError(forEvent: fakeEvent, withRoomState: fakeRoomState, mySession: fakeSession); // Simulate succesful decryption after grace period but before max wait timeShifter.timestamp = TimeInterval(20) @@ -302,6 +321,7 @@ class DecryptionFailureTrackerTests: XCTestCase { func test_should_report_permanent_decryption_error() { let myUser = "test@example.com"; + fakeSession.mockUserId = myUser; let decryptionFailureTracker = DecryptionFailureTracker(); decryptionFailureTracker.timeProvider = timeShifter; @@ -319,7 +339,7 @@ class DecryptionFailureTrackerTests: XCTestCase { let fakeRoomState = FakeRoomState(); fakeRoomState.mockMembers = FakeRoomMembers(joined: [myUser]) - decryptionFailureTracker.reportUnableToDecryptError(forEvent: fakeEvent, withRoomState: fakeRoomState, myUser: myUser); + decryptionFailureTracker.reportUnableToDecryptError(forEvent: fakeEvent, withRoomState: fakeRoomState, mySession: fakeSession); // Simulate succesful decryption after max wait timeShifter.timestamp = TimeInterval(70) @@ -337,5 +357,177 @@ class DecryptionFailureTrackerTests: XCTestCase { XCTAssertEqual(analyticsError.timeToDecryptMillis, -1) } + + + func test_should_report_trust_status_at_decryption_time() { + + let myUser = "test@example.com"; + fakeSession.mockUserId = myUser; + + let decryptionFailureTracker = DecryptionFailureTracker(); + decryptionFailureTracker.timeProvider = timeShifter; + + let testDelegate = AnalyticsDelegate(); + + decryptionFailureTracker.delegate = testDelegate; + + timeShifter.timestamp = TimeInterval(0) + + let fakeEvent = FakeEvent(id: "$0000"); + fakeEvent.decryptionError = NSError(domain: MXDecryptingErrorDomain, code: Int(MXDecryptingErrorUnknownInboundSessionIdCode.rawValue)) + + // set session as not yet verified + fakeCrossSigning.canTrustCrossSigning = false + + let fakeRoomState = FakeRoomState(); + fakeRoomState.mockMembers = FakeRoomMembers(joined: [myUser]) + + decryptionFailureTracker.reportUnableToDecryptError(forEvent: fakeEvent, withRoomState: fakeRoomState, mySession: fakeSession); + + // set verified now + fakeCrossSigning.canTrustCrossSigning = true + + // Simulate succesful decryption after max wait + timeShifter.timestamp = TimeInterval(70) + + decryptionFailureTracker.checkFailures(); + + // Event should have been reported as a late decrypt + XCTAssertEqual(testDelegate.reportedFailure?.trustOwnIdentityAtTimeOfFailure, false); + + // Assert that it's converted to -1 for reporting + let analyticsError = testDelegate.reportedFailure!.toAnalyticsEvent() + + XCTAssertEqual(analyticsError.userTrustsOwnIdentity, false) + + // Report a new error now that session is verified + + let fakeEvent2 = FakeEvent(id: "$0001"); + fakeEvent2.decryptionError = NSError(domain: MXDecryptingErrorDomain, code: Int(MXDecryptingErrorUnknownInboundSessionIdCode.rawValue)) + + + decryptionFailureTracker.reportUnableToDecryptError(forEvent: fakeEvent2, withRoomState: fakeRoomState, mySession: fakeSession); + + // Simulate permanent UTD + timeShifter.timestamp = TimeInterval(140) + + decryptionFailureTracker.checkFailures(); + + XCTAssertEqual(testDelegate.reportedFailure?.failedEventId, "$0001"); + XCTAssertEqual(testDelegate.reportedFailure?.trustOwnIdentityAtTimeOfFailure, true); + + let analyticsError2 = testDelegate.reportedFailure!.toAnalyticsEvent() + + XCTAssertEqual(analyticsError2.userTrustsOwnIdentity, true) + + } + + + func test_should_report_event_age() { + + let myUser = "test@example.com"; + fakeSession.mockUserId = myUser; + + let format = DateFormatter() + format.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ" + + let sessionCreationTimeMillis = format.date(from: "2024-03-09T10:00:00Z")!.timeIntervalSince1970 * 1000 + + let now = format.date(from: "2024-03-09T10:02:00Z")!.timeIntervalSince1970 + + // 5mn after session was created + let postCreationMessageTs = UInt64(format.date(from: "2024-03-09T10:05:00Z")!.timeIntervalSince1970 * 1000) + + let decryptionFailureTracker = DecryptionFailureTracker(); + decryptionFailureTracker.timeProvider = timeShifter; + + let testDelegate = AnalyticsDelegate(); + + decryptionFailureTracker.delegate = testDelegate; + + timeShifter.timestamp = now + + let fakeEvent = FakeEvent(id: "$0000"); + fakeEvent.mockOrigineServerTs = postCreationMessageTs; + fakeEvent.decryptionError = NSError(domain: MXDecryptingErrorDomain, code: Int(MXDecryptingErrorUnknownInboundSessionIdCode.rawValue)) + + fakeCrypto.deviceCreationTs = UInt64(sessionCreationTimeMillis) + + let fakeRoomState = FakeRoomState(); + fakeRoomState.mockMembers = FakeRoomMembers(joined: [myUser]) + + decryptionFailureTracker.reportUnableToDecryptError(forEvent: fakeEvent, withRoomState: fakeRoomState, mySession: fakeSession); + + // set verified now + fakeCrossSigning.canTrustCrossSigning = true + + // Simulate permanent UTD + timeShifter.timestamp = now + TimeInterval(70) + + decryptionFailureTracker.checkFailures(); + + XCTAssertEqual(testDelegate.reportedFailure?.eventLocalAgeMillis, 5 * 60 * 1000); + + let analyticsError = testDelegate.reportedFailure!.toAnalyticsEvent() + + XCTAssertEqual(analyticsError.eventLocalAgeMillis, 5 * 60 * 1000) + + } + + + func test_should_report_expected_utds() { + + let myUser = "test@example.com"; + fakeSession.mockUserId = myUser; + + let format = DateFormatter() + format.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ" + + let sessionCreationTimeMillis = format.date(from: "2024-03-09T10:00:00Z")!.timeIntervalSince1970 * 1000 + + let now = format.date(from: "2024-03-09T10:02:00Z")!.timeIntervalSince1970 + + // 1 day before session was created + let historicalMessageTs = UInt64(format.date(from: "2024-03-08T10:00:00Z")!.timeIntervalSince1970 * 1000) + + let decryptionFailureTracker = DecryptionFailureTracker(); + decryptionFailureTracker.timeProvider = timeShifter; + + let testDelegate = AnalyticsDelegate(); + + decryptionFailureTracker.delegate = testDelegate; + + timeShifter.timestamp = now + + let fakeEvent = FakeEvent(id: "$0000"); + fakeEvent.mockOrigineServerTs = historicalMessageTs; + fakeEvent.decryptionError = NSError(domain: MXDecryptingErrorDomain, code: Int(MXDecryptingErrorUnknownInboundSessionIdCode.rawValue)) + + fakeCrypto.deviceCreationTs = UInt64(sessionCreationTimeMillis) + + let fakeRoomState = FakeRoomState(); + fakeRoomState.mockMembers = FakeRoomMembers(joined: [myUser]) + + fakeCrossSigning.canTrustCrossSigning = false + + decryptionFailureTracker.reportUnableToDecryptError(forEvent: fakeEvent, withRoomState: fakeRoomState, mySession: fakeSession); + + // set verified now + fakeCrossSigning.canTrustCrossSigning = true + + // Simulate permanent UTD + timeShifter.timestamp = now + TimeInterval(70) + + decryptionFailureTracker.checkFailures(); + + // Event should have been reported as a late decrypt + XCTAssertEqual(testDelegate.reportedFailure?.eventLocalAgeMillis, -24 * 60 * 60 * 1000); + + let analyticsError = testDelegate.reportedFailure!.toAnalyticsEvent() + + XCTAssertEqual(analyticsError.name, .HistoricalMessage) + + } + } diff --git a/RiotTests/FakeUtils.swift b/RiotTests/FakeUtils.swift index 7bd350e4bc..f6b1fe4771 100644 --- a/RiotTests/FakeUtils.swift +++ b/RiotTests/FakeUtils.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2024 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,6 +22,7 @@ class FakeEvent: MXEvent { var mockEventId: String; var mockSender: String!; var mockDecryptionError: Error? + var mockOrigineServerTs: UInt64 = 0 init(id: String) { mockEventId = id @@ -31,7 +32,7 @@ class FakeEvent: MXEvent { required init?(coder: NSCoder) { fatalError() } - + override var sender: String! { get { return mockSender } set { mockSender = newValue } @@ -47,6 +48,12 @@ class FakeEvent: MXEvent { set { mockDecryptionError = newValue } } + override var originServerTs: UInt64 { + get { return mockOrigineServerTs } + set { mockOrigineServerTs = newValue } + + } + } @@ -80,7 +87,7 @@ class FakeRoomMember: MXRoomMember { get { return mockUserId } set { mockUserId = newValue } } - + } @@ -107,3 +114,232 @@ class FakeRoomMembers: MXRoomMembers { } } + +class FakeSession: MXSession { + + var mockCrypto: MXCrypto? = FakeCrypto() + + var mockUserId: String = "@alice:localhost" + + init(mockCrypto: MXCrypto? = nil) { + self.mockCrypto = mockCrypto + super.init() + } + + override var crypto: MXCrypto? { + get { + return mockCrypto + } + set { + // nothing + } + } + + override var myUserId: String! { + get { + return mockUserId + } + set { + mockUserId = newValue + } + } +} + +class FakeCrypto: NSObject, MXCrypto { + + + var version: String = "" + + var deviceCurve25519Key: String? + + var deviceEd25519Key: String? + + var deviceCreationTs: UInt64 = 0 + + var backup: MXKeyBackup? + + var keyVerificationManager: MXKeyVerificationManager = FakeKeyVerificationManager() + + var crossSigning: MXCrossSigning = FakeCrossSigning() + + var recoveryService: MXRecoveryService! = nil + + var dehydrationService: MatrixSDK.DehydrationService! = nil + + override init() { + super.init() + } + + func start(_ onComplete: (() -> Void)?, failure: ((Swift.Error) -> Void)? = nil) { + + } + + func close(_ deleteStore: Bool) { + + } + + func isRoomEncrypted(_ roomId: String) -> Bool { + return true + } + + func encryptEventContent(_ eventContent: [AnyHashable : Any], withType eventType: String, in room: MXRoom, success: (([AnyHashable : Any], String) -> Void)?, failure: ((Swift.Error) -> Void)? = nil) -> MXHTTPOperation? { + return nil + } + + func decryptEvents(_ events: [MXEvent], inTimeline timeline: String?, onComplete: (([MXEventDecryptionResult]) -> Void)? = nil) { + + } + + func ensureEncryption(inRoom roomId: String, success: (() -> Void)?, failure: ((Swift.Error) -> Void)? = nil) -> MXHTTPOperation? { + return nil + } + + func eventDeviceInfo(_ event: MXEvent) -> MXDeviceInfo? { + return nil + } + + func discardOutboundGroupSessionForRoom(withRoomId roomId: String, onComplete: (() -> Void)? = nil) { + + } + + func handle(_ syncResponse: MXSyncResponse, onComplete: @escaping () -> Void) { + + } + + func setDeviceVerification(_ verificationStatus: MXDeviceVerification, forDevice deviceId: String, ofUser userId: String, success: (() -> Void)?, failure: ((Swift.Error) -> Void)? = nil) { + + } + + func setUserVerification(_ verificationStatus: Bool, forUser userId: String, success: (() -> Void)?, failure: ((Swift.Error) -> Void)? = nil) { + + } + + func trustLevel(forUser userId: String) -> MXUserTrustLevel { + return MXUserTrustLevel.init(crossSigningVerified: true, locallyVerified: true) + } + + func deviceTrustLevel(forDevice deviceId: String, ofUser userId: String) -> MXDeviceTrustLevel? { + return nil + } + + func trustLevelSummary(forUserIds userIds: [String], forceDownload: Bool, success: ((MXUsersTrustLevelSummary?) -> Void)?, failure: ((Swift.Error) -> Void)? = nil) { + + } + + func downloadKeys(_ userIds: [String], forceDownload: Bool, success: ((MXUsersDevicesMap?, [String : MXCrossSigningInfo]?) -> Void)?, failure: ((Swift.Error) -> Void)? = nil) -> MXHTTPOperation? { + return nil + } + + func devices(forUser userId: String) -> [String : MXDeviceInfo] { + return [:]; + } + + func device(withDeviceId deviceId: String, ofUser userId: String) -> MXDeviceInfo? { + return nil + } + + func exportRoomKeys(withPassword password: String, success: ((Data) -> Void)?, failure: ((Swift.Error) -> Void)? = nil) { + + } + + func importRoomKeys(_ keyFile: Data, withPassword password: String, success: ((UInt, UInt) -> Void)?, failure: ((Swift.Error) -> Void)? = nil) { + + } + + func reRequestRoomKey(for event: MXEvent) { + + } + + var globalBlacklistUnverifiedDevices: Bool = false + + func isBlacklistUnverifiedDevices(inRoom roomId: String) -> Bool { + return false + } + + func setBlacklistUnverifiedDevicesInRoom(_ roomId: String, blacklist: Bool) { + + } + +} + + +class FakeCrossSigning: NSObject, MXCrossSigning { + + + override init() { + super.init() + } + + var state: MXCrossSigningState = MXCrossSigningState.trustCrossSigning + + var myUserCrossSigningKeys: MXCrossSigningInfo? = nil + + var canTrustCrossSigning: Bool = true + + var canCrossSign: Bool = true + + var hasAllPrivateKeys: Bool = true + + func refreshState(success: ((Bool) -> Void)?, failure: ((Swift.Error) -> Void)? = nil) { + + } + + func setup(withPassword password: String, success: @escaping () -> Void, failure: @escaping (Swift.Error) -> Void) { + + } + + func setup(withAuthParams authParams: [AnyHashable : Any], success: @escaping () -> Void, failure: @escaping (Swift.Error) -> Void) { + + } + + func crossSignDevice(withDeviceId deviceId: String, userId: String, success: @escaping () -> Void, failure: @escaping (Swift.Error) -> Void) { + + } + + func signUser(withUserId userId: String, success: @escaping () -> Void, failure: @escaping (Swift.Error) -> Void) { + + } + + func crossSigningKeys(forUser userId: String) -> MXCrossSigningInfo? { + return nil + } + +} + +class FakeKeyVerificationManager: NSObject, MXKeyVerificationManager { + + override init() { + super.init() + } + + func requestVerificationByToDevice(withUserId userId: String, deviceIds: [String]?, methods: [String], success: @escaping (MXKeyVerificationRequest) -> Void, failure: @escaping (Swift.Error) -> Void) { + + } + + func requestVerificationByDM(withUserId userId: String, roomId: String?, fallbackText: String, methods: [String], success: @escaping (MXKeyVerificationRequest) -> Void, failure: @escaping (Swift.Error) -> Void) { + + } + + var pendingRequests: [MXKeyVerificationRequest] = [] + + func beginKeyVerification(from request: MXKeyVerificationRequest, method: String, success: @escaping (MXKeyVerificationTransaction) -> Void, failure: @escaping (Swift.Error) -> Void) { + + } + + func transactions(_ complete: @escaping ([MXKeyVerificationTransaction]) -> Void) { + + } + + func keyVerification(fromKeyVerificationEvent event: MXEvent, roomId: String, success: @escaping (MXKeyVerification) -> Void, failure: @escaping (Swift.Error) -> Void) -> MXHTTPOperation? { + return nil + } + + func qrCodeTransaction(withTransactionId transactionId: String) -> MXQRCodeTransaction? { + return nil + } + + func removeQRCodeTransaction(withTransactionId transactionId: String) { + + } + +} From a5d4d1c93c0f2561e7150535a796709b6db9ed9b Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 5 Apr 2024 09:53:07 +0200 Subject: [PATCH 03/14] Telemetry | Add more properties to posthog utd errors --- .../DecryptionFailure+Analytics.swift | 7 +- .../Modules/Analytics/DecryptionFailure.swift | 8 ++ .../Analytics/DecryptionFailureTracker.swift | 11 +++ RiotTests/DecryptionFailureTrackerTests.swift | 91 ++++++++++++++++--- 4 files changed, 102 insertions(+), 15 deletions(-) diff --git a/Riot/Modules/Analytics/DecryptionFailure+Analytics.swift b/Riot/Modules/Analytics/DecryptionFailure+Analytics.swift index 5ed3c36c2c..119366a665 100644 --- a/Riot/Modules/Analytics/DecryptionFailure+Analytics.swift +++ b/Riot/Modules/Analytics/DecryptionFailure+Analytics.swift @@ -42,14 +42,13 @@ extension DecryptionFailure { cryptoModule: .Rust, cryptoSDK: .Rust, domain: .E2EE, - eventLocalAgeMillis: self.eventLocalAgeMillis, - isFederated: nil, - isMatrixDotOrg: nil, + isFederated: self.isFederated, + isMatrixDotOrg: self.isMatrixOrg, name: errorName, timeToDecryptMillis: timeToDecryptMillis, userTrustsOwnIdentity: self.trustOwnIdentityAtTimeOfFailure, - wasVisibleToUser: nil + wasVisibleToUser: self.wasVisibleToUser ) } } diff --git a/Riot/Modules/Analytics/DecryptionFailure.swift b/Riot/Modules/Analytics/DecryptionFailure.swift index 6d3f6c1a83..179509087e 100644 --- a/Riot/Modules/Analytics/DecryptionFailure.swift +++ b/Riot/Modules/Analytics/DecryptionFailure.swift @@ -52,6 +52,14 @@ import AnalyticsEvents var eventLocalAgeMillis: Int? + /// Is the current user on matrix org + var isMatrixOrg: Bool? + /// Are the sender and recipient on the same homeserver + var isFederated: Bool? + + /// As for now the ios App only reports UTDs visible to user (error are reported from EventFormatter + var wasVisibleToUser: Bool = true + init(failedEventId: String, reason: DecryptionFailureReason, context: String, ts: TimeInterval) { self.failedEventId = failedEventId self.reason = reason diff --git a/Riot/Modules/Analytics/DecryptionFailureTracker.swift b/Riot/Modules/Analytics/DecryptionFailureTracker.swift index 503c733ba3..0832476ffe 100644 --- a/Riot/Modules/Analytics/DecryptionFailureTracker.swift +++ b/Riot/Modules/Analytics/DecryptionFailureTracker.swift @@ -103,6 +103,17 @@ class DecryptionFailureTracker: NSObject { failure.eventLocalAgeMillis = Int(exactly: eventRelativeAgeMillis) failure.trustOwnIdentityAtTimeOfFailure = isSessionVerified + let myDomain = userId.components(separatedBy: ":")[1] + failure.isMatrixOrg = myDomain == "matrix.org" + + if MXTools.isMatrixUserIdentifier(event.sender) { + let senderDomain = event.sender.components(separatedBy: ":")[1] + failure.isFederated = senderDomain != myDomain + } + + /// XXX for future work, as for now only the event formatter reports UTDs. That means that it's only UTD ~visible to users + failure.wasVisibleToUser = true + reportedFailures[failedEventId] = failure diff --git a/RiotTests/DecryptionFailureTrackerTests.swift b/RiotTests/DecryptionFailureTrackerTests.swift index c142514cb0..d37b96f081 100644 --- a/RiotTests/DecryptionFailureTrackerTests.swift +++ b/RiotTests/DecryptionFailureTrackerTests.swift @@ -56,7 +56,7 @@ class DecryptionFailureTrackerTests: XCTestCase { func test_grace_period() { - let myUser = "test@example.com"; + let myUser = "@test:example.com"; fakeSession.mockUserId = myUser; let decryptionFailureTracker = DecryptionFailureTracker(); @@ -95,7 +95,7 @@ class DecryptionFailureTrackerTests: XCTestCase { func test_report_ratcheted_key_utd() { - let myUser = "test@example.com"; + let myUser = "@test:example.com"; fakeSession.mockUserId = myUser; let decryptionFailureTracker = DecryptionFailureTracker(); @@ -125,7 +125,7 @@ class DecryptionFailureTrackerTests: XCTestCase { func test_report_unspecified_error() { - let myUser = "test@example.com"; + let myUser = "@test:example.com"; fakeSession.mockUserId = myUser; let decryptionFailureTracker = DecryptionFailureTracker(); @@ -157,7 +157,7 @@ class DecryptionFailureTrackerTests: XCTestCase { func test_do_not_double_report() { - let myUser = "test@example.com"; + let myUser = "@test:example.com"; fakeSession.mockUserId = myUser; let decryptionFailureTracker = DecryptionFailureTracker(); @@ -199,7 +199,7 @@ class DecryptionFailureTrackerTests: XCTestCase { func test_ignore_not_member() { - let myUser = "test@example.com"; + let myUser = "@test:example.com"; fakeSession.mockUserId = myUser; let decryptionFailureTracker = DecryptionFailureTracker(); @@ -234,7 +234,7 @@ class DecryptionFailureTrackerTests: XCTestCase { func test_notification_center() { - let myUser = "test@example.com"; + let myUser = "@test:example.com"; fakeSession.mockUserId = myUser; let decryptionFailureTracker = DecryptionFailureTracker(); @@ -275,7 +275,7 @@ class DecryptionFailureTrackerTests: XCTestCase { func test_should_report_late_decrypt() { - let myUser = "test@example.com"; + let myUser = "@test:example.com"; fakeSession.mockUserId = myUser; let decryptionFailureTracker = DecryptionFailureTracker(); @@ -320,7 +320,7 @@ class DecryptionFailureTrackerTests: XCTestCase { func test_should_report_permanent_decryption_error() { - let myUser = "test@example.com"; + let myUser = "@test:example.com"; fakeSession.mockUserId = myUser; let decryptionFailureTracker = DecryptionFailureTracker(); @@ -361,7 +361,7 @@ class DecryptionFailureTrackerTests: XCTestCase { func test_should_report_trust_status_at_decryption_time() { - let myUser = "test@example.com"; + let myUser = "@test:example.com"; fakeSession.mockUserId = myUser; let decryptionFailureTracker = DecryptionFailureTracker(); @@ -425,7 +425,7 @@ class DecryptionFailureTrackerTests: XCTestCase { func test_should_report_event_age() { - let myUser = "test@example.com"; + let myUser = "@test:example.com"; fakeSession.mockUserId = myUser; let format = DateFormatter() @@ -477,7 +477,7 @@ class DecryptionFailureTrackerTests: XCTestCase { func test_should_report_expected_utds() { - let myUser = "test@example.com"; + let myUser = "@test:example.com"; fakeSession.mockUserId = myUser; let format = DateFormatter() @@ -529,5 +529,74 @@ class DecryptionFailureTrackerTests: XCTestCase { } + + func test_should_report_is_matrix_org_and_is_federated() { + + let myUser = "@test:example.com"; + fakeSession.mockUserId = myUser; + + let decryptionFailureTracker = DecryptionFailureTracker(); + decryptionFailureTracker.timeProvider = timeShifter; + + let testDelegate = AnalyticsDelegate(); + + decryptionFailureTracker.delegate = testDelegate; + + timeShifter.timestamp = TimeInterval(0) + + let fakeEvent = FakeEvent(id: "$0000"); + fakeEvent.sender = "@bob:example.com" + fakeEvent.decryptionError = NSError(domain: MXDecryptingErrorDomain, code: Int(MXDecryptingErrorUnknownInboundSessionIdCode.rawValue)) + + // set session as not yet verified + fakeCrossSigning.canTrustCrossSigning = false + + let fakeRoomState = FakeRoomState(); + fakeRoomState.mockMembers = FakeRoomMembers(joined: [myUser]) + + decryptionFailureTracker.reportUnableToDecryptError(forEvent: fakeEvent, withRoomState: fakeRoomState, mySession: fakeSession); + + // Simulate succesful decryption after max wait + timeShifter.timestamp = TimeInterval(70) + + decryptionFailureTracker.checkFailures(); + + XCTAssertEqual(testDelegate.reportedFailure?.isMatrixOrg, false); + XCTAssertEqual(testDelegate.reportedFailure?.isFederated, false); + + + let analyticsError = testDelegate.reportedFailure!.toAnalyticsEvent() + + XCTAssertEqual(analyticsError.isMatrixDotOrg, false) + XCTAssertEqual(analyticsError.isFederated, false) + + // Report a new error now that session is verified + + let fakeEvent2 = FakeEvent(id: "$0001"); + fakeEvent2.sender = "@bob:example.com" + fakeEvent2.decryptionError = NSError(domain: MXDecryptingErrorDomain, code: Int(MXDecryptingErrorUnknownInboundSessionIdCode.rawValue)) + + fakeSession.mockUserId = "@test:matrix.org"; + fakeRoomState.mockMembers = FakeRoomMembers(joined: [fakeSession.mockUserId]) + + decryptionFailureTracker.reportUnableToDecryptError(forEvent: fakeEvent2, withRoomState: fakeRoomState, mySession: fakeSession); + + // Simulate permanent UTD + timeShifter.timestamp = TimeInterval(140) + + decryptionFailureTracker.checkFailures(); + + XCTAssertEqual(testDelegate.reportedFailure?.failedEventId, "$0001"); + XCTAssertEqual(testDelegate.reportedFailure?.isMatrixOrg, true); + XCTAssertEqual(testDelegate.reportedFailure?.isFederated, true); + + let analyticsError2 = testDelegate.reportedFailure!.toAnalyticsEvent() + + XCTAssertEqual(analyticsError2.isMatrixDotOrg, true) + XCTAssertEqual(analyticsError2.isFederated, true) + + } + + } From afd6deea86b052e933f2e7f6c13cb79f3bd59cd8 Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 15 Apr 2024 10:00:19 +0200 Subject: [PATCH 04/14] review use array last for safety --- Riot/Modules/Analytics/DecryptionFailureTracker.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Riot/Modules/Analytics/DecryptionFailureTracker.swift b/Riot/Modules/Analytics/DecryptionFailureTracker.swift index 0832476ffe..d76a38ca59 100644 --- a/Riot/Modules/Analytics/DecryptionFailureTracker.swift +++ b/Riot/Modules/Analytics/DecryptionFailureTracker.swift @@ -103,12 +103,12 @@ class DecryptionFailureTracker: NSObject { failure.eventLocalAgeMillis = Int(exactly: eventRelativeAgeMillis) failure.trustOwnIdentityAtTimeOfFailure = isSessionVerified - let myDomain = userId.components(separatedBy: ":")[1] + let myDomain = userId.components(separatedBy: ":").last failure.isMatrixOrg = myDomain == "matrix.org" if MXTools.isMatrixUserIdentifier(event.sender) { - let senderDomain = event.sender.components(separatedBy: ":")[1] - failure.isFederated = senderDomain != myDomain + let senderDomain = event.sender.components(separatedBy: ":").last + failure.isFederated = senderDomain != nil && senderDomain != myDomain } /// XXX for future work, as for now only the event formatter reports UTDs. That means that it's only UTD ~visible to users From af8c5fdec9235da18d0d08566dd6d144dc626489 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Mesk=C3=B3?= Date: Tue, 2 Apr 2024 15:52:22 +0000 Subject: [PATCH 05/14] Translated using Weblate (Hungarian) Currently translated at 98.9% (2390 of 2416 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/hu/ --- Riot/Assets/hu.lproj/Vector.strings | 1 + 1 file changed, 1 insertion(+) diff --git a/Riot/Assets/hu.lproj/Vector.strings b/Riot/Assets/hu.lproj/Vector.strings index 274042639a..cc1a8dc8b3 100644 --- a/Riot/Assets/hu.lproj/Vector.strings +++ b/Riot/Assets/hu.lproj/Vector.strings @@ -2740,3 +2740,4 @@ "key_verification_self_verify_security_upgrade_alert_title" = "Alkalmazás frissítve"; "settings_acceptable_use" = "Elfogadható felhasználói feltételek"; +"room_creation_user_not_found_prompt_title" = "Megerősítés"; From b3ad2c1e1b879dab83be4a2568eb066eafdc2a75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Mesk=C3=B3?= Date: Wed, 3 Apr 2024 11:27:03 +0000 Subject: [PATCH 06/14] Translated using Weblate (Hungarian) Currently translated at 99.2% (2399 of 2416 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/hu/ --- Riot/Assets/hu.lproj/Vector.strings | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Riot/Assets/hu.lproj/Vector.strings b/Riot/Assets/hu.lproj/Vector.strings index cc1a8dc8b3..21632cfec5 100644 --- a/Riot/Assets/hu.lproj/Vector.strings +++ b/Riot/Assets/hu.lproj/Vector.strings @@ -2741,3 +2741,14 @@ "key_verification_self_verify_security_upgrade_alert_title" = "Alkalmazás frissítve"; "settings_acceptable_use" = "Elfogadható felhasználói feltételek"; "room_creation_user_not_found_prompt_title" = "Megerősítés"; +"room_creation_user_not_found_prompt_message" = "Nem található profil ehhez a Matrix-azonosítóhoz. Mindenképp elindítja a privát csevegést?"; +"room_creation_user_not_found_prompt_invite_action" = "Privát csevegés mindenképp"; +"room_participants_invite_unknown_participant_prompt_to_msg" = "Nem található profil ehhez a Matrix-azonosítóhoz. Mindenképp meghívja %@ felhasználót a(z) %@ szobába?"; +"room_participants_invite_anyway" = "Meghívás mindenképp"; + +// Room commands descriptions +"room_command_change_display_name_description" = "Megváltoztatja a megjelenítendő becenevét"; +"room_command_emote_description" = "Megjeleníti a műveletet"; +"room_command_join_room_description" = "Csatlakozik a megadott című szobához"; +"room_command_part_room_description" = "Elhagyja a szobát"; +"room_command_invite_user_description" = "Meghívja az adott azonosítójú felhasználót a jelenlegi szobába"; From 05f91376973872883e00d8e47b9d46f403b0ed76 Mon Sep 17 00:00:00 2001 From: Danial Behzadi Date: Sat, 13 Apr 2024 16:26:53 +0000 Subject: [PATCH 07/14] Translated using Weblate (Persian) Currently translated at 45.6% (1103 of 2416 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/fa/ --- Riot/Assets/fa.lproj/Vector.strings | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/Riot/Assets/fa.lproj/Vector.strings b/Riot/Assets/fa.lproj/Vector.strings index a7f7b720c1..489c8540db 100644 --- a/Riot/Assets/fa.lproj/Vector.strings +++ b/Riot/Assets/fa.lproj/Vector.strings @@ -97,7 +97,7 @@ "contacts_address_book_matrix_users_toggle" = "فقط کاربران ماتریس"; "directory_search_results_more_than" = ">%tu نتیجه برای %@ یافت شد"; "directory_search_results" = "%tu نتیجه برای %@ یافت شد"; -"search_in_progress" = "در حال جست وجو"; +"search_in_progress" = "جوییدن…"; "search_no_result" = "نتیچه ای یافت نشد"; "search_people_placeholder" = "جستجو بر اساس شناسه کاربری، نام یا آدرس ایمیل"; "search_default_placeholder" = "جست و جو"; @@ -246,7 +246,7 @@ "room_accessibility_search" = "جستجو"; "room_message_edits_history_title" = "ویرایش های پیام"; "room_resource_usage_limit_reached_message_2" = "برخی از کاربران نمی توانند وارد سیستم شوند."; -"room_resource_limit_exceeded_message_contact_3" = " برای ادامه استفاده از این سرویس"; +"room_resource_limit_exceeded_message_contact_3" = " برای ادامه استفاده از این خدمت."; "room_resource_limit_exceeded_message_contact_2_link" = "با سرپرست سرویس خود تماس بگیرید"; "room_resource_limit_exceeded_message_contact_1" = " لطفا "; "room_predecessor_link" = "برای دیدن پیام‌های قدیمی‌تر به اینجاضربه بزنید."; @@ -416,7 +416,7 @@ "event_formatter_call_retry" = "تلاش مجدد"; "event_formatter_call_answer" = "پاسخ"; "event_formatter_call_decline" = "رد تماس"; -"event_formatter_call_back" = "تماس"; +"event_formatter_call_back" = "پاسخ تماس"; "event_formatter_call_connection_failed" = "ارتباط ناموفق بود"; "event_formatter_call_missed_video" = "تماس ویدیویی از دست رفته"; "event_formatter_call_missed_voice" = "تماس صوتی از دست رفته"; @@ -464,7 +464,7 @@ "group_participants_invited_section" = "دعوت شده"; "group_participants_invite_malformed_id_title" = "خطای دعوت"; "group_participants_invite_another_user" = "جستجو / دعوت با شناسه کاربری یا نام"; -"group_participants_filter_members" = "اعضای انجمن را فیلتر کنید"; +"group_participants_filter_members" = "پالایش اعضای اجتماع"; "group_participants_invite_prompt_msg" = "آیا مطمئنید که می خواهید %@ را به این گروه دعوت کنید؟"; "group_participants_invite_prompt_title" = "تایید"; "group_participants_remove_prompt_msg" = "آیا مطمئنید که می خواهید %@ را از این گروه حذف کنید؟"; @@ -570,7 +570,7 @@ "room_details_files" = "بارگذاری شده ها"; "room_details_people" = "اعضا"; "room_details_title_for_dm" = "جزییات"; -"identity_server_settings_alert_disconnect_still_sharing_3pid_button" = "به هر حال قطع کن"; +"identity_server_settings_alert_disconnect_still_sharing_3pid_button" = "قطع شدن به هر روی"; "identity_server_settings_alert_disconnect_button" = "قطع کردن"; "identity_server_settings_disconnect" = "قطع کردن"; "identity_server_settings_change" = "تغییر دادن"; @@ -652,7 +652,7 @@ "settings_confirm_password" = "تایید رمز"; "settings_new_password" = "رمز جدید"; "settings_old_password" = "رمز قدیمی"; -"settings_third_party_notices" = "اعلامیه های شخص ثالث"; +"settings_third_party_notices" = "یادآوری‌های سوم‌شخص"; "settings_privacy_policy" = "سیاست حفظ حریم خصوصی"; "settings_term_conditions" = "شرایط و ضوابط"; "settings_copyright" = "کپی رایت"; @@ -684,7 +684,7 @@ "settings_room_upgrades" = "ارتقاء اتاق"; "settings_messages_by_a_bot" = "پیام های ربات"; "settings_call_invitations" = "دعوت نامه های تماس"; -"settings_room_invitations" = "دعوت نامه های اتاق"; +"settings_room_invitations" = "دعوت‌های اتاق"; "settings_messages_containing_keywords" = "کلمات کلیدی"; "settings_messages_containing_at_room" = "@ اتاق"; "settings_messages_containing_user_name" = "نام کاربری من"; @@ -694,13 +694,13 @@ "settings_encrypted_direct_messages" = "پیام های مستقیم رمزگذاری شده"; "settings_direct_messages" = "پیام مستقیم"; "settings_notify_me_for" = "به من اطلاع بده برای"; -"settings_mentions_and_keywords" = "ذکر و کلمات کلیدی"; +"settings_mentions_and_keywords" = "نام‌بری و کلیدواژگان"; "settings_default" = "اعلان های پیش فرض"; "settings_notifications_disabled_alert_message" = "برای فعال کردن اعلان‌ها، به تنظیمات دستگاه خود بروید."; "settings_notifications_disabled_alert_title" = "غیر فعال کردن اعلان ها"; "settings_pin_rooms_with_missed_notif" = "پین کردن اتاق هایی با اعلان های از دست رفته"; "settings_pin_rooms_with_unread" = "پین کردن اتاق هایی با پیام های خوانده نشده"; -"settings_global_settings_info" = "تنظیمات اعلان جهانی در سرویس گیرنده وب %@ شما موجود است"; +"settings_global_settings_info" = "تنظیمات آگاهی عمومی در کارخواه وب %@تان موجود است"; "settings_show_decrypted_content" = "نمایش محتوای رمزگشایی شده"; "settings_device_notifications" = "اعلان های دستگاه"; "settings_enable_push_notif" = "اعلان ها در این دستگاه"; @@ -1346,3 +1346,5 @@ "onboarding_splash_page_2_title" = "تحت کنترل شماست."; "onboarding_splash_page_1_message" = "یک ارتباط امن و مستقل که سطح حریم شخصی آن دقیقا مشابه ارتباط رو در رو در منزل شماست."; "accessibility_selected" = "انتخاب شده"; +"room_multiple_typing_notification" = "٪@ و دیگران"; +"room_title_members" = "%@ عضو"; From 41a4caf11e5c838ed8f3efa78b5eab70dd6bc426 Mon Sep 17 00:00:00 2001 From: r2d2sq13 Date: Mon, 22 Apr 2024 13:57:57 +0000 Subject: [PATCH 08/14] Translated using Weblate (Georgian) Currently translated at 2.7% (66 of 2416 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/ka/ --- Riot/Assets/ka.lproj/Vector.strings | 76 +++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/Riot/Assets/ka.lproj/Vector.strings b/Riot/Assets/ka.lproj/Vector.strings index 8b13789179..8c20e5dc3b 100644 --- a/Riot/Assets/ka.lproj/Vector.strings +++ b/Riot/Assets/ka.lproj/Vector.strings @@ -1 +1,77 @@ + +// String for App Store +"store_short_description" = "უსაფრთხო დეცენტრალიზებული ჩატი/ვოიპი"; + +// Titles +"title_home" = "მთავარი"; +"title_favourites" = "ფავორიტები"; +"title_people" = "ხალხი"; +"title_rooms" = "ოთახები"; +"title_groups" = "თემები"; +"warning" = "გაფრთხილება"; + +// Actions +"view" = "ხედი"; +"next" = "შემდეგი"; +"back" = "უკან"; +"continue" = "გაგრძელება"; +"create" = "შექმნა"; +"start" = "აწყება"; +"leave" = "დატოვება"; +"remove" = "წაშლა"; +"retry" = "გამეორება"; +"on" = "ჩართული"; +"off" = "გამორთული"; +"enable" = "ჩართვა"; +"save" = "შენახვა"; +"join" = "შეუერთდება"; +"decline" = "უარყოფა"; +"accept" = "მიღება"; +"camera" = "კამერა"; +"voice" = "ხმა"; +"video" = "ვიდეო"; +"active_call" = "აქტიური ზარი"; +"later" = "შემდეგ"; +"rename" = "გადარქმევა"; +"collapse" = "ჩაკეცვა"; +"send_to" = "გაგზავნა %@-ს"; +"close" = "დახურვა"; +"skip" = "გამოტოვება"; +"joined" = "შეუერთდა"; +"switch" = "გადართვა"; +"more" = "მეტი"; +"less" = "ნაკლები"; +"open" = "გახსნა"; +"private" = "ირადი"; +"public" = "საჯარო"; +"stop" = "შეჩერება"; +"new_word" = "ახალი"; +"existing" = "არსებული"; +"add" = "დამატება"; +"ok" = "კარგი"; +"error" = "შეცდომა"; +"suggest" = "შესთავაზება"; +"confirm" = "დადასტურება"; +"invite_to" = "მიწვევა %@-ში"; + +// Activities +"loading" = "ჩატვირთვა"; +"sending" = "გაგზავნა"; +"callbar_active_and_single_paused" = "1 აქტიური ზარი (%@) · 1 შეჩერებული ზარი"; +"callbar_active_and_multiple_paused" = "1 აქტიური ზარი (%@) · %@ შეჩერებული ზარები"; +"callbar_only_single_paused" = "შეჩერებული ზარი"; +"callbar_return" = "დაბრუნება"; +"store_promotional_text" = "კონფიდენციალობის დაცვით ჩატისა და თანამშრომლობის აპლიკაცია, ღია ქსელზე. დეცენტრალიზებული, რათა მართვა გადაგიცემათ. არაა დათამაინინგი, არაა უკანა კარები და არაა მესამე მხარის წვდომა."; +"invite" = "მიწვევა"; +"cancel" = "გაუქმება"; +"preview" = "წინასწარი ხილვა"; +"active_call_details" = "აქტიური ზარი (%@)"; +"joining" = "შეერთება"; +"done" = "დასრულებული"; +"edit" = "რედაქტირება"; +"saving" = "შენახვა"; + +// Call Bar +"callbar_only_single_active" = "შეეხეთ, რომ დაბრუნდეთ ზარში (%@)"; +"callbar_only_multiple_paused" = "%@ შეჩერებული ზარები"; From 37d9c9075ac0113bac254b7ca0acef5452a43258 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Mesk=C3=B3?= Date: Thu, 25 Apr 2024 10:57:49 +0000 Subject: [PATCH 09/14] Translated using Weblate (Hungarian) Currently translated at 99.4% (2402 of 2416 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/hu/ --- Riot/Assets/hu.lproj/Vector.strings | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Riot/Assets/hu.lproj/Vector.strings b/Riot/Assets/hu.lproj/Vector.strings index 21632cfec5..c965d650ce 100644 --- a/Riot/Assets/hu.lproj/Vector.strings +++ b/Riot/Assets/hu.lproj/Vector.strings @@ -2752,3 +2752,6 @@ "room_command_join_room_description" = "Csatlakozik a megadott című szobához"; "room_command_part_room_description" = "Elhagyja a szobát"; "room_command_invite_user_description" = "Meghívja az adott azonosítójú felhasználót a jelenlegi szobába"; +"room_command_kick_user_description" = "Eltávolítja az adott azonosítójú felhasználót ebből a szobából"; +"room_command_ban_user_description" = "Kitiltja az adott azonosítójú felhasználót"; +"room_command_unban_user_description" = "Feloldja az adott azonosítójú felhasználó kitiltását"; From a4d40a1ef690095d95a164d5d9777264c01c19d9 Mon Sep 17 00:00:00 2001 From: Doug Date: Wed, 1 May 2024 09:54:29 +0100 Subject: [PATCH 10/14] Remove invalid string. --- Riot/Assets/fa.lproj/Vector.strings | 1 - 1 file changed, 1 deletion(-) diff --git a/Riot/Assets/fa.lproj/Vector.strings b/Riot/Assets/fa.lproj/Vector.strings index 489c8540db..4948e2e90a 100644 --- a/Riot/Assets/fa.lproj/Vector.strings +++ b/Riot/Assets/fa.lproj/Vector.strings @@ -1346,5 +1346,4 @@ "onboarding_splash_page_2_title" = "تحت کنترل شماست."; "onboarding_splash_page_1_message" = "یک ارتباط امن و مستقل که سطح حریم شخصی آن دقیقا مشابه ارتباط رو در رو در منزل شماست."; "accessibility_selected" = "انتخاب شده"; -"room_multiple_typing_notification" = "٪@ و دیگران"; "room_title_members" = "%@ عضو"; From bd03fe6dca07e53ba35f550f21119973582a3bc5 Mon Sep 17 00:00:00 2001 From: Doug Date: Wed, 1 May 2024 10:41:03 +0100 Subject: [PATCH 11/14] changelog.d: Upgrade MatrixSDK version ([v0.27.7](https://github.com/matrix-org/matrix-ios-sdk/releases/tag/v0.27.7)). --- Podfile | 2 +- changelog.d/x-nolink-0.change | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 changelog.d/x-nolink-0.change diff --git a/Podfile b/Podfile index 9ad4fefa28..f2df4910e0 100644 --- a/Podfile +++ b/Podfile @@ -16,7 +16,7 @@ use_frameworks! # - `{ :specHash => {sdk spec hash}` to depend on specific pod options (:git => …, :podspec => …) for MatrixSDK repo. Used by Fastfile during CI # # Warning: our internal tooling depends on the name of this variable name, so be sure not to change it -$matrixSDKVersion = '= 0.27.6' +$matrixSDKVersion = '= 0.27.7' # $matrixSDKVersion = :local # $matrixSDKVersion = { :branch => 'develop'} # $matrixSDKVersion = { :specHash => { git: 'https://git.io/fork123', branch: 'fix' } } diff --git a/changelog.d/x-nolink-0.change b/changelog.d/x-nolink-0.change new file mode 100644 index 0000000000..ca236cb456 --- /dev/null +++ b/changelog.d/x-nolink-0.change @@ -0,0 +1 @@ +Upgrade MatrixSDK version ([v0.27.7](https://github.com/matrix-org/matrix-ios-sdk/releases/tag/v0.27.7)). \ No newline at end of file From 756de54f7a2bb3b56c899740ac0f6573d3cfe4fb Mon Sep 17 00:00:00 2001 From: Doug Date: Wed, 1 May 2024 10:41:03 +0100 Subject: [PATCH 12/14] version++ --- CHANGES.md | 7 +++++++ changelog.d/x-nolink-0.change | 1 - 2 files changed, 7 insertions(+), 1 deletion(-) delete mode 100644 changelog.d/x-nolink-0.change diff --git a/CHANGES.md b/CHANGES.md index eb5267b66d..2c1002292b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,10 @@ +## Changes in 1.11.10 (2024-05-01) + +🙌 Improvements + +- Upgrade MatrixSDK version ([v0.27.7](https://github.com/matrix-org/matrix-ios-sdk/releases/tag/v0.27.7)). + + ## Changes in 1.11.9 (2024-04-02) Others diff --git a/changelog.d/x-nolink-0.change b/changelog.d/x-nolink-0.change deleted file mode 100644 index ca236cb456..0000000000 --- a/changelog.d/x-nolink-0.change +++ /dev/null @@ -1 +0,0 @@ -Upgrade MatrixSDK version ([v0.27.7](https://github.com/matrix-org/matrix-ios-sdk/releases/tag/v0.27.7)). \ No newline at end of file From f4c979b368422b3b9fbc2306888e2730de27aca3 Mon Sep 17 00:00:00 2001 From: Doug Date: Wed, 1 May 2024 10:46:07 +0100 Subject: [PATCH 13/14] Changelog. --- CHANGES.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 2c1002292b..0e6621096e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,10 @@ - Upgrade MatrixSDK version ([v0.27.7](https://github.com/matrix-org/matrix-ios-sdk/releases/tag/v0.27.7)). +Others + +- Improvements to reporting of decryption failures. + ## Changes in 1.11.9 (2024-04-02) From 4e45dd116d1fa242c1822a1a3ec30b8a0f91d36a Mon Sep 17 00:00:00 2001 From: Doug Date: Wed, 1 May 2024 12:00:08 +0100 Subject: [PATCH 14/14] finish version++ --- Podfile.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Podfile.lock b/Podfile.lock index 07f1fb4d9f..c8298201a4 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -39,9 +39,9 @@ PODS: - LoggerAPI (1.9.200): - Logging (~> 1.1) - Logging (1.4.0) - - MatrixSDK (0.27.6): - - MatrixSDK/Core (= 0.27.6) - - MatrixSDK/Core (0.27.6): + - MatrixSDK (0.27.7): + - MatrixSDK/Core (= 0.27.7) + - MatrixSDK/Core (0.27.7): - AFNetworking (~> 4.0.0) - GZIP (~> 1.3.0) - libbase58 (~> 0.1.4) @@ -49,7 +49,7 @@ PODS: - OLMKit (~> 3.2.5) - Realm (= 10.27.0) - SwiftyBeaver (= 1.9.5) - - MatrixSDK/JingleCallStack (0.27.6): + - MatrixSDK/JingleCallStack (0.27.7): - JitsiMeetSDKLite (= 8.1.2-lite) - MatrixSDK/Core - MatrixSDKCrypto (0.3.13) @@ -102,8 +102,8 @@ DEPENDENCIES: - KeychainAccess (~> 4.2.2) - KTCenterFlowLayout (~> 1.3.1) - libPhoneNumber-iOS (~> 0.9.13) - - MatrixSDK (= 0.27.6) - - MatrixSDK/JingleCallStack (= 0.27.6) + - MatrixSDK (= 0.27.7) + - MatrixSDK/JingleCallStack (= 0.27.7) - OLMKit - PostHog (~> 2.0.0) - ReadMoreTextView (~> 3.0.1) @@ -187,7 +187,7 @@ SPEC CHECKSUMS: libPhoneNumber-iOS: 0a32a9525cf8744fe02c5206eb30d571e38f7d75 LoggerAPI: ad9c4a6f1e32f518fdb43a1347ac14d765ab5e3d Logging: beeb016c9c80cf77042d62e83495816847ef108b - MatrixSDK: 4129ab9c0acda1d0aad50b1c9765bd795b8d70b9 + MatrixSDK: e07b2309f3c6498c1df987441da7006d099c47a4 MatrixSDKCrypto: bf08b72f2cd015d8749420a2b8b92fc0536bedf4 OLMKit: da115f16582e47626616874e20f7bb92222c7a51 PostHog: 660ec6c9d80cec17b685e148f17f6785a88b597d @@ -208,6 +208,6 @@ SPEC CHECKSUMS: zxcvbn-ios: fef98b7c80f1512ff0eec47ac1fa399fc00f7e3c ZXingObjC: 8898711ab495761b2dbbdec76d90164a6d7e14c5 -PODFILE CHECKSUM: c87b532985dd755b373732f841e3bcfe616f4e4f +PODFILE CHECKSUM: 1197abec9c5affbef652747dd5cd6aaf00ef3a47 COCOAPODS: 1.14.3