diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index a533a2faa3..edf8ca3560 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -692,6 +692,7 @@ 88BFD4732335C2B3001797D7 /* waveformLoading.json in Resources */ = {isa = PBXBuildFile; fileRef = 88BFD4722335C2B3001797D7 /* waveformLoading.json */; }; 88C4E38024635337009C9B97 /* DeviceTransferService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88C4E37F24635337009C9B97 /* DeviceTransferService.swift */; }; 88C659B024688335002AC115 /* SelfSignedIdentity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88C659AF24688335002AC115 /* SelfSignedIdentity.swift */; }; + 88C7597324B7EAA600DB03EA /* AdvancedPinSettingsTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88C7597224B7EAA600DB03EA /* AdvancedPinSettingsTableViewController.swift */; }; 88D1D40222EBB5A100F472C5 /* MessageRequestView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88D1D40122EBB5A100F472C5 /* MessageRequestView.swift */; }; 88D1D40422EF8A9700F472C5 /* ThreadDetailsInteraction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88D1D40322EF8A9700F472C5 /* ThreadDetailsInteraction.swift */; }; 88D1D40622EF8F1100F472C5 /* ThreadDetailsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88D1D40522EF8F1100F472C5 /* ThreadDetailsCell.swift */; }; @@ -1699,6 +1700,7 @@ 88C483F424A3001C00D9A85A /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = translations/ja.lproj/InfoPlist.strings; sourceTree = ""; }; 88C4E37F24635337009C9B97 /* DeviceTransferService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceTransferService.swift; sourceTree = ""; }; 88C659AF24688335002AC115 /* SelfSignedIdentity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelfSignedIdentity.swift; sourceTree = ""; }; + 88C7597224B7EAA600DB03EA /* AdvancedPinSettingsTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdvancedPinSettingsTableViewController.swift; sourceTree = ""; }; 88D1D40122EBB5A100F472C5 /* MessageRequestView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRequestView.swift; sourceTree = ""; }; 88D1D40322EF8A9700F472C5 /* ThreadDetailsInteraction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadDetailsInteraction.swift; sourceTree = ""; }; 88D1D40522EF8F1100F472C5 /* ThreadDetailsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadDetailsCell.swift; sourceTree = ""; }; @@ -2013,6 +2015,7 @@ children = ( 340FC884204DAC8C007AEB0F /* AboutTableViewController.h */, 340FC893204DAC8C007AEB0F /* AboutTableViewController.m */, + 88C7597224B7EAA600DB03EA /* AdvancedPinSettingsTableViewController.swift */, 340FC881204DAC8C007AEB0F /* AdvancedSettingsTableViewController.h */, 340FC88C204DAC8C007AEB0F /* AdvancedSettingsTableViewController.m */, 8827004D23208A1900F01C46 /* AppearanceSettingsTableViewController.swift */, @@ -4654,6 +4657,7 @@ 347B83FD24378DDF0019A52C /* PendingGroupMembersViewController.swift in Sources */, 8835DE03230DEC6A00DC6B66 /* AddToBlockListViewController.swift in Sources */, 34D1F0BD1F8D108C0066283D /* AttachmentUploadView.m in Sources */, + 88C7597324B7EAA600DB03EA /* AdvancedPinSettingsTableViewController.swift in Sources */, 4C8A6DFE22E54AFA00469AE7 /* MediaInteractiveDismiss.swift in Sources */, 452EC6DF205E9E30000E787C /* MediaGallery.swift in Sources */, 88D1D40622EF8F1100F472C5 /* ThreadDetailsCell.swift in Sources */, @@ -5088,7 +5092,6 @@ 888A27FA24B3E9800045D639 /* sk */, 888A27FB24B3E9810045D639 /* ru */, 888A27FC24B3E9810045D639 /* lt */, - 888A27FD24B3E9810045D639 /* cy */, 888A27FE24B3E9810045D639 /* id */, 888A27FF24B3E9810045D639 /* et */, 888A280024B3E9810045D639 /* bg */, diff --git a/Signal/Images.xcassets/more-horiz-24.imageset/Contents.json b/Signal/Images.xcassets/more-horiz-24.imageset/Contents.json new file mode 100644 index 0000000000..7395ab4915 --- /dev/null +++ b/Signal/Images.xcassets/more-horiz-24.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "more-horiz-24.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Signal/Images.xcassets/more-horiz-24.imageset/more-horiz-24.pdf b/Signal/Images.xcassets/more-horiz-24.imageset/more-horiz-24.pdf new file mode 100644 index 0000000000..db0680639a --- /dev/null +++ b/Signal/Images.xcassets/more-horiz-24.imageset/more-horiz-24.pdf @@ -0,0 +1,102 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 4.000000 10.470947 cm +0.231373 0.231373 0.231373 scn +16.000000 1.529053 m +16.000000 1.232381 15.912027 0.942372 15.747205 0.695698 c +15.582382 0.449024 15.348114 0.256765 15.074025 0.143233 c +14.799936 0.029702 14.498338 -0.000003 14.207366 0.057875 c +13.916394 0.115752 13.649120 0.258614 13.439342 0.468392 c +13.229563 0.678171 13.086700 0.945445 13.028823 1.236417 c +12.970945 1.527388 13.000651 1.828989 13.114182 2.103078 c +13.227714 2.377167 13.419972 2.611435 13.666646 2.776258 c +13.913320 2.941080 14.203329 3.029053 14.500001 3.029053 c +14.897825 3.029053 15.279356 2.871018 15.560660 2.589713 c +15.841965 2.308408 16.000000 1.926877 16.000000 1.529053 c +h +8.000001 3.029053 m +7.703329 3.029053 7.413320 2.941080 7.166646 2.776258 c +6.919972 2.611435 6.727713 2.377167 6.614182 2.103078 c +6.500650 1.828989 6.470945 1.527388 6.528823 1.236417 c +6.586701 0.945445 6.729562 0.678171 6.939341 0.468392 c +7.149119 0.258614 7.416393 0.115752 7.707365 0.057875 c +7.998337 -0.000003 8.299937 0.029702 8.574026 0.143233 c +8.848115 0.256765 9.082383 0.449024 9.247206 0.695698 c +9.412028 0.942372 9.500001 1.232381 9.500001 1.529053 c +9.500001 1.926877 9.341966 2.308408 9.060661 2.589713 c +8.779357 2.871018 8.397825 3.029053 8.000001 3.029053 c +h +1.500001 3.029053 m +1.203329 3.029053 0.913319 2.941080 0.666645 2.776258 c +0.419972 2.611435 0.227713 2.377167 0.114181 2.103078 c +0.000650 1.828989 -0.029055 1.527388 0.028823 1.236417 c +0.086701 0.945445 0.229562 0.678171 0.439340 0.468392 c +0.649119 0.258614 0.916394 0.115752 1.207365 0.057875 c +1.498337 -0.000003 1.799937 0.029702 2.074026 0.143233 c +2.348115 0.256765 2.582383 0.449024 2.747205 0.695698 c +2.912027 0.942372 3.000001 1.232381 3.000001 1.529053 c +3.000001 1.926877 2.841966 2.308408 2.560661 2.589713 c +2.279356 2.871018 1.897826 3.029053 1.500001 3.029053 c +1.500001 3.029053 l +h +f +n +Q + +endstream +endobj + +3 0 obj + 1927 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 24.000000 24.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Type /Catalog + /Pages 5 0 R + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000002017 00000 n +0000002040 00000 n +0000002213 00000 n +0000002287 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +2346 +%%EOF \ No newline at end of file diff --git a/Signal/src/AppDelegate.m b/Signal/src/AppDelegate.m index be1874f22d..a9a6f7895d 100644 --- a/Signal/src/AppDelegate.m +++ b/Signal/src/AppDelegate.m @@ -665,8 +665,8 @@ void uncaughtExceptionHandler(NSException *exception) - (void)enableBackgroundRefreshIfNecessary { [AppReadiness runNowOrWhenAppDidBecomeReady:^{ - if (OWS2FAManager.sharedManager.is2FAEnabled && [self.tsAccountManager isRegisteredAndReady]) { - // Ping server once a day to keep-alive 2FA clients. + if (OWS2FAManager.sharedManager.isRegistrationLockEnabled && [self.tsAccountManager isRegisteredAndReady]) { + // Ping server once a day to keep-alive reglock clients. const NSTimeInterval kBackgroundRefreshInterval = 24 * 60 * 60; [[UIApplication sharedApplication] setMinimumBackgroundFetchInterval:kBackgroundRefreshInterval]; } else { diff --git a/Signal/src/Experience Upgrades/ExperienceUpgradeManager.swift b/Signal/src/Experience Upgrades/ExperienceUpgradeManager.swift index a155b9da00..092db6e2f6 100644 --- a/Signal/src/Experience Upgrades/ExperienceUpgradeManager.swift +++ b/Signal/src/Experience Upgrades/ExperienceUpgradeManager.swift @@ -107,7 +107,7 @@ class ExperienceUpgradeManager: NSObject { // Only use a splash for message requests if the user doesn't have a profile name. return OWSProfileManager.shared().localFullName()?.isEmpty != false case .introducingPins: - return RemoteConfig.mandatoryPins + return true default: return false } diff --git a/Signal/src/Experience Upgrades/Upgrade Views/IntroducingPINs.swift b/Signal/src/Experience Upgrades/Upgrade Views/IntroducingPINs.swift index 0a4159e167..c02249a836 100644 --- a/Signal/src/Experience Upgrades/Upgrade Views/IntroducingPINs.swift +++ b/Signal/src/Experience Upgrades/Upgrade Views/IntroducingPINs.swift @@ -37,8 +37,6 @@ class IntroducingPinsMegaphone: MegaphoneView { } let secondaryButton = snoozeButton(fromViewController: fromViewController) { - guard RemoteConfig.mandatoryPins else { return MegaphoneStrings.weWillRemindYouLater } - let daysRemaining = ExperienceUpgradeManager.splashStartDay - experienceUpgrade.daysSinceFirstViewed assert(daysRemaining > 0) @@ -96,7 +94,22 @@ class IntroducingPinsSplash: SplashViewController { heroImageView.setCompressionResistanceLow() let title = NSLocalizedString("UPGRADE_EXPERIENCE_INTRODUCING_PINS_SETUP_TITLE", comment: "Header for PINs splash screen") - let body = NSLocalizedString("UPGRADE_EXPERIENCE_INTRODUCING_PINS_SETUP_DESCRIPTION", comment: "Body text for PINs splash screen") + + let attributedBody = NSMutableAttributedString( + string: NSLocalizedString("UPGRADE_EXPERIENCE_INTRODUCING_PINS_SETUP_DESCRIPTION", + comment: "Body text for PINs splash screen"), + attributes: [ + .font: UIFont.ows_dynamicTypeBody, + .foregroundColor: Theme.primaryTextColor + ] + ) + attributedBody.append(" ") + attributedBody.append(CommonStrings.learnMore, + attributes: [ + .link: URL(string: "https://support.signal.org/hc/articles/360007059792")!, + .font: UIFont.ows_dynamicTypeBody + ] + ) let hMargin: CGFloat = ScaleFromIPhone5To7Plus(16, 24) @@ -116,12 +129,10 @@ class IntroducingPinsSplash: SplashViewController { titleLabel.setContentHuggingVerticalHigh() // Body label - let bodyLabel = UILabel() - bodyLabel.text = body - bodyLabel.font = UIFont.ows_dynamicTypeBody + let bodyLabel = LinkingTextView() + bodyLabel.attributedText = attributedBody + bodyLabel.isUserInteractionEnabled = true bodyLabel.textColor = Theme.primaryTextColor - bodyLabel.numberOfLines = 0 - bodyLabel.lineBreakMode = .byWordWrapping bodyLabel.textAlignment = .center view.addSubview(bodyLabel) bodyLabel.autoPinWidthToSuperview(withMargin: hMargin) @@ -138,22 +149,20 @@ class IntroducingPinsSplash: SplashViewController { primaryButton.autoSetHeightUsingFont() view.addSubview(primaryButton) + primaryButton.autoPinBottomToSuperviewMargin(withInset: ScaleFromIPhone5(10)) primaryButton.autoPinWidthToSuperview(withMargin: hMargin) primaryButton.autoPinEdge(.top, to: .bottom, of: bodyLabel, withOffset: 28) primaryButton.setContentHuggingVerticalHigh() - // Secondary button - let secondaryButton = UIButton() - secondaryButton.setTitle(secondaryButtonTitle(), for: .normal) - secondaryButton.setTitleColor(Theme.accentBlueColor, for: .normal) - secondaryButton.titleLabel?.font = .ows_dynamicTypeBody - secondaryButton.addTarget(self, action: #selector(didTapSecondaryButton), for: .touchUpInside) - view.addSubview(secondaryButton) + // More button + let moreButton = UIButton() + moreButton.setTemplateImageName("more-horiz-24", tintColor: Theme.primaryIconColor) + moreButton.addTarget(self, action: #selector(didTapMoreButton), for: .touchUpInside) + view.addSubview(moreButton) - secondaryButton.autoPinBottomToSuperviewMargin(withInset: ScaleFromIPhone5(10)) - secondaryButton.autoPinWidthToSuperview(withMargin: hMargin) - secondaryButton.autoPinEdge(.top, to: .bottom, of: primaryButton, withOffset: 15) - secondaryButton.setContentHuggingVerticalHigh() + moreButton.autoSetDimensions(to: CGSize(square: 44)) + moreButton.autoPinEdge(toSuperviewSafeArea: .trailing, withInset: 8) + moreButton.autoPinEdge(toSuperviewSafeArea: .top, withInset: 8) } @objc @@ -165,9 +174,47 @@ class IntroducingPinsSplash: SplashViewController { } @objc - func didTapSecondaryButton(_ sender: UIButton) { - let vc = SFSafariViewController(url: URL(string: "https://support.signal.org/hc/articles/360007059792")!) - present(vc, animated: true, completion: nil) + func didTapMoreButton(_ sender: UIButton) { + let actionSheet = ActionSheetController() + actionSheet.addAction(OWSActionSheets.cancelAction) + + let learnMoreAction = ActionSheetAction( + title: NSLocalizedString( + "UPGRADE_EXPERIENCE_INTRODUCING_PINS_LEARN_MORE", + comment: "Learn more action on the one time splash screen that appears after upgrading" + ) + ) { [weak self] _ in + guard let self = self else { return } + let vc = SFSafariViewController(url: URL(string: "https://support.signal.org/hc/articles/360007059792")!) + self.present(vc, animated: true, completion: nil) + } + actionSheet.addAction(learnMoreAction) + + let skipAction = ActionSheetAction( + title: NSLocalizedString( + "UPGRADE_EXPERIENCE_INTRODUCING_PINS_SKIP", + comment: "Skip action on the one time splash screen that appears after upgrading" + ) + ) { [weak self] _ in + guard let self = self else { return } + PinSetupViewController.disablePinWithConfirmation(fromViewController: self).done { [weak self] pinDisabled in + guard pinDisabled else { return } + self?.dismiss(animated: true) + }.catch { [weak self] _ in + guard let self = self else { return } + OWSActionSheets.showActionSheet( + title: NSLocalizedString("PIN_DISABLE_ERROR_TITLE", + comment: "Error title indicating that the attempt to disable a PIN failed."), + message: NSLocalizedString("PIN_DISABLE_ERROR_MESSAGE", + comment: "Error body indicating that the attempt to disable a PIN failed.") + ) { _ in + self.dismissWithoutCompleting(animated: true) + } + } + } + actionSheet.addAction(skipAction) + + presentActionSheet(actionSheet) } func primaryButtonTitle() -> String { @@ -179,15 +226,21 @@ class IntroducingPinsSplash: SplashViewController { return CommonStrings.learnMore } + var toastText: String { + return OWS2FAManager.shared().isUsingRandomPin + ? NSLocalizedString("PINS_MEGAPHONE_TOAST_DISABLED", comment: "Toast indicating that a PIN has been disabled.") + : NSLocalizedString("PINS_MEGAPHONE_TOAST", comment: "Toast indicating that a PIN has been created.") + } + override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) { guard let fromViewController = presentingViewController else { - return owsFailDebug("Trying to dismiss while not presented.") + return //owsFailDebug("Trying to dismiss while not presented.") } super.dismiss(animated: flag) { [weak self] in if let self = self, !self.isDismissWithoutCompleting { self.presentToast( - text: NSLocalizedString("PINS_MEGAPHONE_TOAST", comment: "Toast indicating that a PIN has been created."), + text: self.toastText, fromViewController: fromViewController ) } diff --git a/Signal/src/ViewControllers/AppSettings/AdvancedPinSettingsTableViewController.swift b/Signal/src/ViewControllers/AppSettings/AdvancedPinSettingsTableViewController.swift new file mode 100644 index 0000000000..f225a3b596 --- /dev/null +++ b/Signal/src/ViewControllers/AppSettings/AdvancedPinSettingsTableViewController.swift @@ -0,0 +1,54 @@ +// +// Copyright (c) 2020 Open Whisper Systems. All rights reserved. +// + +import Foundation + +@objc +class AdvancedPinSettingsTableViewController: OWSTableViewController { + override func viewDidLoad() { + super.viewDidLoad() + + title = NSLocalizedString("SETTINGS_ADVANCED_PIN_TITLE", comment: "The title for the advanced pin settings.") + + self.useThemeBackgroundColors = true + + updateTableContents() + } + + func updateTableContents() { + let contents = OWSTableContents() + + let pinsSection = OWSTableSection() + pinsSection.headerTitle = NSLocalizedString("SETTINGS_ADVANCED_PINS_HEADER", + comment: "Table header for the 'pins' section.") + + pinsSection.add(OWSTableItem.actionItem( + withText: OWS2FAManager.shared().isUsingRandomPin + ? NSLocalizedString("SETTINGS_ADVANCED_PINS_ENABLE_PIN_ACTION", + comment: "") + : NSLocalizedString("SETTINGS_ADVANCED_PINS_DISABLE_PIN_ACTION", + comment: ""), + textColor: Theme.accentBlueColor, + accessibilityIdentifier: "advancedPinSettings.disable", + actionBlock: { [weak self] in + guard let self = self else { return } + if OWS2FAManager.shared().isUsingRandomPin { + let vc = PinSetupViewController.creating { [weak self] _, _ in + guard let self = self else { return } + self.navigationController?.setNavigationBarHidden(false, animated: false) + self.navigationController?.popToViewController(self, animated: true) + self.updateTableContents() + } + self.navigationController?.pushViewController(vc, animated: true) + } else { + PinSetupViewController.disablePinWithConfirmation(fromViewController: self).done { [weak self] _ in + self?.updateTableContents() + } + } + })) + contents.addSection(pinsSection) + + self.contents = contents + } +} diff --git a/Signal/src/ViewControllers/AppSettings/AdvancedSettingsTableViewController.m b/Signal/src/ViewControllers/AppSettings/AdvancedSettingsTableViewController.m index aaf2251181..0457e81c32 100644 --- a/Signal/src/ViewControllers/AppSettings/AdvancedSettingsTableViewController.m +++ b/Signal/src/ViewControllers/AppSettings/AdvancedSettingsTableViewController.m @@ -229,6 +229,17 @@ NS_ASSUME_NONNULL_BEGIN } [contents addSection:censorshipSection]; + OWSTableSection *pinsSection = [OWSTableSection new]; + pinsSection.headerTitle + = NSLocalizedString(@"SETTINGS_ADVANCED_PINS_HEADER", @"Table header for the 'pins' section."); + [pinsSection addItem:[OWSTableItem disclosureItemWithText:NSLocalizedString(@"SETTINGS_ADVANCED_PIN_SETTINGS", + @"Label for the 'advanced pin settings' button.") + accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, @"pins") + actionBlock:^{ + [weakSelf showAdvancedPinSettings]; + }]]; + [contents addSection:pinsSection]; + self.contents = contents; } @@ -271,6 +282,12 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark - Actions +- (void)showAdvancedPinSettings +{ + AdvancedPinSettingsTableViewController *vc = [AdvancedPinSettingsTableViewController new]; + [self.navigationController pushViewController:vc animated:YES]; +} + - (void)syncPushTokens { OWSSyncPushTokensJob *job = diff --git a/Signal/src/ViewControllers/AppSettings/PrivacySettingsTableViewController.m b/Signal/src/ViewControllers/AppSettings/PrivacySettingsTableViewController.m index 4d7bdf2a54..2ce9307376 100644 --- a/Signal/src/ViewControllers/AppSettings/PrivacySettingsTableViewController.m +++ b/Signal/src/ViewControllers/AppSettings/PrivacySettingsTableViewController.m @@ -573,6 +573,7 @@ NS_ASSUME_NONNULL_BEGIN __weak PrivacySettingsTableViewController *weakSelf = self; OWSPinSetupViewController *vc = [OWSPinSetupViewController creatingWithCompletionHandler:^(OWSPinSetupViewController *pinSetupVC, NSError *_Nullable error) { + [weakSelf.navigationController setNavigationBarHidden:NO animated:NO]; [weakSelf.navigationController popToViewController:weakSelf animated:YES]; }]; [self.navigationController pushViewController:vc animated:YES]; @@ -607,6 +608,7 @@ NS_ASSUME_NONNULL_BEGIN OWSPinSetupViewController *vc = [OWSPinSetupViewController creatingRegistrationLockWithCompletionHandler:^( OWSPinSetupViewController *pinSetupVC, NSError *_Nullable error) { + [weakSelf.navigationController setNavigationBarHidden:NO animated:NO]; [weakSelf.navigationController popToViewController:weakSelf animated:YES]; }]; [self.navigationController pushViewController:vc animated:YES]; diff --git a/Signal/src/ViewControllers/OWSPinSetupViewController.swift b/Signal/src/ViewControllers/OWSPinSetupViewController.swift index 15517d4862..5d11c27f65 100644 --- a/Signal/src/ViewControllers/OWSPinSetupViewController.swift +++ b/Signal/src/ViewControllers/OWSPinSetupViewController.swift @@ -5,6 +5,7 @@ import UIKit import Lottie import PromiseKit +import SafariServices @objc(OWSPinSetupViewController) public class PinSetupViewController: OWSViewController { @@ -152,12 +153,26 @@ public class PinSetupViewController: OWSViewController { } else { // Back button - let topButton = UIButton() + let backButton = UIButton() let topButtonImage = CurrentAppContext().isRTL ? #imageLiteral(resourceName: "NavBarBackRTL") : #imageLiteral(resourceName: "NavBarBack") - topButton.setTemplateImage(topButtonImage, tintColor: Theme.secondaryTextAndIconColor) - topButton.autoSetDimensions(to: CGSize(square: 40)) - topButton.addTarget(self, action: #selector(navigateBack), for: .touchUpInside) + backButton.setTemplateImage(topButtonImage, tintColor: Theme.secondaryTextAndIconColor) + backButton.autoSetDimensions(to: CGSize(square: 40)) + backButton.addTarget(self, action: #selector(navigateBack), for: .touchUpInside) + + // More button + + let trailingView: UIView + if mode.isConfirming { + trailingView = UIView.spacer(withWidth: 40) + } else { + let moreButton = UIButton() + moreButton.setTemplateImageName("more-horiz-24", tintColor: Theme.primaryIconColor) + moreButton.autoSetDimensions(to: CGSize(square: 40)) + moreButton.addTarget(self, action: #selector(didTapMoreButton), for: .touchUpInside) + + trailingView = moreButton + } // Title @@ -172,9 +187,9 @@ public class PinSetupViewController: OWSViewController { // If we're in creating mode AND we're the rootViewController, don't allow going back if case .creating = mode, navigationController?.viewControllers.first == self { - arrangedSubviews = [label] + arrangedSubviews = [UIView.spacer(withWidth: 40), label, trailingView] } else { - arrangedSubviews = [topButton, label, UIView.spacer(withWidth: 40)] + arrangedSubviews = [backButton, label, trailingView] } let row = UIStackView(arrangedSubviews: arrangedSubviews) @@ -352,6 +367,50 @@ public class PinSetupViewController: OWSViewController { } } + @objc + func didTapMoreButton(_ sender: UIButton) { + let actionSheet = ActionSheetController() + actionSheet.addAction(OWSActionSheets.cancelAction) + + let learnMoreAction = ActionSheetAction( + title: NSLocalizedString( + "PIN_CREATION_LEARN_MORE", + comment: "Learn more action on the pin creation view" + ) + ) { [weak self] _ in + guard let self = self else { return } + let vc = SFSafariViewController(url: URL(string: "https://support.signal.org/hc/articles/360007059792")!) + self.present(vc, animated: true, completion: nil) + } + actionSheet.addAction(learnMoreAction) + + let skipAction = ActionSheetAction( + title: NSLocalizedString( + "PIN_CREATION_SKIP", + comment: "Skip action on the pin creation view" + ) + ) { [weak self] _ in + guard let self = self else { return } + Self.disablePinWithConfirmation(fromViewController: self).done { [weak self] pinDisabled in + guard pinDisabled, let self = self else { return } + self.completionHandler(self, nil) + }.catch { [weak self] error in + guard let self = self else { return } + OWSActionSheets.showActionSheet( + title: NSLocalizedString("PIN_DISABLE_ERROR_TITLE", + comment: "Error title indicating that the attempt to disable a PIN failed."), + message: NSLocalizedString("PIN_DISABLE_ERROR_MESSAGE", + comment: "Error body indicating that the attempt to disable a PIN failed.") + ) { _ in + self.completionHandler(self, error) + } + } + } + actionSheet.addAction(skipAction) + + presentActionSheet(actionSheet) + } + @objc func nextPressed() { Logger.info("") @@ -590,6 +649,94 @@ extension PinSetupViewController: UITextFieldDelegate { } } +extension PinSetupViewController { + public class func disablePinWithConfirmation(fromViewController: UIViewController) -> Promise { + guard !OWS2FAManager.shared().isRegistrationLockV2Enabled else { + return showRegistrationLockConfirmation(fromViewController: fromViewController) + } + + let (promise, resolver) = Promise.pending() + + let actionSheet = ActionSheetController( + title: NSLocalizedString("PIN_CREATION_DISABLE_CONFIRMATION_TITLE", + comment: "Title of the 'pin disable' action sheet."), + message: NSLocalizedString("PIN_CREATION_DISABLE_CONFIRMATION_MESSAGE", + comment: "Message of the 'pin disable' action sheet.") + ) + + let cancelAction = ActionSheetAction(title: CommonStrings.cancelButton, style: .cancel) { _ in + resolver.fulfill(false) + } + actionSheet.addAction(cancelAction) + + let disableAction = ActionSheetAction( + title: NSLocalizedString("PIN_CREATION_DISABLE_CONFIRMATION_ACTION", + comment: "Action of the 'pin disable' action sheet."), + style: .destructive + ) { _ in + ModalActivityIndicatorViewController.present( + fromViewController: fromViewController, + canCancel: false + ) { modal in + OWS2FAManager.shared().markEnabledWithRandomPin().done { + modal.dismiss { resolver.fulfill(true) } + }.catch { error in + modal.dismiss { resolver.reject(error) } + } + } + } + actionSheet.addAction(disableAction) + + fromViewController.presentActionSheet(actionSheet) + + return promise + } + + private class func showRegistrationLockConfirmation(fromViewController: UIViewController) -> Promise { + let (promise, resolver) = Promise.pending() + + let actionSheet = ActionSheetController( + title: NSLocalizedString("PIN_CREATION_REGLOCK_CONFIRMATION_TITLE", + comment: "Title of the 'pin disable' reglock action sheet."), + message: NSLocalizedString("PIN_CREATION_REGLOCK_CONFIRMATION_MESSAGE", + comment: "Message of the 'pin disable' reglock action sheet.") + ) + + let cancelAction = ActionSheetAction(title: CommonStrings.cancelButton, style: .cancel) { _ in + resolver.fulfill(false) + } + actionSheet.addAction(cancelAction) + + let disableAction = ActionSheetAction( + title: NSLocalizedString("PIN_CREATION_REGLOCK_CONFIRMATION_ACTION", + comment: "Action of the 'pin disable' reglock action sheet."), + style: .destructive + ) { _ in + ModalActivityIndicatorViewController.present( + fromViewController: fromViewController, + canCancel: false + ) { modal in + OWS2FAManager.shared().disableRegistrationLockV2().then { + Promise { resolver in + modal.dismiss { resolver.fulfill(()) } + } + }.then { () -> Promise in + disablePinWithConfirmation(fromViewController: fromViewController) + }.done { success in + resolver.fulfill(success) + }.catch { error in + modal.dismiss { resolver.reject(error) } + } + } + } + actionSheet.addAction(disableAction) + + fromViewController.presentActionSheet(actionSheet) + + return promise + } +} + class PinProgressView: UIView { private let label = UILabel() private let progressAnimation = AnimationView(name: "pinCreationInProgress") diff --git a/Signal/src/ViewControllers/Registration/Onboarding2FAViewController.swift b/Signal/src/ViewControllers/Registration/Onboarding2FAViewController.swift index 8edc7ad04a..31f7aaa3a5 100644 --- a/Signal/src/ViewControllers/Registration/Onboarding2FAViewController.swift +++ b/Signal/src/ViewControllers/Registration/Onboarding2FAViewController.swift @@ -367,17 +367,12 @@ public class Onboarding2FAViewController: OnboardingBaseViewController { onboardingController.showNextMilestone(navigationController: navigationController) } - var hasEverGuessedWrongPIN = false private func updateValidationWarnings() { AssertIsOnMainThread() - if attemptState.isInvalid { - hasEverGuessedWrongPIN = true - } pinStrokeNormal.isHidden = attemptState.isInvalid pinStrokeError.isHidden = !attemptState.isInvalid validationWarningLabel.isHidden = !attemptState.isInvalid - needHelpLink.isHidden = !hasEverGuessedWrongPIN switch attemptState { case .exhausted: diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index b7f2cbe07a..3059568c89 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -164,7 +164,7 @@ /* Name of application */ "APPLICATION_NAME" = "Signal"; -/* Pressing this button moves a thread from the inbox to the archive */ +/* Label for the archive button for conversations list view */ "ARCHIVE_ACTION" = "Archive"; /* One-line label indicating the user can add no more text to the attachment caption. */ @@ -2702,6 +2702,15 @@ /* Button asking if the user would like to create an numeric PIN */ "PIN_CREATION_CREATE_NUMERIC" = "Create Numeric PIN"; +/* Action of the 'pin disable' action sheet. */ +"PIN_CREATION_DISABLE_CONFIRMATION_ACTION" = "Skip PIN Creation"; + +/* Message of the 'pin disable' action sheet. */ +"PIN_CREATION_DISABLE_CONFIRMATION_MESSAGE" = "Skipping PIN creation will create a hidden, high-entropy PIN associated with your account that is unrecoverable. When you re-register Signal you will lose all data unless you manually back up and restore. You can not turn on Registration Lock while the PIN is disabled."; + +/* Title of the 'pin disable' action sheet. */ +"PIN_CREATION_DISABLE_CONFIRMATION_TITLE" = "Warning"; + /* Error body indicating that the attempt to create a PIN failed. */ "PIN_CREATION_ERROR_MESSAGE" = "Your PIN was not saved. We’ll prompt you to create a PIN later."; @@ -2709,7 +2718,10 @@ "PIN_CREATION_ERROR_TITLE" = "PIN Creation Failed"; /* The explanation in the 'pin creation' view. */ -"PIN_CREATION_EXPLANATION" = "PINs keep information stored with Signal encrypted so only you can access it. Your profile, settings, and contacts will restore when you reinstall Signal."; +"PIN_CREATION_EXPLANATION" = "PINs keep information stored with Signal encrypted so only you can access it. Your profile, settings, and contacts will restore when you reinstall. You won’t need your PIN to open the app."; + +/* Learn more action on the pin creation view */ +"PIN_CREATION_LEARN_MORE" = "Learn More About PINs"; /* Label indicating that the attempted PIN does not match the first PIN */ "PIN_CREATION_MISMATCH_ERROR" = "PINs don’t match. Try again."; @@ -2729,12 +2741,30 @@ /* Title of the 'pin creation' recreation view. */ "PIN_CREATION_RECREATION_TITLE" = "Change your PIN"; +/* Action of the 'pin disable' reglock action sheet. */ +"PIN_CREATION_REGLOCK_CONFIRMATION_ACTION" = "Turn Off"; + +/* Message of the 'pin disable' reglock action sheet. */ +"PIN_CREATION_REGLOCK_CONFIRMATION_MESSAGE" = "To disable your PIN, you first have to turn off Registration Lock."; + +/* Title of the 'pin disable' reglock action sheet. */ +"PIN_CREATION_REGLOCK_CONFIRMATION_TITLE" = "Turn Off Registration Lock?"; + +/* Skip action on the pin creation view */ +"PIN_CREATION_SKIP" = "Skip PIN Creation"; + /* Title of the 'pin creation' view. */ "PIN_CREATION_TITLE" = "Create your PIN"; /* Label indicating that the attempted PIN is too weak */ "PIN_CREATION_WEAK_ERROR" = "Choose a stronger PIN"; +/* Error body indicating that the attempt to disable a PIN failed. */ +"PIN_DISABLE_ERROR_MESSAGE" = "Your PIN was not able to be disabled. We’ll prompt you again later."; + +/* Error title indicating that the attempt to disable a PIN failed. */ +"PIN_DISABLE_ERROR_TITLE" = "PIN Disable Failed"; + /* The explanation for the 'pin reminder' dialog. */ "PIN_REMINDER_EXPLANATION" = "To help you memorize your PIN, we’ll ask you to enter it periodically. We’ll ask less over time."; @@ -2786,6 +2816,9 @@ /* Toast indicating that a PIN has been created. */ "PINS_MEGAPHONE_TOAST" = "PIN created. You can change it in settings."; +/* Toast indicating that a PIN has been disabled. */ +"PINS_MEGAPHONE_TOAST_DISABLED" = "PIN Disabled."; + /* Accessibility label for button to start media playback */ "PLAY_BUTTON_ACCESSABILITY_LABEL" = "Play Media"; @@ -3425,6 +3458,21 @@ /* No comment provided by engineer. */ "SETTINGS_ADVANCED_DEBUGLOG" = "Enable Debug Log"; +/* Label for the 'advanced pin settings' button. */ +"SETTINGS_ADVANCED_PIN_SETTINGS" = "Advanced PIN Settings"; + +/* The title for the advanced pin settings. */ +"SETTINGS_ADVANCED_PIN_TITLE" = "Advanced PIN Settings"; + +/* No comment provided by engineer. */ +"SETTINGS_ADVANCED_PINS_DISABLE_PIN_ACTION" = "Disable PIN"; + +/* No comment provided by engineer. */ +"SETTINGS_ADVANCED_PINS_ENABLE_PIN_ACTION" = "Enable PIN"; + +/* Table header for the 'pins' section. */ +"SETTINGS_ADVANCED_PINS_HEADER" = "Signal PiN"; + /* No comment provided by engineer. */ "SETTINGS_ADVANCED_SUBMIT_DEBUGLOG" = "Submit Debug Log"; @@ -3881,7 +3929,7 @@ /* Label for the delete button in an alert or action sheet. */ "TXT_DELETE_TITLE" = "Delete"; -/* Pressing this button moves an archived thread from the archive back to the inbox */ +/* Label for the unarchive button for conversations list view */ "UNARCHIVE_ACTION" = "Unarchive"; /* Message shown in conversation view that offers to block an unknown user. */ @@ -3965,12 +4013,18 @@ /* Button to start a create pin flow from the one time splash screen that appears after upgrading */ "UPGRADE_EXPERIENCE_INTRODUCING_PINS_CREATE_BUTTON" = "Create your PIN"; +/* Learn more action on the one time splash screen that appears after upgrading */ +"UPGRADE_EXPERIENCE_INTRODUCING_PINS_LEARN_MORE" = "Learn More About PINs"; + /* Body text for PINs splash screen */ -"UPGRADE_EXPERIENCE_INTRODUCING_PINS_SETUP_DESCRIPTION" = "PINs keep information stored with Signal encrypted so only you can access it. Your profile, settings, and contacts will restore when you reinstall Signal."; +"UPGRADE_EXPERIENCE_INTRODUCING_PINS_SETUP_DESCRIPTION" = "PINs keep information stored with Signal encrypted so only you can access it. Your profile, settings, and contacts will restore when you reinstall. You won’t need your PIN to open the app."; /* Header for PINs splash screen */ "UPGRADE_EXPERIENCE_INTRODUCING_PINS_SETUP_TITLE" = "Introducing PINs"; +/* Skip action on the one time splash screen that appears after upgrading */ +"UPGRADE_EXPERIENCE_INTRODUCING_PINS_SKIP" = "Skip PIN Creation"; + /* Message for the alert indicating that user should upgrade iOS. */ "UPGRADE_IOS_ALERT_MESSAGE" = "Signal will soon require iOS 11 or later. Please upgrade in Settings app >> General >> Software Update."; diff --git a/SignalMessaging/ViewControllers/OWSTableViewController.h b/SignalMessaging/ViewControllers/OWSTableViewController.h index bae32851a2..6a9e78201b 100644 --- a/SignalMessaging/ViewControllers/OWSTableViewController.h +++ b/SignalMessaging/ViewControllers/OWSTableViewController.h @@ -121,6 +121,11 @@ typedef BOOL (^OWSTableSwitchBlock)(void); accessibilityIdentifier:(nullable NSString *)accessibilityIdentifier actionBlock:(nullable OWSTableActionBlock)actionBlock; ++ (OWSTableItem *)actionItemWithText:(NSString *)text + textColor:(nullable UIColor *)textColor + accessibilityIdentifier:(nullable NSString *)accessibilityIdentifier + actionBlock:(nullable OWSTableActionBlock)actionBlock; + + (OWSTableItem *)softCenterLabelItemWithText:(NSString *)text; + (OWSTableItem *)softCenterLabelItemWithText:(NSString *)text customRowHeight:(CGFloat)customRowHeight; diff --git a/SignalMessaging/ViewControllers/OWSTableViewController.m b/SignalMessaging/ViewControllers/OWSTableViewController.m index 8d3548f284..c36a52f38a 100644 --- a/SignalMessaging/ViewControllers/OWSTableViewController.m +++ b/SignalMessaging/ViewControllers/OWSTableViewController.m @@ -326,6 +326,17 @@ const CGFloat kOWSTable_DefaultCellHeight = 45.f; + (OWSTableItem *)actionItemWithText:(NSString *)text accessibilityIdentifier:(nullable NSString *)accessibilityIdentifier actionBlock:(nullable OWSTableActionBlock)actionBlock +{ + return [self actionItemWithText:text + textColor:nil + accessibilityIdentifier:accessibilityIdentifier + actionBlock:actionBlock]; +} + ++ (OWSTableItem *)actionItemWithText:(NSString *)text + textColor:(nullable UIColor *)textColor + accessibilityIdentifier:(nullable NSString *)accessibilityIdentifier + actionBlock:(nullable OWSTableActionBlock)actionBlock { OWSAssertDebug(text.length > 0); OWSAssertDebug(actionBlock); @@ -335,6 +346,9 @@ const CGFloat kOWSTable_DefaultCellHeight = 45.f; item.customCellBlock = ^{ UITableViewCell *cell = [OWSTableItem newCell]; cell.textLabel.text = text; + if (textColor) { + cell.textLabel.textColor = textColor; + } cell.accessibilityIdentifier = accessibilityIdentifier; return cell; }; diff --git a/SignalServiceKit/src/Network/API/Requests/OWSRequestFactory.m b/SignalServiceKit/src/Network/API/Requests/OWSRequestFactory.m index 4d36886363..e0dba5ae43 100644 --- a/SignalServiceKit/src/Network/API/Requests/OWSRequestFactory.m +++ b/SignalServiceKit/src/Network/API/Requests/OWSRequestFactory.m @@ -476,7 +476,7 @@ NSString *const OWSRequestKey_AuthKey = @"AuthKey"; NSString *_Nullable registrationLockToken = [OWSKeyBackupService deriveRegistrationLockToken]; if (registrationLockToken.length > 0 && OWS2FAManager.sharedManager.isRegistrationLockV2Enabled) { accountAttributes[@"registrationLock"] = registrationLockToken; - } else if (pin.length > 0 && OWS2FAManager.sharedManager.mode != OWS2FAMode_V2) { + } else if (pin.length > 0 && self.ows2FAManager.mode != OWS2FAMode_V2 && !self.ows2FAManager.isUsingRandomPin) { accountAttributes[@"pin"] = pin; } @@ -508,7 +508,7 @@ NSString *const OWSRequestKey_AuthKey = @"AuthKey"; if (SSKFeatureFlags.groupsV2SetCapability) { capabilities[@"gv2"] = @(YES); } - if (OWSKeyBackupService.hasMasterKey) { + if (OWSKeyBackupService.hasMasterKey && !self.ows2FAManager.isUsingRandomPin) { capabilities[@"storage"] = @(YES); } if (SSKDebugFlags.groupsV2memberStatusIndicators) { diff --git a/SignalServiceKit/src/Util/KeyBackupService.swift b/SignalServiceKit/src/Util/KeyBackupService.swift index 64b3dbc45f..5dc50cc062 100644 --- a/SignalServiceKit/src/Util/KeyBackupService.swift +++ b/SignalServiceKit/src/Util/KeyBackupService.swift @@ -187,16 +187,20 @@ public class KeyBackupService: NSObject { } } - @objc(generateAndBackupKeysWithPin:) - static func objc_generateAndBackupKeys(with pin: String) -> AnyPromise { - return AnyPromise(generateAndBackupKeys(with: pin)) + @objc(generateAndBackupKeysWithPin:rotateMasterKey:) + @available(swift, obsoleted: 1.0) + static func generateAndBackupKeys(with pin: String, rotateMasterKey: Bool) -> AnyPromise { + return AnyPromise(generateAndBackupKeys(with: pin, rotateMasterKey: rotateMasterKey)) } /// Backs up the user's master key to KBS and stores it locally in the database. /// If the user doesn't have a master key already a new one is generated. - public static func generateAndBackupKeys(with pin: String) -> Promise { + public static func generateAndBackupKeys(with pin: String, rotateMasterKey: Bool) -> Promise { return fetchBackupId(auth: nil).map(on: .global()) { backupId -> (Data, Data, Data) in - let masterKey = cacheQueue.sync { cachedMasterKey } ?? generateMasterKey() + let masterKey: Data = { + if rotateMasterKey { return generateMasterKey() } + return cacheQueue.sync { cachedMasterKey } ?? generateMasterKey() + }() let (encryptionKey, accessKey) = try deriveEncryptionKeyAndAccessKey(pin: pin, backupId: backupId) let encryptedMasterKey = try encryptMasterKey(masterKey, encryptionKey: encryptionKey) @@ -233,7 +237,13 @@ public class KeyBackupService: NSObject { // We successfully stored the new keys in KBS, save them in the database databaseStorage.write { transaction in - store(masterKey, pinType: PinType(forPin: pin), encodedVerificationString: encodedVerificationString, transaction: transaction) + if rotateMasterKey { storageServiceManager.resetLocalData(transaction: transaction) } + store( + masterKey, + pinType: PinType(forPin: pin), + encodedVerificationString: encodedVerificationString, + transaction: transaction + ) } } }.recover(on: .global()) { error in diff --git a/SignalServiceKit/src/Util/OWS2FAManager.h b/SignalServiceKit/src/Util/OWS2FAManager.h index e192c71f02..2af4d654a4 100644 --- a/SignalServiceKit/src/Util/OWS2FAManager.h +++ b/SignalServiceKit/src/Util/OWS2FAManager.h @@ -49,6 +49,7 @@ typedef NS_CLOSED_ENUM(NSUInteger, OWS2FAMode) { // Request with service - (void)requestEnable2FAWithPin:(NSString *)pin mode:(OWS2FAMode)mode + rotateMasterKey:(BOOL)rotateMasterKey success:(nullable OWS2FASuccess)success failure:(nullable OWS2FAFailure)failure; diff --git a/SignalServiceKit/src/Util/OWS2FAManager.m b/SignalServiceKit/src/Util/OWS2FAManager.m index 179ca15c53..4055c87352 100644 --- a/SignalServiceKit/src/Util/OWS2FAManager.m +++ b/SignalServiceKit/src/Util/OWS2FAManager.m @@ -134,6 +134,9 @@ const NSUInteger kLegacyTruncated2FAv1PinLength = 16; { // Identify what version of 2FA we're using if (OWSKeyBackupService.hasMasterKey) { + if (self.isUsingRandomPin) { + return OWS2FAMode_Disabled; + } return OWS2FAMode_V2; } else if (self.pinCode != nil) { return OWS2FAMode_V1; @@ -169,6 +172,9 @@ const NSUInteger kLegacyTruncated2FAv1PinLength = 16; // Since we just created this pin, we know it doesn't need migration. Mark it as such. [self markLegacyPinAsMigratedWithTransaction:transaction]; + // Reset random state for the new PIN + [self setIsUsingRandomPin:NO transaction:transaction]; + // Reset the reminder repetition interval for the new pin. [self setDefaultRepetitionIntervalWithTransaction:transaction]; @@ -188,6 +194,7 @@ const NSUInteger kLegacyTruncated2FAv1PinLength = 16; - (void)requestEnable2FAWithPin:(NSString *)pin mode:(OWS2FAMode)mode + rotateMasterKey:(BOOL)rotateMasterKey success:(nullable OWS2FASuccess)success failure:(nullable OWS2FAFailure)failure { @@ -199,7 +206,7 @@ const NSUInteger kLegacyTruncated2FAv1PinLength = 16; case OWS2FAMode_V2: { // Enabling V2 2FA doesn't inherently enable registration lock, // it's managed by a separate setting. - [OWSKeyBackupService generateAndBackupKeysWithPin:pin] + [OWSKeyBackupService generateAndBackupKeysWithPin:pin rotateMasterKey:rotateMasterKey] .then(^{ OWSAssertIsOnMainThread(); @@ -342,6 +349,10 @@ const NSUInteger kLegacyTruncated2FAv1PinLength = 16; return NO; } + if ([self isUsingRandomPinWithTransaction:transaction]) { + return NO; + } + NSDate *nextReminderDate = [self nextReminderDateWithTransaction:transaction]; return nextReminderDate.timeIntervalSinceNow < 0; diff --git a/SignalServiceKit/src/Util/OWS2FAManager.swift b/SignalServiceKit/src/Util/OWS2FAManager.swift index 522f17b5b5..898775977c 100644 --- a/SignalServiceKit/src/Util/OWS2FAManager.swift +++ b/SignalServiceKit/src/Util/OWS2FAManager.swift @@ -47,9 +47,9 @@ extension OWS2FAManager { ) } - public func requestEnable2FA(withPin pin: String, mode: OWS2FAMode) -> Promise { + public func requestEnable2FA(withPin pin: String, mode: OWS2FAMode, rotateMasterKey: Bool = false) -> Promise { return Promise { resolver in - requestEnable2FA(withPin: pin, mode: mode, success: { + requestEnable2FA(withPin: pin, mode: mode, rotateMasterKey: rotateMasterKey, success: { resolver.fulfill(()) }) { error in resolver.reject(error) @@ -153,4 +153,71 @@ extension OWS2FAManager { return self.enableRegistrationLockV2() } } + + private static var isUsingRandomPinKey = "isUsingRandomPinKey" + + @objc + public var isUsingRandomPin: Bool { + return databaseStorage.read { transaction in + OWS2FAManager.keyValueStore().getBool( + OWS2FAManager.isUsingRandomPinKey, + defaultValue: false, + transaction: transaction + ) + } + } + + @objc + public func isUsingRandomPin(transaction: SDSAnyReadTransaction) -> Bool { + return OWS2FAManager.keyValueStore().getBool( + OWS2FAManager.isUsingRandomPinKey, + defaultValue: false, + transaction: transaction + ) + } + + @objc + public func setIsUsingRandomPin(_ value: Bool, transaction: SDSAnyWriteTransaction) { + OWS2FAManager.keyValueStore().setBool( + value, + key: OWS2FAManager.isUsingRandomPinKey, + transaction: transaction + ) + } + + @objc + @available(swift, obsoleted: 1.0) + public func markEnabledWithRandomPin() -> AnyPromise { + return AnyPromise(markEnabledWithRandomPin()) + } + + public func markEnabledWithRandomPin() -> Promise { + let highEntropyPIN = Cryptography.generateRandomBytes(32).hexadecimalString + + return firstly { + requestEnable2FA(withPin: highEntropyPIN, mode: .V2, rotateMasterKey: true) + }.done(on: .global()) { + self.databaseStorage.write { transaction in + self.setIsUsingRandomPin(true, transaction: transaction) + } + + firstly { + TSAccountManager.sharedInstance().updateAccountAttributes() + }.catch { error in + Logger.error("Error updating account attributes: \(error)") + } + + }.recover(on: .global()) { error in + Logger.error("Error disabling PIN: \(error)") + + self.databaseStorage.write { transaction in + OWS2FAManager.keyValueStore().removeValue( + forKey: OWS2FAManager.isUsingRandomPinKey, + transaction: transaction + ) + } + + throw error + } + } } diff --git a/SignalServiceKit/src/Util/RemoteConfigManager.swift b/SignalServiceKit/src/Util/RemoteConfigManager.swift index e19622d3fa..17ad39535d 100644 --- a/SignalServiceKit/src/Util/RemoteConfigManager.swift +++ b/SignalServiceKit/src/Util/RemoteConfigManager.swift @@ -16,9 +16,6 @@ public class RemoteConfig: BaseFlags { // into a getter below... private let config: [String: Bool] - @objc - public static var mandatoryPins: Bool { isEnabled(.mandatoryPins) } - @objc public static var kbs: Bool { // This feature latches "on" – once they have a master key in KBS, @@ -129,7 +126,6 @@ private struct Flags { // to production. enum Supported: String, FlagType { case kbs - case mandatoryPins case groupsV2CreateGroups case groupsV2GoodCitizen case deleteForEveryone