Merge pull request #7056 from vector-im/aleksandrs/7043_unverified_sessions_alert

Unverified sessions alert
This commit is contained in:
Aleksandrs Proskurins 2022-11-10 09:23:21 +02:00 committed by GitHub
commit 3dd03a1f13
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 186 additions and 60 deletions

View file

@ -437,4 +437,7 @@ final class BuildSettings: NSObject {
static let qrLoginEnableDisplayingQRs = false
static let rendezvousServerBaseURL = URL(string: "https://rendezvous.lab.element.dev/")!
// MARK: - Alerts
static let showUnverifiedSessionsAlert = true
}

View file

@ -1548,9 +1548,8 @@ Tap the + to start adding people.";
"key_verification_self_verify_current_session_alert_validate_action" = "Verify";
// Unverified sessions
"key_verification_self_verify_unverified_sessions_alert_title" = "Review where you're logged in";
"key_verification_self_verify_unverified_sessions_alert_message" = "Verify all your sessions to ensure your account & messages are safe.";
"key_verification_alert_title" = "You have unverified sessions";
"key_verification_alert_body" = "Review to ensure your account is safe.";
"key_verification_self_verify_unverified_sessions_alert_validate_action" = "Review";
// MARK: Self verification wait

View file

@ -0,0 +1,25 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
extension Date {
func daysBetween(date: Date) -> Int {
let components = Calendar.current.dateComponents([.day], from: self, to: date)
return components.day ?? 0
}
}

View file

