Add the ability to opt out of PINs.

This commit is contained in:
Nora Trapp 2020-07-09 18:29:48 -07:00
parent aead92f567
commit de0522586a
20 changed files with 601 additions and 58 deletions

View File

@ -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 = "<group>"; };
88C4E37F24635337009C9B97 /* DeviceTransferService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceTransferService.swift; sourceTree = "<group>"; };
88C659AF24688335002AC115 /* SelfSignedIdentity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelfSignedIdentity.swift; sourceTree = "<group>"; };
88C7597224B7EAA600DB03EA /* AdvancedPinSettingsTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdvancedPinSettingsTableViewController.swift; sourceTree = "<group>"; };
88D1D40122EBB5A100F472C5 /* MessageRequestView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRequestView.swift; sourceTree = "<group>"; };
88D1D40322EF8A9700F472C5 /* ThreadDetailsInteraction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadDetailsInteraction.swift; sourceTree = "<group>"; };
88D1D40522EF8F1100F472C5 /* ThreadDetailsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadDetailsCell.swift; sourceTree = "<group>"; };
@ -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 */,

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "more-horiz-24.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -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

View File

@ -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 {

View File

@ -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
}

View File

@ -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
)
}

View File

@ -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
}
}

View File

@ -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 =

View File

@ -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];

View File

@ -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<Bool> {
guard !OWS2FAManager.shared().isRegistrationLockV2Enabled else {
return showRegistrationLockConfirmation(fromViewController: fromViewController)
}
let (promise, resolver) = Promise<Bool>.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<Bool> {
let (promise, resolver) = Promise<Bool>.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<Bool> 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")

View File

@ -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:

View File

@ -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. Well 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 wont 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 dont 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. Well 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, well ask you to enter it periodically. Well 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 wont 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.";

View File

@ -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;

View File

@ -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;
};

View File

@ -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) {

View File

@ -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<Void> {
public static func generateAndBackupKeys(with pin: String, rotateMasterKey: Bool) -> Promise<Void> {
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

View File

@ -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;

View File

@ -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;

View File

@ -47,9 +47,9 @@ extension OWS2FAManager {
)
}
public func requestEnable2FA(withPin pin: String, mode: OWS2FAMode) -> Promise<Void> {
public func requestEnable2FA(withPin pin: String, mode: OWS2FAMode, rotateMasterKey: Bool = false) -> Promise<Void> {
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<Void> {
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
}
}
}

View File

@ -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