Skip to content

Commit

Permalink
Merge pull request #149 from verygoodsecurity/canary
Browse files Browse the repository at this point in the history
Public Release 1.5.2
  • Loading branch information
dmytrokhl committed Jun 2, 2020
2 parents cb77723 + bb2a5f8 commit fc96545
Show file tree
Hide file tree
Showing 84 changed files with 1,389 additions and 186 deletions.
1 change: 1 addition & 0 deletions .jazzy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ custom_categories:
- VGSCollect
- Environment
- State
- SSNState
- CardState
- VGSResponse
- HTTPMethod
Expand Down
12 changes: 10 additions & 2 deletions Sources/VGSCollectSDK/Core/Collector/VGSCollect.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public class VGSCollect {
internal let storage = Storage()
/// Max file size limit by proxy. Is static and can't be changed!
internal let maxFileSizeInternalLimitInBytes = 24_000_000

// MARK: Custom HTTP Headers

/// Set your custom HTTP headers
Expand All @@ -29,14 +29,22 @@ public class VGSCollect {
}
}

// MARK: - Observe VGSTextField states
// MARK: Observe VGSTextField states

/// Observe only focused `VGSTextField` on editing events.
public var observeFieldState: ((_ textField: VGSTextField) -> Void)?

/// Observe all `VGSTextField` on editing events.
public var observeStates: ((_ form: [VGSTextField]) -> Void)?


// MARK: Get Registered VGSTextFields

/// Returns array of `VGSTextField`s associated with `VGSCollect` instance.
public var textFields: [VGSTextField] {
return storage.elements
}

// MARK: - Initialzation

/// Initialzation
Expand Down
29 changes: 16 additions & 13 deletions Sources/VGSCollectSDK/Core/Enums.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ public enum FieldType: Int, CaseIterable {

/// Field type that requires Cardholder Name input formatting and validation.
case cardHolderName

/// Field type that requires US Social Security Number input formatting and validation.
case ssn
}

internal extension FieldType {
Expand All @@ -49,16 +52,22 @@ internal extension FieldType {
return "####"
case .expDate:
return DateFormatPattern.shortYear.rawValue
case .ssn:
return "###-##-####"
default:
return ""
}
}

var defaultDivider: String {
if self == .expDate {
return "/"
}
switch self {
case .expDate:
return "/"
case .ssn:
return "-"
default:
return ""
}
}

var regex: String {
Expand All @@ -71,23 +80,17 @@ internal extension FieldType {
return "^(0[1-9]|1[0-2])\\/?([0-9]{4}|[0-9]{2})$"
case .cardHolderName:
return "^([a-zA-Z0-9\\ \\,\\.\\-\\']{2,})$"
case .ssn:
return
"^(?!\\b(\\d)\\1+\\b)(?!(123456789|219099999|078051120|457555462))(?!(000|666|9))(\\d{3}-?(?!(00))\\d{2}-?(?!(0000))\\d{4})$"
default:
return ""
}
}

var isSecureDate: Bool {
switch self {
case .cvc:
return true
default:
return false
}
}