@ -2923,6 +2923,14 @@ public class VectorL10n: NSObject {
public static var keyBackupSetupTitle: String {
return VectorL10n.tr("Vector", "key_backup_setup_title")
}
/// Review to ensure your account is safe.
public static var keyVerificationAlertBody: String {
return VectorL10n.tr("Vector", "key_verification_alert_body")
}
/// You have unverified sessions
public static var keyVerificationAlertTitle: String {
return VectorL10n.tr("Vector", "key_verification_alert_title")
}
/// You need to bootstrap cross-signing first.
public static var keyVerificationBootstrapNotSetupMessage: String {
return VectorL10n.tr("Vector", "key_verification_bootstrap_not_setup_message")
@ -3007,14 +3015,6 @@ public class VectorL10n: NSObject {
public static var keyVerificationSelfVerifyCurrentSessionAlertValidateAction: String {
return VectorL10n.tr("Vector", "key_verification_self_verify_current_session_alert_validate_action")
}
/// Verify all your sessions to ensure your account & messages are safe.
public static var keyVerificationSelfVerifyUnverifiedSessionsAlertMessage: String {
return VectorL10n.tr("Vector", "key_verification_self_verify_unverified_sessions_alert_message")
}
/// Review where you're logged in
public static var keyVerificationSelfVerifyUnverifiedSessionsAlertTitle: String {
return VectorL10n.tr("Vector", "key_verification_self_verify_unverified_sessions_alert_title")
}
/// Review
public static var keyVerificationSelfVerifyUnverifiedSessionsAlertValidateAction: String {
return VectorL10n.tr("Vector", "key_verification_self_verify_unverified_sessions_alert_validate_action")

View file

@ -203,9 +203,6 @@ final class RiotSettings: NSObject {
@UserDefault(key: "hideVerifyThisSessionAlert", defaultValue: false, storage: defaults)
var hideVerifyThisSessionAlert
@UserDefault(key: "hideReviewSessionsAlert", defaultValue: false, storage: defaults)
var hideReviewSessionsAlert
@UserDefault(key: "matrixApps", defaultValue: false, storage: defaults)
var matrixApps

View file

@ -638,6 +638,8 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
[self.pushNotificationService applicationDidBecomeActive];
[self configurePinCodeScreenFor:application createIfRequired:NO];
[self checkCrossSigningForSession:self.mxSessions.firstObject];
}
- (void)configurePinCodeScreenFor:(UIApplication *)application
@ -2193,6 +2195,8 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
// Reset analytics
[Analytics.shared reset];
[[[ReviewSessionAlertSnoozeController alloc] init] clearSnooze];
#ifdef MX_CALL_STACK_ENDPOINT
// Erase all created certificates and private keys by MXEndpointCallStack
for (MXKAccount *account in MXKAccountManager.sharedManager.accounts)

View file

@ -60,7 +60,7 @@ class AllChatsViewController: HomeViewController {
private let tableViewPaginationThrottler = MXThrottler(minimumDelay: 0.1)
private var reviewSessionAlertHasBeenDisplayed: Bool = false
private let reviewSessionAlertSnoozeController = ReviewSessionAlertSnoozeController()
private var bannerView: UIView? {
didSet {
@ -72,9 +72,7 @@ class AllChatsViewController: HomeViewController {
private var isOnboardingCoordinatorPreparing: Bool = false
private var allChatsOnboardingCoordinatorBridgePresenter: AllChatsOnboardingCoordinatorBridgePresenter?
private var currentAlert: UIAlertController?
@IBOutlet private var toolbar: UIToolbar!
private var isToolbarHidden: Bool = false {
didSet {
@ -821,12 +819,13 @@ extension AllChatsViewController: SplitViewMasterViewControllerProtocol {
/// - Parameters:
/// - session: the matrix session.
func presentVerifyCurrentSessionAlertIfNeeded(with session: MXSession) {
guard !RiotSettings.shared.hideVerifyThisSessionAlert, !reviewSessionAlertHasBeenDisplayed, !isOnboardingInProgress else {
guard !RiotSettings.shared.hideVerifyThisSessionAlert,
!isOnboardingInProgress,
presentedViewController == nil,
viewIfLoaded?.window != nil else {
return
}
reviewSessionAlertHasBeenDisplayed = true
// Force verification if required by the HS configuration
guard !session.vc_homeserverConfiguration().encryption.isSecureBackupRequired else {
MXLog.debug("[AllChatsViewController] presentVerifyCurrentSessionAlertIfNeededWithSession: Force verification of the device")
@ -842,21 +841,16 @@ extension AllChatsViewController: SplitViewMasterViewControllerProtocol {
/// - Parameters:
/// - session: the matrix session.
func presentReviewUnverifiedSessionsAlertIfNeeded(with session: MXSession) {
guard !RiotSettings.shared.hideReviewSessionsAlert, !reviewSessionAlertHasBeenDisplayed else {
guard BuildSettings.showUnverifiedSessionsAlert,
!reviewSessionAlertSnoozeController.isSnoozed(),
presentedViewController == nil,
viewIfLoaded?.window != nil else {
return
}
let devices = mainSession.crypto.devices(forUser: mainSession.myUserId).values
var userHasOneUnverifiedDevice = false
for device in devices {
if !device.trustLevel.isCrossSigningVerified {
userHasOneUnverifiedDevice = true
break
}
}
let userHasOneUnverifiedDevice = devices.contains(where: {!$0.trustLevel.isCrossSigningVerified})
if userHasOneUnverifiedDevice {
reviewSessionAlertHasBeenDisplayed = true
presentReviewUnverifiedSessionsAlert(with: session)
}
}
@ -968,8 +962,6 @@ extension AllChatsViewController: SplitViewMasterViewControllerProtocol {
private func presentVerifyCurrentSessionAlert(with session: MXSession) {
MXLog.debug("[AllChatsViewController] presentVerifyCurrentSessionAlertWithSession")
currentAlert?.dismiss(animated: true, completion: nil)
let alert = UIAlertController(title: VectorL10n.keyVerificationSelfVerifyCurrentSessionAlertTitle,
message: VectorL10n.keyVerificationSelfVerifyCurrentSessionAlertMessage,
preferredStyle: .alert)
@ -989,16 +981,13 @@ extension AllChatsViewController: SplitViewMasterViewControllerProtocol {
}))
self.present(alert, animated: true)
currentAlert = alert
}
private func presentReviewUnverifiedSessionsAlert(with session: MXSession) {
MXLog.debug("[AllChatsViewController] presentReviewUnverifiedSessionsAlert")
currentAlert?.dismiss(animated: true, completion: nil)
let alert = UIAlertController(title: VectorL10n.keyVerificationSelfVerifyUnverifiedSessionsAlertTitle,
message: VectorL10n.keyVerificationSelfVerifyUnverifiedSessionsAlertMessage,
let alert = UIAlertController(title: VectorL10n.keyVerificationAlertTitle,
message: VectorL10n.keyVerificationAlertBody,
preferredStyle: .alert)
alert.addAction(UIAlertAction(title: VectorL10n.keyVerificationSelfVerifyUnverifiedSessionsAlertValidateAction,
@ -1007,14 +996,11 @@ extension AllChatsViewController: SplitViewMasterViewControllerProtocol {
self.showSettingsSecurityScreen(with: session)
}))
alert.addAction(UIAlertAction(title: VectorL10n.later, style: .cancel))
alert.addAction(UIAlertAction(title: VectorL10n.doNotAskAgain, style: .destructive, handler: { action in
RiotSettings.shared.hideReviewSessionsAlert = true
alert.addAction(UIAlertAction(title: VectorL10n.later, style: .cancel, handler: { [weak self] _ in
self?.reviewSessionAlertSnoozeController.snooze()
}))
present(alert, animated: true)
currentAlert = alert
}
private func showSettingsSecurityScreen(with session: MXSession) {
@ -1030,7 +1016,12 @@ extension AllChatsViewController: SplitViewMasterViewControllerProtocol {
settingsViewController.loadViewIfNeeded()
AppDelegate.theDelegate().restoreInitialDisplay {
self.navigationController?.viewControllers = [self, settingsViewController, securityViewController]
if RiotSettings.shared.enableNewSessionManager {
self.navigationController?.viewControllers = [self, settingsViewController]
settingsViewController.showUserSessionsFlow()
} else {
self.navigationController?.viewControllers = [self, settingsViewController, securityViewController]
}
}
}
@ -1053,9 +1044,7 @@ extension AllChatsViewController: SplitViewMasterViewControllerProtocol {
}
private func resetReviewSessionsFlags() {
reviewSessionAlertHasBeenDisplayed = false
RiotSettings.shared.hideVerifyThisSessionAlert = false
RiotSettings.shared.hideReviewSessionsAlert = false
}
private func presentOnboardingFlow() {

View file

@ -0,0 +1,49 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
@objcMembers
class ReviewSessionAlertSnoozeController: NSObject {
private let userDefaults: UserDefaults
private let snoozeDateKey = "ReviewSessionAlertSnoozeController_snoozeDateKey"
private let minDaysBetweenAlerts = 7
// for Objective-C
convenience override init() {
self.init(userDefaults: UserDefaults.standard)
}
init(userDefaults: UserDefaults = UserDefaults.standard) {
self.userDefaults = userDefaults
}
func isSnoozed() -> Bool {
guard let lastDisplayedDate = userDefaults.object(forKey: snoozeDateKey) as? Date else {
return false
}
return lastDisplayedDate.daysBetween(date: Date()) <= minDaysBetweenAlerts
}
func snooze() {
userDefaults.set(Date(), forKey: snoozeDateKey)
}
func clearSnooze() {
userDefaults.removeObject(forKey: snoozeDateKey)
}
}

View file

@ -6762,8 +6762,8 @@ static CGSize kThreadListBarButtonItemImageSize;
[currentAlert dismissViewControllerAnimated:NO completion:nil];
UIAlertController *alert = [UIAlertController alertControllerWithTitle:[VectorL10n keyVerificationSelfVerifyUnverifiedSessionsAlertTitle]
message:[VectorL10n keyVerificationSelfVerifyUnverifiedSessionsAlertMessage]
UIAlertController *alert = [UIAlertController alertControllerWithTitle:[VectorL10n keyVerificationAlertTitle]
message:[VectorL10n keyVerificationAlertBody]
preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:[VectorL10n keyVerificationSelfVerifyUnverifiedSessionsAlertValidateAction]

View file

@ -20,5 +20,6 @@
+ (instancetype)instantiate;
- (void)showUserSessionsFlow;
@end

View file

@ -903,7 +903,7 @@
- (void)presentReviewUnverifiedSessionsAlertIfNeededWithSession:(MXSession*)session
{
if (RiotSettings.shared.hideReviewSessionsAlert || self.reviewSessionAlertHasBeenDisplayed)
if (self.reviewSessionAlertHasBeenDisplayed)
{
return;
}
@ -934,8 +934,8 @@
[currentAlert dismissViewControllerAnimated:NO completion:nil];
UIAlertController *alert = [UIAlertController alertControllerWithTitle:[VectorL10n keyVerificationSelfVerifyUnverifiedSessionsAlertTitle]
message:[VectorL10n keyVerificationSelfVerifyUnverifiedSessionsAlertMessage]
UIAlertController *alert = [UIAlertController alertControllerWithTitle:[VectorL10n keyVerificationAlertTitle]
message:[VectorL10n keyVerificationAlertBody]
preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:[VectorL10n keyVerificationSelfVerifyUnverifiedSessionsAlertValidateAction]
@ -948,13 +948,6 @@
style:UIAlertActionStyleCancel
handler:nil]];
[alert addAction:[UIAlertAction actionWithTitle:[VectorL10n doNotAskAgain]
style:UIAlertActionStyleDestructive
handler:^(UIAlertAction * action) {
RiotSettings.shared.hideReviewSessionsAlert = YES;
}]];
[self presentViewController:alert animated:YES completion:nil];
currentAlert = alert;
@ -975,7 +968,6 @@
{
self.reviewSessionAlertHasBeenDisplayed = NO;
RiotSettings.shared.hideVerifyThisSessionAlert = NO;
RiotSettings.shared.hideReviewSessionsAlert = NO;
}
#pragma mark - UITabBarDelegate

View file

@ -0,0 +1,66 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import XCTest
@testable import Element
final class ReviewSessionAlertSnoozeControllerTests: XCTestCase {
private let snoozeDateKey = "ReviewSessionAlertSnoozeController_snoozeDateKey"
private var userDefaults = UserDefaults(suiteName: "testSuit")!
private var sut: ReviewSessionAlertSnoozeController!
override func setUpWithError() throws {
userDefaults.removePersistentDomain(forName: "testSuit")
sut = ReviewSessionAlertSnoozeController(userDefaults: userDefaults)
}
func test_whenAlertNotSnoozedBefore_isSnoozedFalse() {
XCTAssertFalse(sut.isSnoozed())
}
func test_whenAlertSnoozedYesterday_isSnoozedTrue() {
let yesterday = Calendar.current.date(byAdding: .day, value: -1, to: Date())
userDefaults.set(yesterday, forKey: snoozeDateKey)
XCTAssertTrue(sut.isSnoozed())
}
func test_whenAlertSnoozed8DaysAgo_isSnoozedFalse() {
let eightDaysAgo = Calendar.current.date(byAdding: .day, value: -8, to: Date())
userDefaults.set(eightDaysAgo, forKey: snoozeDateKey)
XCTAssertFalse(sut.isSnoozed())
}
func test_whenAlertSnoozed7DaysAgo_isSnoozedTrue() {
let eightDaysAgo = Calendar.current.date(byAdding: .day, value: -7, to: Date())
userDefaults.set(eightDaysAgo, forKey: snoozeDateKey)
XCTAssertTrue(sut.isSnoozed())
}
func test_whenAlertSnoozed_isSnoozedTrue() {
XCTAssertFalse(sut.isSnoozed())
sut.snooze()
XCTAssertTrue(sut.isSnoozed())
}
func test_whenClearSnooze_isSnoozedFalse() {
sut.snooze()
XCTAssertTrue(sut.isSnoozed())
sut.clearSnooze()
XCTAssertFalse(sut.isSnoozed())
}
}

1
changelog.d/7056.feature Normal file
View file

@ -0,0 +1 @@
Unverified sessions alert.