From eb71c4979443a4501425de40f8a16b2be6f28c6a Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Thu, 10 Jan 2019 11:21:33 -0700 Subject: [PATCH] registration validator --- Signal.xcodeproj/project.pbxproj | 8 +++ Signal/src/Models/PhoneNumberValidator.swift | 53 ++++++++++++++ .../Registration/RegistrationViewController.m | 3 +- Signal/test/PhoneNumberValidatorTest.swift | 69 +++++++++++++++++++ .../src/Util/NSRegularExpression+SSK.swift | 14 ++++ 5 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 Signal/src/Models/PhoneNumberValidator.swift create mode 100644 Signal/test/PhoneNumberValidatorTest.swift create mode 100644 SignalServiceKit/src/Util/NSRegularExpression+SSK.swift diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 5fd555667..9bf6c177a 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -453,6 +453,8 @@ 4C3EF802210918740007EBF7 /* SSKProtoEnvelopeTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3EF801210918740007EBF7 /* SSKProtoEnvelopeTest.swift */; }; 4C4AEC4520EC343B0020E72B /* DismissableTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C4AEC4420EC343B0020E72B /* DismissableTextField.swift */; }; 4C4BC6C32102D697004040C9 /* ContactDiscoveryOperationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C4BC6C22102D697004040C9 /* ContactDiscoveryOperationTest.swift */; }; + 4C5250D221E7BD7D00CE3D95 /* PhoneNumberValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C5250D121E7BD7D00CE3D95 /* PhoneNumberValidator.swift */; }; + 4C5250D421E7C51900CE3D95 /* PhoneNumberValidatorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C5250D321E7C51900CE3D95 /* PhoneNumberValidatorTest.swift */; }; 4C618199219DF03A009BD6B5 /* OWSButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C618198219DF03A009BD6B5 /* OWSButton.swift */; }; 4C61819F219E1796009BD6B5 /* typing-animation-dark.gif in Resources */ = {isa = PBXBuildFile; fileRef = 4C61819E219E1795009BD6B5 /* typing-animation-dark.gif */; }; 4C63CC00210A620B003AE45C /* SignalTSan.supp in Resources */ = {isa = PBXBuildFile; fileRef = 4C63CBFF210A620B003AE45C /* SignalTSan.supp */; }; @@ -1174,6 +1176,8 @@ 4C3EF801210918740007EBF7 /* SSKProtoEnvelopeTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSKProtoEnvelopeTest.swift; sourceTree = ""; }; 4C4AEC4420EC343B0020E72B /* DismissableTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DismissableTextField.swift; sourceTree = ""; }; 4C4BC6C22102D697004040C9 /* ContactDiscoveryOperationTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ContactDiscoveryOperationTest.swift; path = contact/ContactDiscoveryOperationTest.swift; sourceTree = ""; }; + 4C5250D121E7BD7D00CE3D95 /* PhoneNumberValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhoneNumberValidator.swift; sourceTree = ""; }; + 4C5250D321E7C51900CE3D95 /* PhoneNumberValidatorTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhoneNumberValidatorTest.swift; sourceTree = ""; }; 4C618198219DF03A009BD6B5 /* OWSButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OWSButton.swift; sourceTree = ""; }; 4C61819E219E1795009BD6B5 /* typing-animation-dark.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = "typing-animation-dark.gif"; sourceTree = ""; }; 4C63CBFF210A620B003AE45C /* SignalTSan.supp */ = {isa = PBXFileReference; lastKnownFileType = text; path = SignalTSan.supp; sourceTree = ""; }; @@ -2178,6 +2182,7 @@ 458E38351D668EBF0094BD24 /* OWSDeviceProvisioningURLParser.h */, 458E38361D668EBF0094BD24 /* OWSDeviceProvisioningURLParser.m */, 4CB5F26820F7D060004D1B42 /* MessageActions.swift */, + 4C5250D121E7BD7D00CE3D95 /* PhoneNumberValidator.swift */, ); path = Models; sourceTree = ""; @@ -2185,6 +2190,7 @@ 458E38381D6699110094BD24 /* Models */ = { isa = PBXGroup; children = ( + 4C5250D321E7C51900CE3D95 /* PhoneNumberValidatorTest.swift */, 4C04F58321C860C50090D0BB /* MantlePerfTest.swift */, 4C4BC6C22102D697004040C9 /* ContactDiscoveryOperationTest.swift */, 458E38391D6699FA0094BD24 /* OWSDeviceProvisioningURLParserTest.m */, @@ -3537,6 +3543,7 @@ 45B5360E206DD8BB00D61655 /* UIResponder+OWS.swift in Sources */, 3441FD9F21A3604F00BB9542 /* BackupRestoreViewController.swift in Sources */, 45F659821E1BE77000444429 /* NonCallKitCallUIAdaptee.swift in Sources */, + 4C5250D221E7BD7D00CE3D95 /* PhoneNumberValidator.swift in Sources */, 45AE48511E0732D6004D96C2 /* TurnServerInfo.swift in Sources */, 34B3F8771E8DF1700035BE1A /* ContactsPicker.swift in Sources */, 45C0DC1B1E68FE9000E04C47 /* UIApplication+OWS.swift in Sources */, @@ -3631,6 +3638,7 @@ 45E7A6A81E71CA7E00D44FB5 /* DisplayableTextFilterTest.swift in Sources */, 34843B2421432293004DED45 /* SignalBaseTest.m in Sources */, 4C3EF802210918740007EBF7 /* SSKProtoEnvelopeTest.swift in Sources */, + 4C5250D421E7C51900CE3D95 /* PhoneNumberValidatorTest.swift in Sources */, 452D1AF12081059C00A67F7F /* StringAdditionsTest.swift in Sources */, 4C4BC6C32102D697004040C9 /* ContactDiscoveryOperationTest.swift in Sources */, B660F6D21C29868000687D6E /* PushManagerTest.m in Sources */, diff --git a/Signal/src/Models/PhoneNumberValidator.swift b/Signal/src/Models/PhoneNumberValidator.swift new file mode 100644 index 000000000..dcab1e017 --- /dev/null +++ b/Signal/src/Models/PhoneNumberValidator.swift @@ -0,0 +1,53 @@ +// +// Copyright (c) 2019 Open Whisper Systems. All rights reserved. +// + +import Foundation +import SignalServiceKit + +@objc +public enum ValidatedPhoneCountryCodes: UInt { + case unitedStates = 1 + case brazil = 55 +} + +@objc +public class PhoneNumberValidator: NSObject { + + @objc + public func isValidForRegistration(phoneNumber: PhoneNumber) -> Bool { + guard let countryCode = phoneNumber.getCountryCode() else { + return false + } + + guard let validatedCountryCode = ValidatedPhoneCountryCodes(rawValue: countryCode.uintValue) else { + // no extra validation for this country + return true + } + + switch validatedCountryCode { + case .brazil: + return isValidForBrazilRegistration(phoneNumber: phoneNumber) + case .unitedStates: + return isValidForUnitedStatesRegistration(phoneNumber: phoneNumber) + } + } + + let validBrazilPhoneNumberRegex = try! NSRegularExpression(pattern: "^\\+55\\d{2}9?\\d{8}$", options: []) + private func isValidForBrazilRegistration(phoneNumber: PhoneNumber) -> Bool { + guard let e164 = phoneNumber.toE164() else { + return false + } + + return validBrazilPhoneNumberRegex.hasMatch(input: e164) + } + + let validUnitedStatesPhoneNumberRegex = try! NSRegularExpression(pattern: "^\\+1\\d{10}$", options: []) + private func isValidForUnitedStatesRegistration(phoneNumber: PhoneNumber) -> Bool { + guard let e164 = phoneNumber.toE164() else { + return false + } + + return validUnitedStatesPhoneNumberRegex.hasMatch(input: e164) + } +} diff --git a/Signal/src/ViewControllers/Registration/RegistrationViewController.m b/Signal/src/ViewControllers/Registration/RegistrationViewController.m index a1aa2b43e..2ea33ad28 100644 --- a/Signal/src/ViewControllers/Registration/RegistrationViewController.m +++ b/Signal/src/ViewControllers/Registration/RegistrationViewController.m @@ -414,7 +414,8 @@ NSString *const kKeychainKey_LastRegisteredPhoneNumber = @"kKeychainKey_LastRegi NSString *phoneNumber = [NSString stringWithFormat:@"%@%@", _callingCode, phoneNumberText]; PhoneNumber *localNumber = [PhoneNumber tryParsePhoneNumberFromUserSpecifiedText:phoneNumber]; NSString *parsedPhoneNumber = localNumber.toE164; - if (parsedPhoneNumber.length < 1) { + if (parsedPhoneNumber.length < 1 + || ![[PhoneNumberValidator new] isValidForRegistrationWithPhoneNumber:localNumber]) { [OWSAlerts showAlertWithTitle: NSLocalizedString(@"REGISTRATION_VIEW_INVALID_PHONE_NUMBER_ALERT_TITLE", @"Title of alert indicating that users needs to enter a valid phone number to register.") diff --git a/Signal/test/PhoneNumberValidatorTest.swift b/Signal/test/PhoneNumberValidatorTest.swift new file mode 100644 index 000000000..62543a06b --- /dev/null +++ b/Signal/test/PhoneNumberValidatorTest.swift @@ -0,0 +1,69 @@ +// +// Copyright (c) 2019 Open Whisper Systems. All rights reserved. +// + +import XCTest +import Signal + +class PhoneNumberValidatorTest: SignalBaseTest { + + func assertValid(e164: String, + file: StaticString = #file, + line: UInt = #line) { + let validator = PhoneNumberValidator() + guard let phoneNumber = PhoneNumber(fromE164: e164) else { + XCTFail("unparseable phone number", file: file, line: line) + return + } + let isValid = validator.isValidForRegistration(phoneNumber: phoneNumber) + XCTAssertTrue(isValid, file: file, line: line) + } + + func assertInvalid(e164: String, + file: StaticString = #file, + line: UInt = #line) { + let validator = PhoneNumberValidator() + guard let phoneNumber = PhoneNumber(fromE164: e164) else { + XCTFail("unparseable phone number", file: file, line: line) + return + } + let isValid = validator.isValidForRegistration(phoneNumber: phoneNumber) + XCTAssertFalse(isValid, file: file, line: line) + } + + func testUnitedStates() { + // valid us number + assertValid(e164: "+13235551234") + + // too short + assertInvalid(e164: "+1323555123") + + // too long + assertInvalid(e164: "+132355512345") + + // not a US phone number + assertValid(e164: "+3235551234") + } + + func testBrazil() { + // valid mobile + assertValid(e164: "+5532912345678") + + // valid landline + assertValid(e164: "+553212345678") + + // mobile length, but with out the leading '9' + assertInvalid(e164: "+5532812345678") + + // too short + assertInvalid(e164: "+5532812345678") + + // too long landline + assertInvalid(e164: "+5532123456789") + assertInvalid(e164: "+55321234567890") + + // too long mobile + assertInvalid(e164: "+55329123456789") + assertInvalid(e164: "+553291234567890") + } +} diff --git a/SignalServiceKit/src/Util/NSRegularExpression+SSK.swift b/SignalServiceKit/src/Util/NSRegularExpression+SSK.swift new file mode 100644 index 000000000..33d20d49d --- /dev/null +++ b/SignalServiceKit/src/Util/NSRegularExpression+SSK.swift @@ -0,0 +1,14 @@ +// +// Copyright (c) 2019 Open Whisper Systems. All rights reserved. +// + +import Foundation + +@objc +public extension NSRegularExpression { + + @objc + public func hasMatch(input: String) -> Bool { + return self.firstMatch(in: input, options: [], range: NSRange(location: 0, length: input.utf16.count)) != nil + } +}