var keyboardType: UIKeyboardType {
switch self {
case .cardNumber, .cvc, .expDate:
case .cardNumber, .cvc, .expDate, .ssn:
return .asciiCapableNumberPad
default:
return .alphabet
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,11 @@ public class VGSCardIOScanController {

/// `VGSCardIOScanControllerDelegate` - handle user interaction with `Card.io` scanner
public var delegate: VGSCardIOScanControllerDelegate? {
didSet {
scanHandler?.delegate = delegate
set {
scanHandler?.delegate = newValue
}
get {
return scanHandler?.delegate
}
}

Expand Down
31 changes: 30 additions & 1 deletion Sources/VGSCollectSDK/UIElements/Text Field/State/State.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public class State {
}
}

/// An object that describes `VGSCardTextField` state. State attributes are read-only.
/// An object that describes `VGSTextField` state with configuration `FieldType.cardNumber` . State attributes are read-only.
public class CardState: State {

/// Last 4 digits of the valid card number from associated `VGSTextField` with field configuration type `FieldType.cardNumber`.
Expand Down Expand Up @@ -102,3 +102,32 @@ public class CardState: State {
return result
}
}

/// An object that describes `VGSTextField` state with configuration `FieldType.ssn` . State attributes are read-only.
public class SSNState: State {

/// Last 4 digits of the valid ssn from associated `VGSTextField` with field configuration type `FieldType.ssn`.
internal(set) open var last4: String = ""

override init(tf: VGSTextField) {
super.init(tf: tf)

guard let originalText = tf.textField.getSecureRawText else {
return
}
self.last4 = self.isValid ? String(originalText.suffix(4)) : ""
}

/// Message that contains `SSNState` attributes and their values.
override public var description: String {
var result = super.description
if isValid {
result.append("""
, {
"last4": \(last4)
}
""")
}
return result
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ public extension VGSTextField {
switch fieldType {
case .cardNumber:
result = CardState(tf: self)
case .ssn:
result = SSNState(tf: self)
default:
result = State(tf: self)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ public class VGSTextField: UIView {
didSet { textField.clearButtonMode = clearButtonMode }
}

/// Identifies whether the text object should disable text copying and in some cases hide the text being entered. Default is false.
public var isSecureTextEntry: Bool = false {
didSet { textField.isSecureTextEntry = isSecureTextEntry }
}

/// Indicates whether `VGSTextField ` should automatically update its font when the device’s `UIContentSizeCategory` is changed.
public var adjustsFontForContentSizeCategory: Bool = false {
didSet { textField.adjustsFontForContentSizeCategory = adjustsFontForContentSizeCategory }
Expand All @@ -71,7 +76,6 @@ public class VGSTextField: UIView {
isRequired = configuration.isRequired
isRequiredValidOnly = configuration.isRequiredValidOnly
fieldType = configuration.type
textField.isSecureTextEntry = configuration.type.isSecureDate
textField.keyboardType = configuration.keyboardType ?? configuration.type.keyboardType
textField.returnKeyType = configuration.returnKeyType ?? .default
textField.keyboardAppearance = configuration.keyboardAppearance ?? .default
Expand Down
7 changes: 3 additions & 4 deletions Tests/FrameworkTests/Card Brand Tests/CardBrandTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,13 +82,12 @@ class CardBrandTest: XCTestCase {
allBrands.forEach { brand in
let numbers = brand.cardNumbers
for number in numbers {
/// there are 19 digits numbers that detected as valid by Luhn algorithms when there are 16-19 digits
if number.count > 16 {
continue
}

let digitsCount = Int.random(in: 1...6)

let input = String(number.prefix(number.count - digitsCount))

let input = String(number.prefix(number.count - 1))
cardTextField.textField.secureText = input
guard let state = cardTextField.state as? CardState else {
XCTFail("Guard fail")
Expand Down
105 changes: 105 additions & 0 deletions Tests/FrameworkTests/Text Fields Tests/SSNTextFieldTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
//
// SSNTextFieldTests.swift
// FrameworkTests
//
// Created by Dima on 29.05.2020.
// Copyright © 2020 VGS. All rights reserved.
//

import XCTest
@testable import VGSCollectSDK

class SSNTextFieldTests: XCTestCase {
var collector: VGSCollect!
var ssnTextField: VGSTextField!

override func setUp() {
collector = VGSCollect(id: "tntva5wfdrp")

ssnTextField = VGSTextField()

let config = VGSConfiguration(collector: collector, fieldName: "ssn")
config.type = .ssn
config.isRequired = true
ssnTextField.configuration = config

ssnTextField.textField.secureText = "123-44-5555"
}

override func tearDown() {
collector = nil
ssnTextField = nil
}

func testAlias() {
XCTAssertTrue(ssnTextField.state.fieldName == "ssn")
}

func testContent() {
XCTAssertTrue(ssnTextField.textField.secureText == "123-44-5555")
}

func testStates() {
let state = ssnTextField.state
XCTAssertFalse(state.isEmpty)
XCTAssertTrue(state.isValid)
XCTAssertTrue(state.isRequired)
XCTAssertNotNil(state.description)
XCTAssertTrue(state is SSNState, "SSNState should be available for .ssn configuration")
}

func testNotValidSSNReturnsFalse() {
let config = VGSConfiguration(collector: collector, fieldName: "ssn")
config.type = .ssn
config.formatPattern = ""
ssnTextField.configuration = config

let notValidSSN = [
"111111111", "222222222", "555555555",
"666666666", "999999999", "000000000",
"000123456", "143004563", "235230000",
"923423423", "666123456", "123456789",
"219099999", "078051120", "457555462",
"22334455", "3434343", "11111111222"
]

for ssn in notValidSSN {
ssnTextField.textField.secureText = ssn
ssnTextField.focusOn()
XCTAssertFalse(ssnTextField.state.isValid)
XCTAssertFalse(ssnTextField.state.isEmpty)
if let state = ssnTextField.state as? SSNState {
XCTAssertTrue(state.last4 == "")
}
}
}

func testValidSSNReturnsTrue() {
let config = VGSConfiguration(collector: collector, fieldName: "ssn")
config.type = .ssn
ssnTextField.configuration = config

let validSSN = [
"111111112", "222232222", "455555555",
"166666666", "899999999", "001010001",
"100123456", "143104563", "235231000",
"823423423", "665123455", "123456780",
"219099998", "078051125", "457555465",
/// mask should cut extra symbols
"234567890123456789"
]

for ssn in validSSN {
ssnTextField.textField.secureText = ssn
ssnTextField.focusOn()
XCTAssertTrue(ssnTextField.state.isValid)
XCTAssertTrue(ssnTextField.state.inputLength == 9)
XCTAssertFalse(ssnTextField.state.isEmpty)

if let state = ssnTextField.state as? SSNState {
XCTAssertTrue(state.last4.count == 4)
}
}
}
}

29 changes: 26 additions & 3 deletions Tests/FrameworkTests/VGSCollectTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,17 +52,40 @@ class VGSCollectTests: XCTestCase {
XCTAssertTrue(VGSCollect.canOpen(path: path))
}

func testRegistrationTextField() {
func testRegistrationSingleTextField() {
let config = VGSConfiguration(collector: collector, fieldName: "test")
let tf = VGSTextField()
let tf = VGSCardTextField()
tf.configuration = config

XCTAssertTrue(collector.storage.elements.count == 1)

XCTAssertTrue(collector.textFields.count == 1)

collector.unregisterTextFields(textField: [tf])

XCTAssertTrue(collector.storage.elements.count == 0)
XCTAssertTrue(collector.textFields.count == 0)
}

func testRegistrationMultipleTextFields() {

let fieldsCount = 5

collector = VGSCollect(id: "tntva5wfdrp")

for _ in 0..<fieldsCount {
let config = VGSConfiguration(collector: collector, fieldName: "test")
let tf = VGSTextField()
tf.configuration = config
}

XCTAssertTrue(collector.storage.elements.count == fieldsCount)
XCTAssertTrue(collector.textFields.count == fieldsCount)

collector.unregisterTextFields(textField: collector.textFields)

XCTAssertTrue(collector.storage.elements.count == 0)
XCTAssertTrue(collector.textFields.count == 0)
}

func testCustomJsonMapping() {
let cardConfiguration = VGSConfiguration(collector: collector, fieldName: "user.card_data.card_number")
Expand Down
2 changes: 1 addition & 1 deletion VGSCollectSDK.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |spec|
spec.name = 'VGSCollectSDK'
spec.version = '1.5.1'
spec.version = '1.5.2'
spec.summary = 'VGS Collect - is a product suite that allows customers to collect information securely without possession of it.'
spec.swift_version = '5.0'
spec.description = <<-DESC
Expand Down
Loading

0 comments on commit fc96545

Please sign in to comment.