mirror of
https://github.com/vector-im/element-ios.git
synced 2024-09-28 23:32:41 +00:00
Merge pull request #7056 from vector-im/aleksandrs/7043_unverified_sessions_alert
Unverified sessions alert
This commit is contained in:
commit
3dd03a1f13
13 changed files with 186 additions and 60 deletions
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
25
Riot/Categories/Date+Calculation.swift
Normal file
25
Riot/Categories/Date+Calculation.swift
Normal 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
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
@ -73,8 +73,6 @@ class AllChatsViewController: HomeViewController {
|
|||
|
||||
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() {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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]
|
||||
|
|
|
@ -20,5 +20,6 @@
|
|||
|
||||
+ (instancetype)instantiate;
|
||||
|
||||
- (void)showUserSessionsFlow;
|
||||
@end
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
66
RiotTests/ReviewSessionAlertSnoozeControllerTests.swift
Normal file
66
RiotTests/ReviewSessionAlertSnoozeControllerTests.swift
Normal 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
1
changelog.d/7056.feature
Normal file
|
@ -0,0 +1 @@
|
|||
Unverified sessions alert.
|
Loading…
Reference in a new issue