Introduce TokenBuilder to build up OTP tokens conveniently
This commit is contained in:
parent
6817f61e3b
commit
2e744a760f
6 changed files with 363 additions and 79 deletions
|
|
@ -17,9 +17,11 @@
|
|||
302E85632125EE550031BA64 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 302E85622125EE550031BA64 /* Constants.swift */; };
|
||||
30A1D29C21AF451E00E2D1F7 /* PasswordGeneratorFlavourTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A1D29B21AF451E00E2D1F7 /* PasswordGeneratorFlavourTest.swift */; };
|
||||
30A1D29E21AF468F00E2D1F7 /* PasswordGeneratorFlavour.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A1D29D21AF468E00E2D1F7 /* PasswordGeneratorFlavour.swift */; };
|
||||
30A1D2A221B2BC6F00E2D1F7 /* TokenBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A1D2A121B2BC6F00E2D1F7 /* TokenBuilder.swift */; };
|
||||
30A1D2A621B2D46100E2D1F7 /* OtpType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A1D2A521B2D46100E2D1F7 /* OtpType.swift */; };
|
||||
30A1D2A821B2D53200E2D1F7 /* PasswordChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A1D2A721B2D53200E2D1F7 /* PasswordChange.swift */; };
|
||||
30A1D2AA21B32A0100E2D1F7 /* OtpTypeTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A1D2A921B32A0100E2D1F7 /* OtpTypeTest.swift */; };
|
||||
30A1D2AC21B32C2A00E2D1F7 /* TokenBuilderTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A1D2AB21B32C2A00E2D1F7 /* TokenBuilderTest.swift */; };
|
||||
30B04860209A5141001013CA /* PasswordTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30B0485F209A5141001013CA /* PasswordTest.swift */; };
|
||||
30FD2F78214D9E0E005E0A92 /* ParserTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30FD2F77214D9E0E005E0A92 /* ParserTest.swift */; };
|
||||
61326CDA7A73757FB68DCB04 /* Pods_passKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DAB3F5541E51ADC8C6B56642 /* Pods_passKit.framework */; };
|
||||
|
|
@ -194,9 +196,11 @@
|
|||
302E85622125EE550031BA64 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = "<group>"; };
|
||||
30A1D29B21AF451E00E2D1F7 /* PasswordGeneratorFlavourTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordGeneratorFlavourTest.swift; sourceTree = "<group>"; };
|
||||
30A1D29D21AF468E00E2D1F7 /* PasswordGeneratorFlavour.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PasswordGeneratorFlavour.swift; path = Helpers/PasswordGeneratorFlavour.swift; sourceTree = "<group>"; };
|
||||
30A1D2A121B2BC6F00E2D1F7 /* TokenBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenBuilder.swift; sourceTree = "<group>"; };
|
||||
30A1D2A521B2D46100E2D1F7 /* OtpType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OtpType.swift; sourceTree = "<group>"; };
|
||||
30A1D2A721B2D53200E2D1F7 /* PasswordChange.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasswordChange.swift; sourceTree = "<group>"; };
|
||||
30A1D2A921B32A0100E2D1F7 /* OtpTypeTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OtpTypeTest.swift; sourceTree = "<group>"; };
|
||||
30A1D2AB21B32C2A00E2D1F7 /* TokenBuilderTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenBuilderTest.swift; sourceTree = "<group>"; };
|
||||
30B0485F209A5141001013CA /* PasswordTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordTest.swift; sourceTree = "<group>"; };
|
||||
30FD2F77214D9E0E005E0A92 /* ParserTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParserTest.swift; sourceTree = "<group>"; };
|
||||
31C3033E8868D05B2C55C8B1 /* Pods-passExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-passExtension.debug.xcconfig"; path = "Pods/Target Support Files/Pods-passExtension/Pods-passExtension.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
|
|
@ -400,6 +404,7 @@
|
|||
30A1D2A521B2D46100E2D1F7 /* OtpType.swift */,
|
||||
302E85602125ECC70031BA64 /* Parser.swift */,
|
||||
30A1D2A721B2D53200E2D1F7 /* PasswordChange.swift */,
|
||||
30A1D2A121B2BC6F00E2D1F7 /* TokenBuilder.swift */,
|
||||
);
|
||||
path = Parser;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -411,6 +416,7 @@
|
|||
301F6467216165290071A4CE /* ConstantsTest.swift */,
|
||||
30A1D2A921B32A0100E2D1F7 /* OtpTypeTest.swift */,
|
||||
30FD2F77214D9E0E005E0A92 /* ParserTest.swift */,
|
||||
30A1D2AB21B32C2A00E2D1F7 /* TokenBuilderTest.swift */,
|
||||
);
|
||||
path = Parser;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -1152,6 +1158,7 @@
|
|||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
30A1D2A221B2BC6F00E2D1F7 /* TokenBuilder.swift in Sources */,
|
||||
A2BEC1BB207D2EFE00F3051C /* UIViewExtension.swift in Sources */,
|
||||
302E85632125EE550031BA64 /* Constants.swift in Sources */,
|
||||
301F6463216162550071A4CE /* AdditionField.swift in Sources */,
|
||||
|
|
@ -1183,6 +1190,7 @@
|
|||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
301F646A216166000071A4CE /* StringExtensionTest.swift in Sources */,
|
||||
30A1D2AC21B32C2A00E2D1F7 /* TokenBuilderTest.swift in Sources */,
|
||||
301F646D216166AA0071A4CE /* AdditionFieldTest.swift in Sources */,
|
||||
30FD2F78214D9E0E005E0A92 /* ParserTest.swift in Sources */,
|
||||
30B04860209A5141001013CA /* PasswordTest.swift in Sources */,
|
||||
|
|
|
|||
|
|
@ -143,84 +143,26 @@ public class Password {
|
|||
|
||||
*/
|
||||
private func updateOtpToken() {
|
||||
self.otpToken = nil
|
||||
|
||||
// get otpauth, if we are able to generate a token, return
|
||||
if var otpauthString = getAdditionValue(withKey: Constants.OTPAUTH, caseSensitive: true) {
|
||||
if !otpauthString.hasPrefix("\(Constants.OTPAUTH):") {
|
||||
otpauthString = "\(Constants.OTPAUTH):\(otpauthString)"
|
||||
}
|
||||
if let otpauthUrl = URL(string: otpauthString),
|
||||
let token = Token(url: otpauthUrl) {
|
||||
self.otpToken = token
|
||||
if let otpauthUrl = URL(string: otpauthString), let token = Token(url: otpauthUrl) {
|
||||
otpToken = token
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// get secret data
|
||||
guard let secretString = getAdditionValue(withKey: Constants.OTP_SECRET),
|
||||
let secretData = MF_Base32Codec.data(fromBase32String: secretString),
|
||||
!secretData.isEmpty else {
|
||||
// Missing / Invalid otp secret
|
||||
return
|
||||
}
|
||||
|
||||
// get type
|
||||
guard let type = getAdditionValue(withKey: Constants.OTP_TYPE)?.lowercased(),
|
||||
(type == Constants.TOTP || type == Constants.HOTP) else {
|
||||
// Missing/Invalid OTP type
|
||||
return
|
||||
}
|
||||
|
||||
// get algorithm (optional)
|
||||
var algorithm = Generator.Algorithm.sha1
|
||||
if let algoString = getAdditionValue(withKey: Constants.OTP_ALGORITHM) {
|
||||
switch algoString.lowercased() {
|
||||
case Constants.SHA256:
|
||||
algorithm = .sha256
|
||||
case Constants.SHA512:
|
||||
algorithm = .sha512
|
||||
default:
|
||||
algorithm = .sha1
|
||||
}
|
||||
}
|
||||
|
||||
// construct the token
|
||||
if type == Constants.TOTP {
|
||||
// HOTP
|
||||
// default: 6 digits, 30 seconds
|
||||
guard let digits = Int(getAdditionValue(withKey: Constants.OTP_DIGITS) ?? Constants.DEFAULT_DIGITS),
|
||||
let period = Double(getAdditionValue(withKey: Constants.OTP_PERIOD) ?? Constants.DEFAULT_PERIOD) else {
|
||||
// Invalid OTP digits or OTP period.
|
||||
return
|
||||
}
|
||||
guard let generator = Generator(
|
||||
factor: .timer(period: period),
|
||||
secret: secretData,
|
||||
algorithm: algorithm,
|
||||
digits: digits) else {
|
||||
// Invalid OTP generator parameters.
|
||||
return
|
||||
}
|
||||
self.otpToken = Token(name: self.name, issuer: "", generator: generator)
|
||||
} else {
|
||||
// HOTP
|
||||
// default: 6 digits
|
||||
guard let digits = Int(getAdditionValue(withKey: Constants.OTP_DIGITS) ?? Constants.DEFAULT_DIGITS),
|
||||
let counter = UInt64(getAdditionValue(withKey: Constants.OTP_COUNTER) ?? Constants.DEFAULT_COUNTER) else {
|
||||
// Invalid OTP digits or OTP counter.
|
||||
return
|
||||
}
|
||||
guard let generator = Generator(
|
||||
factor: .counter(counter),
|
||||
secret: secretData,
|
||||
algorithm: algorithm,
|
||||
digits: digits) else {
|
||||
// Invalid OTP generator parameters.
|
||||
return
|
||||
}
|
||||
self.otpToken = Token(name: self.name, issuer: "", generator: generator)
|
||||
}
|
||||
otpToken = TokenBuilder()
|
||||
.usingName(name)
|
||||
.usingSecret(getAdditionValue(withKey: Constants.OTP_SECRET))
|
||||
.usingType(getAdditionValue(withKey: Constants.OTP_TYPE))
|
||||
.usingAlgorithm(getAdditionValue(withKey: Constants.OTP_ALGORITHM))
|
||||
.usingDigits(getAdditionValue(withKey: Constants.OTP_DIGITS))
|
||||
.usingPeriod(getAdditionValue(withKey: Constants.OTP_PERIOD))
|
||||
.usingCounter(getAdditionValue(withKey: Constants.OTP_COUNTER))
|
||||
.build()
|
||||
}
|
||||
|
||||
// return the description and the password strings
|
||||
|
|
|
|||
|
|
@ -30,9 +30,9 @@ public struct Constants {
|
|||
static let HOTP = "hotp"
|
||||
static let SHA256 = "sha256"
|
||||
static let SHA512 = "sha512"
|
||||
static let DEFAULT_DIGITS = "6"
|
||||
static let DEFAULT_PERIOD = "30.0"
|
||||
static let DEFAULT_COUNTER = ""
|
||||
static let DEFAULT_DIGITS = 6
|
||||
static let DEFAULT_PERIOD = 30.0
|
||||
static let DEFAULT_COUNTER: UInt64? = nil
|
||||
|
||||
static let BLANK = " "
|
||||
static let MULTILINE_WITH_LINE_BREAK_INDICATOR = "|"
|
||||
|
|
|
|||
88
passKit/Parser/TokenBuilder.swift
Normal file
88
passKit/Parser/TokenBuilder.swift
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
//
|
||||
// TokenBuilder.swift
|
||||
// passKit
|
||||
//
|
||||
// Created by Danny Moesch on 01.12.18.
|
||||
// Copyright © 2018 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
import Base32
|
||||
import OneTimePassword
|
||||
|
||||
class TokenBuilder {
|
||||
|
||||
private var name: String = ""
|
||||
private var secret: Data?
|
||||
private var type: OtpType = .totp
|
||||
private var algorithm: Generator.Algorithm = .sha1
|
||||
private var digits: Int? = Constants.DEFAULT_DIGITS
|
||||
private var period: Double? = Constants.DEFAULT_PERIOD
|
||||
private var counter: UInt64? = Constants.DEFAULT_COUNTER
|
||||
|
||||
func usingName(_ name: String) -> TokenBuilder {
|
||||
self.name = name
|
||||
return self
|
||||
}
|
||||
|
||||
func usingSecret(_ secret: String?) -> TokenBuilder {
|
||||
if secret != nil, let secretData = MF_Base32Codec.data(fromBase32String: secret!), !secretData.isEmpty {
|
||||
self.secret = secretData
|
||||
}
|
||||
return self
|
||||
}
|
||||
|
||||
func usingType(_ type: String?) -> TokenBuilder {
|
||||
self.type = OtpType(name: type)
|
||||
return self
|
||||
}
|
||||
|
||||
func usingAlgorithm(_ algorithm: String?) -> TokenBuilder {
|
||||
switch algorithm?.lowercased() {
|
||||
case Constants.SHA256:
|
||||
self.algorithm = .sha256
|
||||
case Constants.SHA512:
|
||||
self.algorithm = .sha512
|
||||
default:
|
||||
self.algorithm = .sha1
|
||||
}
|
||||
return self
|
||||
}
|
||||
|
||||
func usingDigits(_ digits: String?) -> TokenBuilder {
|
||||
self.digits = digits == nil ? nil : Int(digits!)
|
||||
return self
|
||||
}
|
||||
|
||||
func usingPeriod(_ period: String?) -> TokenBuilder {
|
||||
self.period = period == nil ? nil : Double(period!)
|
||||
return self
|
||||
}
|
||||
|
||||
func usingCounter(_ counter: String?) -> TokenBuilder {
|
||||
self.counter = counter == nil ? nil : UInt64(counter!)
|
||||
return self
|
||||
}
|
||||
|
||||
|
||||
func build() -> Token? {
|
||||
guard secret != nil, digits != nil else {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch type {
|
||||
case .totp:
|
||||
return period == nil ? nil : createToken(factor: Generator.Factor.timer(period: period!))
|
||||
case .hotp:
|
||||
return counter == nil ? nil : createToken(factor: Generator.Factor.counter(counter!))
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
private func createToken(factor: Generator.Factor) -> Token? {
|
||||
guard let generator = Generator(factor: factor, secret: secret!, algorithm: algorithm, digits: digits!) else {
|
||||
return nil
|
||||
}
|
||||
return Token(name: name, issuer: "", generator: generator)
|
||||
}
|
||||
}
|
||||
|
|
@ -15,7 +15,8 @@ class PasswordTest: XCTestCase {
|
|||
private let PASSWORD_PATH = "/path/to/password"
|
||||
private let PASSWORD_URL = URL(fileURLWithPath: "/path/to/password")
|
||||
private let PASSWORD_STRING = "abcd1234"
|
||||
private let OTP_TOKEN = "otpauth://totp/email@email.com?secret=abcd1234"
|
||||
private let TOTP_URL = "otpauth://totp/email@email.com?secret=abcd1234"
|
||||
private let HOTP_URL = "otpauth://hotp/email@email.com?secret=abcd1234"
|
||||
|
||||
private let SECURE_URL_FIELD = "url" => "https://secure.com"
|
||||
private let INSECURE_URL_FIELD = "url" => "http://insecure.com"
|
||||
|
|
@ -23,6 +24,7 @@ class PasswordTest: XCTestCase {
|
|||
private let USERNAME_FIELD = "username" => "some username"
|
||||
private let NOTE_FIELD = "note" => "A NOTE"
|
||||
private let HINT_FIELD = "some hints" => "äöüß // €³ %% −° && @²` | [{\\}],.<>"
|
||||
private let TOTP_URL_FIELD = "otpauth" => "//totp/email@email.com?secret=abcd1234"
|
||||
|
||||
func testUrl() {
|
||||
let password = getPasswordObjectWith(content: "")
|
||||
|
|
@ -158,7 +160,7 @@ class PasswordTest: XCTestCase {
|
|||
}
|
||||
|
||||
func testPasswordFileWithOtpToken() {
|
||||
let additions = NOTE_FIELD | OTP_TOKEN
|
||||
let additions = NOTE_FIELD | TOTP_URL
|
||||
let fileContent = PASSWORD_STRING | additions
|
||||
let password = getPasswordObjectWith(content: fileContent)
|
||||
|
||||
|
|
@ -166,22 +168,52 @@ class PasswordTest: XCTestCase {
|
|||
XCTAssertEqual(password.plainData, fileContent.data(using: .utf8))
|
||||
XCTAssertEqual(password.additionsPlainText, additions)
|
||||
|
||||
XCTAssertEqual(password.otpType, OtpType.totp)
|
||||
XCTAssertEqual(password.otpType, .totp)
|
||||
XCTAssertNotNil(password.currentOtp)
|
||||
}
|
||||
|
||||
func testFirstLineIsOtpToken() {
|
||||
let password = getPasswordObjectWith(content: OTP_TOKEN)
|
||||
let password = getPasswordObjectWith(content: TOTP_URL)
|
||||
|
||||
XCTAssertEqual(password.password, OTP_TOKEN)
|
||||
XCTAssertEqual(password.plainData, OTP_TOKEN.data(using: .utf8))
|
||||
XCTAssertEqual(password.password, TOTP_URL)
|
||||
XCTAssertEqual(password.plainData, TOTP_URL.data(using: .utf8))
|
||||
XCTAssertEqual(password.additionsPlainText, "")
|
||||
|
||||
XCTAssertNil(password.username)
|
||||
XCTAssertNil(password.urlString)
|
||||
XCTAssertNil(password.login)
|
||||
|
||||
XCTAssertEqual(password.otpType, OtpType.totp)
|
||||
XCTAssertEqual(password.otpType, .totp)
|
||||
XCTAssertNotNil(password.currentOtp)
|
||||
}
|
||||
|
||||
func testOtpTokenAsField() {
|
||||
let additions = TOTP_URL_FIELD.asString
|
||||
let fileContent = PASSWORD_STRING | additions
|
||||
let password = getPasswordObjectWith(content: fileContent)
|
||||
|
||||
XCTAssertEqual(password.password, PASSWORD_STRING)
|
||||
XCTAssertEqual(password.plainData, fileContent.data(using: .utf8))
|
||||
XCTAssertEqual(password.additionsPlainText, additions)
|
||||
|
||||
XCTAssertEqual(password.otpType, .totp)
|
||||
XCTAssertNotNil(password.currentOtp)
|
||||
}
|
||||
|
||||
func testOtpTokenFromFields() {
|
||||
let additions =
|
||||
Constants.OTP_SECRET => "secret" |
|
||||
Constants.OTP_TYPE => "hotp" |
|
||||
Constants.OTP_COUNTER => "12" |
|
||||
Constants.OTP_DIGITS => "7"
|
||||
let fileContent = PASSWORD_STRING | additions
|
||||
let password = getPasswordObjectWith(content: fileContent)
|
||||
|
||||
XCTAssertEqual(password.password, PASSWORD_STRING)
|
||||
XCTAssertEqual(password.plainData, fileContent.data(using: .utf8))
|
||||
XCTAssertEqual(password.additionsPlainText, additions)
|
||||
|
||||
XCTAssertEqual(password.otpType, .hotp)
|
||||
XCTAssertNotNil(password.currentOtp)
|
||||
}
|
||||
|
||||
|
|
|
|||
214
passKitTests/Parser/TokenBuilderTest.swift
Normal file
214
passKitTests/Parser/TokenBuilderTest.swift
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
//
|
||||
// TokenBuilderTest.swift
|
||||
// passKitTests
|
||||
//
|
||||
// Created by Danny Moesch on 01.12.18.
|
||||
// Copyright © 2018 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
import Base32
|
||||
import OneTimePassword
|
||||
import XCTest
|
||||
|
||||
@testable import passKit
|
||||
|
||||
class TokenBuilderTest: XCTestCase {
|
||||
|
||||
private let SECRET = "secret"
|
||||
private let DIGITS = Constants.DEFAULT_DIGITS
|
||||
private let TIMER = Generator.Factor.timer(period: Constants.DEFAULT_PERIOD)
|
||||
|
||||
func testNoSecret() {
|
||||
XCTAssertNil(TokenBuilder().build())
|
||||
XCTAssertNil(TokenBuilder().usingSecret(nil).build())
|
||||
XCTAssertNil(TokenBuilder().usingSecret("").build())
|
||||
}
|
||||
|
||||
func testDefault() {
|
||||
let token = TokenBuilder()
|
||||
.usingSecret(SECRET)
|
||||
.build()
|
||||
|
||||
XCTAssertEqual(token?.generator.secret, MF_Base32Codec.data(fromBase32String: SECRET))
|
||||
XCTAssertEqual(token?.generator.factor, TIMER)
|
||||
XCTAssertEqual(token?.generator.algorithm, .sha1)
|
||||
XCTAssertEqual(token?.generator.digits, DIGITS)
|
||||
}
|
||||
|
||||
func testName() {
|
||||
[
|
||||
"some name",
|
||||
"a",
|
||||
"totp",
|
||||
].forEach { name in
|
||||
let token = TokenBuilder()
|
||||
.usingName(name)
|
||||
.usingSecret(SECRET)
|
||||
.build()
|
||||
|
||||
XCTAssertEqual(token?.name, name)
|
||||
}
|
||||
}
|
||||
|
||||
func testTypeNone() {
|
||||
let token = TokenBuilder()
|
||||
.usingSecret(SECRET)
|
||||
.usingType("something")
|
||||
.build()
|
||||
|
||||
XCTAssertNil(token)
|
||||
}
|
||||
|
||||
func testTypeTotp() {
|
||||
let token = TokenBuilder()
|
||||
.usingSecret(SECRET)
|
||||
.usingType("toTp")
|
||||
.build()
|
||||
|
||||
XCTAssertEqual(token?.generator.factor, TIMER)
|
||||
}
|
||||
|
||||
func testTypeHotp() {
|
||||
let token = TokenBuilder()
|
||||
.usingSecret(SECRET)
|
||||
.usingType("HotP")
|
||||
.usingCounter("4")
|
||||
.build()
|
||||
|
||||
XCTAssertEqual(token?.generator.factor, Generator.Factor.counter(4))
|
||||
}
|
||||
|
||||
func testAlgorithm() {
|
||||
[
|
||||
("sha1", .sha1),
|
||||
("something", .sha1),
|
||||
(nil, .sha1),
|
||||
("sha256", .sha256),
|
||||
("Sha256", .sha256),
|
||||
("sha512", .sha512),
|
||||
("sHA512", .sha512),
|
||||
].forEach { (inputAlgorithm: String?, algorithm: Generator.Algorithm) in
|
||||
let token = TokenBuilder()
|
||||
.usingSecret(SECRET)
|
||||
.usingAlgorithm(inputAlgorithm)
|
||||
.build()
|
||||
|
||||
XCTAssertEqual(token?.generator.algorithm, algorithm)
|
||||
}
|
||||
}
|
||||
|
||||
func testDigits() {
|
||||
[
|
||||
(nil, nil),
|
||||
(5, nil),
|
||||
(6, 6),
|
||||
(7, 7),
|
||||
(8, 8),
|
||||
(9, nil),
|
||||
].forEach { inputDigits, digits in
|
||||
let token = TokenBuilder()
|
||||
.usingSecret(SECRET)
|
||||
.usingDigits(inputDigits == nil ? nil : String(inputDigits!))
|
||||
.build()
|
||||
|
||||
XCTAssertEqual(token?.generator.digits, digits)
|
||||
}
|
||||
}
|
||||
|
||||
func testUnparsableDigits() {
|
||||
let token = TokenBuilder()
|
||||
.usingSecret(SECRET)
|
||||
.usingDigits("unparsable digits")
|
||||
.build()
|
||||
|
||||
XCTAssertNil(token)
|
||||
}
|
||||
|
||||
func testPeriod() {
|
||||
[
|
||||
(nil, nil),
|
||||
(1.2, 1.2),
|
||||
(-12.0, nil),
|
||||
(27.5, 27.5),
|
||||
(35.0, 35.0),
|
||||
(120.7, 120.7),
|
||||
].forEach { inputPeriod, period in
|
||||
let token = TokenBuilder()
|
||||
.usingSecret(SECRET)
|
||||
.usingPeriod(inputPeriod == nil ? nil : String(inputPeriod!))
|
||||
.build()
|
||||
let timer = period == nil ? nil : Generator.Factor.timer(period: period!)
|
||||
|
||||
XCTAssertEqual(token?.generator.factor, timer)
|
||||
}
|
||||
}
|
||||
|
||||
func testUnparsablePeriod() {
|
||||
let token = TokenBuilder()
|
||||
.usingSecret(SECRET)
|
||||
.usingPeriod("unparsable period")
|
||||
.build()
|
||||
|
||||
XCTAssertNil(token)
|
||||
}
|
||||
|
||||
func testCounter() {
|
||||
[
|
||||
(nil, nil),
|
||||
(1, 1),
|
||||
(0, 0),
|
||||
(27, 27),
|
||||
(120, 120),
|
||||
(4321, 4321),
|
||||
].forEach { inputCounter, counter in
|
||||
let token = TokenBuilder()
|
||||
.usingSecret(SECRET)
|
||||
.usingType("hotp")
|
||||
.usingCounter(inputCounter == nil ? nil : String(inputCounter!))
|
||||
.build()
|
||||
let counter = counter == nil ? nil : Generator.Factor.counter(UInt64(counter!))
|
||||
|
||||
XCTAssertEqual(token?.generator.factor, counter)
|
||||
}
|
||||
}
|
||||
|
||||
func testUnparsableCounter() {
|
||||
let token = TokenBuilder()
|
||||
.usingSecret(SECRET)
|
||||
.usingType("hotp")
|
||||
.usingCounter("unparsable counter")
|
||||
.build()
|
||||
|
||||
XCTAssertNil(token)
|
||||
}
|
||||
|
||||
func testAllMixed() {
|
||||
let builder = TokenBuilder()
|
||||
.usingName("name")
|
||||
.usingSecret(SECRET)
|
||||
.usingAlgorithm("sha512")
|
||||
.usingDigits("7")
|
||||
.usingPeriod("42")
|
||||
.usingCounter("12")
|
||||
|
||||
let totpToken = builder.usingType("totp").build()
|
||||
|
||||
XCTAssertNotNil(totpToken)
|
||||
XCTAssertEqual(totpToken?.name, "name")
|
||||
XCTAssertEqual(totpToken?.currentPassword?.count, 7)
|
||||
XCTAssertEqual(totpToken?.generator.algorithm, .sha512)
|
||||
XCTAssertEqual(totpToken?.generator.digits, 7)
|
||||
XCTAssertEqual(totpToken?.generator.factor, .timer(period: 42))
|
||||
XCTAssertEqual(totpToken?.generator.secret, MF_Base32Codec.data(fromBase32String: SECRET))
|
||||
|
||||
let hotpToken = builder.usingType("hotp").build()
|
||||
|
||||
XCTAssertNotNil(hotpToken)
|
||||
XCTAssertEqual(hotpToken?.name, "name")
|
||||
XCTAssertEqual(hotpToken?.currentPassword?.count, 7)
|
||||
XCTAssertEqual(hotpToken?.generator.algorithm, .sha512)
|
||||
XCTAssertEqual(hotpToken?.generator.digits, 7)
|
||||
XCTAssertEqual(hotpToken?.generator.factor, .counter(12))
|
||||
XCTAssertEqual(hotpToken?.generator.secret, MF_Base32Codec.data(fromBase32String: SECRET))
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue