Add logic for more customizable password generator
This commit is contained in:
parent
49a371d495
commit
ff014a5699
10 changed files with 352 additions and 131 deletions
|
|
@ -22,6 +22,7 @@
|
|||
3032328E22CBD4CD009EBD9C /* CryptographicKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3032328D22CBD4CD009EBD9C /* CryptographicKeys.swift */; };
|
||||
30650E7123F82AF8005CCD5E /* SSHKeyFileImportTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30650E7023F82AF8005CCD5E /* SSHKeyFileImportTableViewController.swift */; };
|
||||
30650E7323F847FC005CCD5E /* KeyImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30650E7223F847FC005CCD5E /* KeyImporter.swift */; };
|
||||
306623332406F1A8000E2AD6 /* PasswordGeneratorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 306623322406F1A7000E2AD6 /* PasswordGeneratorTest.swift */; };
|
||||
3066AD6823EE0D6500F65535 /* PGPKeyImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3066AD6723EE0D6500F65535 /* PGPKeyImporter.swift */; };
|
||||
30697C2A21F63C5A0064FCAC /* NotificationNames.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30697C2321F63C580064FCAC /* NotificationNames.swift */; };
|
||||
30697C2B21F63C5A0064FCAC /* Globals.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30697C2421F63C590064FCAC /* Globals.swift */; };
|
||||
|
|
@ -58,6 +59,7 @@
|
|||
30A1D2AC21B32C2A00E2D1F7 /* TokenBuilderTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A1D2AB21B32C2A00E2D1F7 /* TokenBuilderTest.swift */; };
|
||||
30A86F95230F237000F821A4 /* CryptoFrameworkTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A86F94230F237000F821A4 /* CryptoFrameworkTest.swift */; };
|
||||
30B04860209A5141001013CA /* PasswordTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30B0485F209A5141001013CA /* PasswordTest.swift */; };
|
||||
30B4C7BA24084AAA008B86F7 /* PasswordGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30B4C7B924084AAA008B86F7 /* PasswordGenerator.swift */; };
|
||||
30BAC8C622E3BAAF00438475 /* TestBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30BAC8C422E3BAAF00438475 /* TestBase.swift */; };
|
||||
30BAC8C722E3BAAF00438475 /* TestPGPKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30BAC8C522E3BAAF00438475 /* TestPGPKeys.swift */; };
|
||||
30BAC8CB22E3BB6C00438475 /* DictBasedKeychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30BAC8CA22E3BB6C00438475 /* DictBasedKeychain.swift */; };
|
||||
|
|
@ -71,6 +73,8 @@
|
|||
30CCA91623258C380048CA51 /* PgpInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30CCA91523258C380048CA51 /* PgpInterface.swift */; };
|
||||
30CCA91823258E760048CA51 /* GopenPgp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30CCA91723258E760048CA51 /* GopenPgp.swift */; };
|
||||
30CCA91A232591320048CA51 /* ObjectivePgp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30CCA919232591320048CA51 /* ObjectivePgp.swift */; };
|
||||
30DAFD4A240985A7002456E7 /* Array+Slices.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30DAFD49240985A7002456E7 /* Array+Slices.swift */; };
|
||||
30DAFD4C240985E3002456E7 /* Array+SlicesTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30DAFD4B240985E3002456E7 /* Array+SlicesTest.swift */; };
|
||||
30FD2F78214D9E0E005E0A92 /* ParserTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30FD2F77214D9E0E005E0A92 /* ParserTest.swift */; };
|
||||
3EA2386CD0E9CE2A702A0B3E /* Pods_pass.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FE627E8F3DACEDD8FA220081 /* Pods_pass.framework */; };
|
||||
556EC3D322335C5F00934F9C /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 30BF5ECA21EA8FB5000E4154 /* Localizable.strings */; };
|
||||
|
|
@ -247,6 +251,7 @@
|
|||
3032328D22CBD4CD009EBD9C /* CryptographicKeys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CryptographicKeys.swift; sourceTree = "<group>"; };
|
||||
30650E7023F82AF8005CCD5E /* SSHKeyFileImportTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSHKeyFileImportTableViewController.swift; sourceTree = "<group>"; };
|
||||
30650E7223F847FC005CCD5E /* KeyImporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyImporter.swift; sourceTree = "<group>"; };
|
||||
306623322406F1A7000E2AD6 /* PasswordGeneratorTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordGeneratorTest.swift; sourceTree = "<group>"; };
|
||||
3066AD6723EE0D6500F65535 /* PGPKeyImporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PGPKeyImporter.swift; sourceTree = "<group>"; };
|
||||
30697C2321F63C580064FCAC /* NotificationNames.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationNames.swift; sourceTree = "<group>"; };
|
||||
30697C2421F63C590064FCAC /* Globals.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Globals.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -282,6 +287,7 @@
|
|||
30A1D2AB21B32C2A00E2D1F7 /* TokenBuilderTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenBuilderTest.swift; sourceTree = "<group>"; };
|
||||
30A86F94230F237000F821A4 /* CryptoFrameworkTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CryptoFrameworkTest.swift; sourceTree = "<group>"; };
|
||||
30B0485F209A5141001013CA /* PasswordTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordTest.swift; sourceTree = "<group>"; };
|
||||
30B4C7B924084AAA008B86F7 /* PasswordGenerator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasswordGenerator.swift; sourceTree = "<group>"; };
|
||||
30BAC8C422E3BAAF00438475 /* TestBase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestBase.swift; sourceTree = "<group>"; };
|
||||
30BAC8C522E3BAAF00438475 /* TestPGPKeys.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestPGPKeys.swift; sourceTree = "<group>"; };
|
||||
30BAC8CA22E3BB6C00438475 /* DictBasedKeychain.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DictBasedKeychain.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -300,6 +306,8 @@
|
|||
30CCA91523258C380048CA51 /* PgpInterface.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PgpInterface.swift; sourceTree = "<group>"; };
|
||||
30CCA91723258E760048CA51 /* GopenPgp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GopenPgp.swift; sourceTree = "<group>"; };
|
||||
30CCA919232591320048CA51 /* ObjectivePgp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectivePgp.swift; sourceTree = "<group>"; };
|
||||
30DAFD49240985A7002456E7 /* Array+Slices.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+Slices.swift"; sourceTree = "<group>"; };
|
||||
30DAFD4B240985E3002456E7 /* Array+SlicesTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+SlicesTest.swift"; sourceTree = "<group>"; };
|
||||
30FD2F77214D9E0E005E0A92 /* ParserTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParserTest.swift; sourceTree = "<group>"; };
|
||||
3B2B2F844061EFA534FE9506 /* Pods_passKitTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_passKitTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
62DEE9943E0F2B8C79E3FC5B /* Pods-passExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-passExtension.release.xcconfig"; path = "Pods/Target Support Files/Pods-passExtension/Pods-passExtension.release.xcconfig"; sourceTree = "<group>"; };
|
||||
|
|
@ -460,7 +468,6 @@
|
|||
301F6464216164670071A4CE /* Helpers */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
30A1D29B21AF451E00E2D1F7 /* PasswordGeneratorFlavorTest.swift */,
|
||||
3032328922C9FBA2009EBD9C /* KeyFileManagerTest.swift */,
|
||||
);
|
||||
path = Helpers;
|
||||
|
|
@ -481,10 +488,20 @@
|
|||
name = Pods;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
30662336240835D0000E2AD6 /* Passwords */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
30B4C7B924084AAA008B86F7 /* PasswordGenerator.swift */,
|
||||
30697C2621F63C590064FCAC /* PasswordGeneratorFlavor.swift */,
|
||||
);
|
||||
path = Passwords;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
30697C5521F63F870064FCAC /* Extensions */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
30697C5E21F674800064FCAC /* String+UtilitiesTest.swift */,
|
||||
30DAFD4B240985E3002456E7 /* Array+SlicesTest.swift */,
|
||||
);
|
||||
path = Extensions;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -498,9 +515,19 @@
|
|||
path = Crypto;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
30B4C7BB24085A3C008B86F7 /* Passwords */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
30A1D29B21AF451E00E2D1F7 /* PasswordGeneratorFlavorTest.swift */,
|
||||
306623322406F1A7000E2AD6 /* PasswordGeneratorTest.swift */,
|
||||
);
|
||||
path = Passwords;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
30B6AABA21F49095006B352D /* Extensions */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
30DAFD49240985A7002456E7 /* Array+Slices.swift */,
|
||||
30CCA90A2325119C0048CA51 /* Data+Mutable.swift */,
|
||||
30697C3621F63C990064FCAC /* String+Localization.swift */,
|
||||
30697C3921F63C990064FCAC /* String+Utilities.swift */,
|
||||
|
|
@ -612,6 +639,7 @@
|
|||
A2F4E20F1EED7F0A0011986E /* Helpers */,
|
||||
A2F4E20E1EED7F040011986E /* Models */,
|
||||
30C015A3214ECF2B005BB6DF /* Parser */,
|
||||
30662336240835D0000E2AD6 /* Passwords */,
|
||||
A23DD0DB233FB46900E6CD83 /* Assets.xcassets */,
|
||||
A260757A1EEC6F34005DB03E /* passKit.h */,
|
||||
A26075A51EEC7125005DB03E /* pass.xcdatamodeld */,
|
||||
|
|
@ -629,6 +657,7 @@
|
|||
301F6464216164670071A4CE /* Helpers */,
|
||||
30C015A7214ED378005BB6DF /* Models */,
|
||||
30C015A6214ED32A005BB6DF /* Parser */,
|
||||
30B4C7BB24085A3C008B86F7 /* Passwords */,
|
||||
A26075871EEC6F34005DB03E /* passKitTests.swift */,
|
||||
A26075891EEC6F34005DB03E /* Info.plist */,
|
||||
);
|
||||
|
|
@ -684,7 +713,6 @@
|
|||
3032327322C7F710009EBD9C /* KeyFileManager.swift */,
|
||||
30BAC8CC22E3BB9700438475 /* KeyStore.swift */,
|
||||
30697C2321F63C580064FCAC /* NotificationNames.swift */,
|
||||
30697C2621F63C590064FCAC /* PasswordGeneratorFlavor.swift */,
|
||||
302202EE222F14E400555236 /* SearchBarScope.swift */,
|
||||
30697C2721F63C590064FCAC /* Utils.swift */,
|
||||
);
|
||||
|
|
@ -1324,10 +1352,12 @@
|
|||
302E85612125ECC70031BA64 /* Parser.swift in Sources */,
|
||||
30CCA91A232591320048CA51 /* ObjectivePgp.swift in Sources */,
|
||||
30697C4621F63CAB0064FCAC /* GitCredential.swift in Sources */,
|
||||
30B4C7BA24084AAA008B86F7 /* PasswordGenerator.swift in Sources */,
|
||||
30A1D2A621B2D46100E2D1F7 /* OtpType.swift in Sources */,
|
||||
3032328E22CBD4CD009EBD9C /* CryptographicKeys.swift in Sources */,
|
||||
30697C2A21F63C5A0064FCAC /* NotificationNames.swift in Sources */,
|
||||
30CCA91623258C380048CA51 /* PgpInterface.swift in Sources */,
|
||||
30DAFD4A240985A7002456E7 /* Array+Slices.swift in Sources */,
|
||||
30697C4721F63CAB0064FCAC /* PasscodeLock.swift in Sources */,
|
||||
A2699ACD2402631400F36323 /* PasswordTableEntry.swift in Sources */,
|
||||
30697C3421F63C8B0064FCAC /* PasscodeLockViewController.swift in Sources */,
|
||||
|
|
@ -1350,6 +1380,7 @@
|
|||
files = (
|
||||
30A86F95230F237000F821A4 /* CryptoFrameworkTest.swift in Sources */,
|
||||
30A1D2AC21B32C2A00E2D1F7 /* TokenBuilderTest.swift in Sources */,
|
||||
30DAFD4C240985E3002456E7 /* Array+SlicesTest.swift in Sources */,
|
||||
301F646D216166AA0071A4CE /* AdditionFieldTest.swift in Sources */,
|
||||
30BAC8CB22E3BB6C00438475 /* DictBasedKeychain.swift in Sources */,
|
||||
A2699ACF24027D9500F36323 /* PasswordTableEntryTest.swift in Sources */,
|
||||
|
|
@ -1359,6 +1390,7 @@
|
|||
30B04860209A5141001013CA /* PasswordTest.swift in Sources */,
|
||||
30697C5F21F674800064FCAC /* String+UtilitiesTest.swift in Sources */,
|
||||
3032328A22C9FBA2009EBD9C /* KeyFileManagerTest.swift in Sources */,
|
||||
306623332406F1A8000E2AD6 /* PasswordGeneratorTest.swift in Sources */,
|
||||
30BAC8C722E3BAAF00438475 /* TestPGPKeys.swift in Sources */,
|
||||
30A1D2AA21B32A0100E2D1F7 /* OtpTypeTest.swift in Sources */,
|
||||
301F6468216165290071A4CE /* ConstantsTest.swift in Sources */,
|
||||
|
|
|
|||
28
passKit/Extensions/Array+Slices.swift
Normal file
28
passKit/Extensions/Array+Slices.swift
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
//
|
||||
// Array+Slices.swift
|
||||
// passKit
|
||||
//
|
||||
// Created by Danny Moesch on 28.02.20.
|
||||
// Copyright © 2020 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
extension Array {
|
||||
|
||||
func slices(count: UInt) -> [ArraySlice<Element>] {
|
||||
guard count != 0 else {
|
||||
return []
|
||||
}
|
||||
let sizeEach = Int(self.count / Int(count))
|
||||
var currentIndex = startIndex
|
||||
var slices = [ArraySlice<Element>]()
|
||||
for _ in 0 ..< count {
|
||||
let toIndex = index(currentIndex, offsetBy: sizeEach, limitedBy: endIndex) ?? endIndex
|
||||
slices.append(self[currentIndex ..< toIndex])
|
||||
currentIndex = toIndex
|
||||
}
|
||||
if currentIndex != endIndex {
|
||||
slices[slices.endIndex - 1].append(contentsOf: self[currentIndex ..< endIndex])
|
||||
}
|
||||
return slices
|
||||
}
|
||||
}
|
||||
|
|
@ -20,7 +20,7 @@ public enum GitAuthenticationMethod: String, DefaultsSerializable {
|
|||
}
|
||||
|
||||
extension SearchBarScope: DefaultsSerializable {}
|
||||
extension PasswordGeneratorFlavor: DefaultsSerializable {}
|
||||
extension PasswordGenerator: DefaultsSerializable {}
|
||||
|
||||
public extension DefaultsKeys {
|
||||
var pgpKeySource: DefaultsKey<KeySource?> { .init("pgpKeySource") }
|
||||
|
|
@ -53,7 +53,7 @@ public extension DefaultsKeys {
|
|||
var isShowFolderOn: DefaultsKey<Bool> { .init("isShowFolderOn", defaultValue: true) }
|
||||
var isHidePasswordImagesOn: DefaultsKey<Bool> { .init("isHidePasswordImagesOn", defaultValue: false) }
|
||||
var searchDefault: DefaultsKey<SearchBarScope?> { .init("searchDefault", defaultValue: .all) }
|
||||
var passwordGeneratorFlavor: DefaultsKey<PasswordGeneratorFlavor> { .init("passwordGeneratorFlavor", defaultValue: .apple) }
|
||||
var passwordGenerator: DefaultsKey<PasswordGenerator> { .init("passwordGenerator", defaultValue: PasswordGenerator()) }
|
||||
|
||||
var encryptInArmored: DefaultsKey<Bool> { .init("encryptInArmored", defaultValue: false) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,86 +0,0 @@
|
|||
//
|
||||
// PasswordGeneratorFlavor.swift
|
||||
// passKit
|
||||
//
|
||||
// Created by Danny Moesch on 28.11.18.
|
||||
// Copyright © 2018 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
import KeychainAccess
|
||||
|
||||
public enum PasswordGeneratorFlavor: String {
|
||||
case apple = "Apple"
|
||||
case random = "Random"
|
||||
case xkcd = "XKCD"
|
||||
|
||||
private static let words: [String] = {
|
||||
let bundle = Bundle(identifier: Globals.passKitBundleIdentifier)!
|
||||
return ["eff_long_wordlist", "eff_short_wordlist"]
|
||||
.map { name -> String in
|
||||
guard let asset = NSDataAsset(name: name, bundle: bundle),
|
||||
let data = String(data: asset.data, encoding: .utf8) else {
|
||||
return ""
|
||||
}
|
||||
return data
|
||||
}
|
||||
.joined(separator: "\n")
|
||||
.splitByNewline()
|
||||
}()
|
||||
|
||||
public var localized: String {
|
||||
return rawValue.localize()
|
||||
}
|
||||
|
||||
public var longNameLocalized: String {
|
||||
switch self {
|
||||
case .apple:
|
||||
return "ApplesKeychainStyle".localize()
|
||||
case .random:
|
||||
return "RandomString".localize()
|
||||
case .xkcd:
|
||||
return "XKCDStyle".localize()
|
||||
}
|
||||
}
|
||||
|
||||
public var defaultLength: (min: Int, max: Int, def: Int) {
|
||||
switch self {
|
||||
case .apple:
|
||||
return (15, 15, 15)
|
||||
case .random:
|
||||
return (4, 64, 16)
|
||||
case .xkcd:
|
||||
return (2, 5, 3)
|
||||
}
|
||||
}
|
||||
|
||||
public func generate(length: Int) -> String {
|
||||
switch self {
|
||||
case .apple:
|
||||
return Keychain.generatePassword()
|
||||
case .random:
|
||||
return Self.generateRandom(length: length)
|
||||
case .xkcd:
|
||||
return Self.generateXKCD(length: length)
|
||||
}
|
||||
}
|
||||
|
||||
private static func generateRandom(length: Int) -> String {
|
||||
let chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*_+-="
|
||||
return String((0..<length).map { _ in chars.randomElement()! })
|
||||
}
|
||||
|
||||
private static func generateXKCD(length: Int) -> String {
|
||||
let delimiters = "0123456789!@#$%^&*_+-="
|
||||
var password = ""
|
||||
(0..<length).forEach { _ in
|
||||
var word = words.randomElement()!
|
||||
if Bool.random() {
|
||||
word = word.uppercased()
|
||||
}
|
||||
password += word + String(delimiters.randomElement()!)
|
||||
}
|
||||
return password
|
||||
}
|
||||
}
|
||||
|
||||
extension PasswordGeneratorFlavor: CaseIterable {}
|
||||
121
passKit/Passwords/PasswordGenerator.swift
Normal file
121
passKit/Passwords/PasswordGenerator.swift
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
//
|
||||
// PasswordGenerator.swift
|
||||
// passKit
|
||||
//
|
||||
// Created by Danny Moesch on 27.02.20.
|
||||
// Copyright © 2020 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
public struct PasswordGenerator: Codable {
|
||||
|
||||
private static let digits = "0123456789"
|
||||
private static let letters = "abcdefghijklmnopqrstuvwxyz"
|
||||
private static let capitalLetters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
private static let specialSymbols = "!\"#$%&'()*+,./:;<=>?@[\\]^_`{|}~"
|
||||
|
||||
private static let words: [String] = {
|
||||
let bundle = Bundle(identifier: Globals.passKitBundleIdentifier)!
|
||||
return ["eff_long_wordlist", "eff_short_wordlist"]
|
||||
.map { name -> String in
|
||||
guard let asset = NSDataAsset(name: name, bundle: bundle),
|
||||
let data = String(data: asset.data, encoding: .utf8) else {
|
||||
return ""
|
||||
}
|
||||
return data
|
||||
}
|
||||
.joined(separator: "\n")
|
||||
.splitByNewline()
|
||||
}()
|
||||
|
||||
public var flavor = PasswordGeneratorFlavor.random
|
||||
public var length = 15
|
||||
public var varyCases = true
|
||||
public var useDigits = true
|
||||
public var useSpecialSymbols = true
|
||||
public var groups = 4
|
||||
|
||||
public var limitedLength: Int {
|
||||
let lengthLimits = flavor.lengthLimits
|
||||
return max(lengthLimits.min, min(lengthLimits.max, length))
|
||||
}
|
||||
|
||||
private var characters: String {
|
||||
var characters = Self.letters
|
||||
if varyCases {
|
||||
characters.append(Self.capitalLetters)
|
||||
}
|
||||
if useDigits {
|
||||
characters.append(Self.digits)
|
||||
}
|
||||
if useSpecialSymbols {
|
||||
characters.append(Self.specialSymbols)
|
||||
}
|
||||
return characters
|
||||
}
|
||||
|
||||
private var delimiters: String {
|
||||
var delimiters = ""
|
||||
if useDigits {
|
||||
delimiters.append(Self.digits)
|
||||
}
|
||||
if useSpecialSymbols {
|
||||
delimiters.append(Self.specialSymbols)
|
||||
}
|
||||
return delimiters
|
||||
}
|
||||
|
||||
public func generate() -> String {
|
||||
switch flavor {
|
||||
case .random:
|
||||
return generateRandom()
|
||||
case .xkcd:
|
||||
return generateXkcd()
|
||||
}
|
||||
}
|
||||
|
||||
public func isAcceptable(groups: Int) -> Bool {
|
||||
guard flavor == .random, groups > 0, groups < length else {
|
||||
return false
|
||||
}
|
||||
return (length + 1) % groups == 0
|
||||
}
|
||||
|
||||
private func generateRandom() -> String {
|
||||
let currentCharacters = characters
|
||||
if groups > 1, isAcceptable(groups: groups) {
|
||||
return selectRandomly(count: length - groups + 1, from: currentCharacters)
|
||||
.slices(count: UInt(groups))
|
||||
.map { String($0) }
|
||||
.joined(separator: "-")
|
||||
}
|
||||
return String(selectRandomly(count: length, from: currentCharacters))
|
||||
}
|
||||
|
||||
private func generateXkcd() -> String {
|
||||
let currentDelimiters = delimiters
|
||||
return getRandomDelimiter(from: currentDelimiters) + (0 ..< length)
|
||||
.map { _ in getRandomWord() + getRandomDelimiter(from: currentDelimiters) }
|
||||
.joined()
|
||||
}
|
||||
|
||||
private func getRandomDelimiter(from delimiters: String) -> String {
|
||||
if delimiters.isEmpty {
|
||||
return ""
|
||||
}
|
||||
return String(delimiters.randomElement()!)
|
||||
}
|
||||
|
||||
private func getRandomWord() -> String {
|
||||
let word = Self.words.randomElement()!
|
||||
if varyCases, Bool.random() {
|
||||
return word.uppercased()
|
||||
}
|
||||
return word
|
||||
}
|
||||
|
||||
private func selectRandomly(count: Int, from string: String) -> [Character] {
|
||||
return (0 ..< count).map { _ in string.randomElement()! }
|
||||
}
|
||||
}
|
||||
|
||||
extension PasswordGeneratorFlavor: Codable {}
|
||||
38
passKit/Passwords/PasswordGeneratorFlavor.swift
Normal file
38
passKit/Passwords/PasswordGeneratorFlavor.swift
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
//
|
||||
// PasswordGeneratorFlavor.swift
|
||||
// passKit
|
||||
//
|
||||
// Created by Danny Moesch on 28.11.18.
|
||||
// Copyright © 2018 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
public typealias LengthLimits = (min: Int, max: Int)
|
||||
|
||||
public enum PasswordGeneratorFlavor: String {
|
||||
case random = "Random"
|
||||
case xkcd = "XKCD"
|
||||
|
||||
public var localized: String {
|
||||
return rawValue.localize()
|
||||
}
|
||||
|
||||
public var longNameLocalized: String {
|
||||
switch self {
|
||||
case .random:
|
||||
return "RandomString".localize()
|
||||
case .xkcd:
|
||||
return "XKCDStyle".localize()
|
||||
}
|
||||
}
|
||||
|
||||
public var lengthLimits: LengthLimits {
|
||||
switch self {
|
||||
case .random:
|
||||
return (4, 64)
|
||||
case .xkcd:
|
||||
return (2, 5)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension PasswordGeneratorFlavor: CaseIterable {}
|
||||
29
passKitTests/Extensions/Array+SlicesTest.swift
Normal file
29
passKitTests/Extensions/Array+SlicesTest.swift
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
//
|
||||
// Array+SlicesTest.swift
|
||||
// passKitTests
|
||||
//
|
||||
// Created by Danny Moesch on 28.02.20.
|
||||
// Copyright © 2020 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
@testable import passKit
|
||||
|
||||
class ArraySlicesTest: XCTestCase {
|
||||
|
||||
func testZeroCount() {
|
||||
XCTAssertEqual([1, 2, 3].slices(count: 0), [])
|
||||
}
|
||||
|
||||
func testEmptyArray() {
|
||||
XCTAssertEqual(([] as [String]).slices(count: 4), [[], [], [], []])
|
||||
}
|
||||
|
||||
func testSlices() {
|
||||
XCTAssertEqual([1, 2, 3].slices(count: 3), [[1], [2], [3]])
|
||||
XCTAssertEqual([1, 2, 3, 4].slices(count: 3), [[1], [2], [3, 4]])
|
||||
XCTAssertEqual([1, 2, 3, 4].slices(count: 2), [[1, 2], [3, 4]])
|
||||
XCTAssertEqual([1, 2, 3, 4, 5].slices(count: 2), [[1, 2], [3, 4, 5]])
|
||||
}
|
||||
}
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
//
|
||||
// PasswordGeneratorFlavorTest.swift
|
||||
// passKitTests
|
||||
//
|
||||
// Created by Danny Moesch on 28.11.18.
|
||||
// Copyright © 2018 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
import KeychainAccess
|
||||
import XCTest
|
||||
|
||||
@testable import passKit
|
||||
|
||||
class PasswordGeneratorFlavorTest: XCTestCase {
|
||||
|
||||
private let KEYCHAIN_PASSWORD_LENGTH = Keychain.generatePassword().count
|
||||
|
||||
func testLocalizedName() {
|
||||
XCTAssertEqual(PasswordGeneratorFlavor.apple.localized, "Apple".localize())
|
||||
XCTAssertEqual(PasswordGeneratorFlavor.random.localized, "Random".localize())
|
||||
}
|
||||
|
||||
func testDefaultLength() {
|
||||
// Ensure properly chosen default length values. So this check no longer needs to be performed in the code.
|
||||
PasswordGeneratorFlavor.allCases.map { $0.defaultLength }.forEach { defaultLength in
|
||||
XCTAssertLessThanOrEqual(defaultLength.min, defaultLength.max)
|
||||
XCTAssertLessThanOrEqual(defaultLength.def, defaultLength.max)
|
||||
XCTAssertGreaterThanOrEqual(defaultLength.def, defaultLength.min)
|
||||
}
|
||||
}
|
||||
|
||||
func testGeneratePassword() {
|
||||
let apple = PasswordGeneratorFlavor.apple
|
||||
let random = PasswordGeneratorFlavor.random
|
||||
|
||||
XCTAssertEqual(apple.generate(length: 4).count, KEYCHAIN_PASSWORD_LENGTH)
|
||||
XCTAssertEqual(random.generate(length: 0).count, 0)
|
||||
XCTAssertEqual(random.generate(length: 4).count, 4)
|
||||
XCTAssertEqual(random.generate(length: 100).count, 100)
|
||||
}
|
||||
}
|
||||
21
passKitTests/Passwords/PasswordGeneratorFlavorTest.swift
Normal file
21
passKitTests/Passwords/PasswordGeneratorFlavorTest.swift
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
//
|
||||
// PasswordGeneratorFlavorTest.swift
|
||||
// passKitTests
|
||||
//
|
||||
// Created by Danny Moesch on 28.11.18.
|
||||
// Copyright © 2018 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
@testable import passKit
|
||||
|
||||
class PasswordGeneratorFlavorTest: XCTestCase {
|
||||
|
||||
func testLengthLimits() {
|
||||
// Ensure properly chosen length limits. So this check no longer needs to be performed in the code.
|
||||
PasswordGeneratorFlavor.allCases.map { $0.lengthLimits }.forEach {
|
||||
XCTAssertLessThanOrEqual($0.min, $0.max)
|
||||
}
|
||||
}
|
||||
}
|
||||
79
passKitTests/Passwords/PasswordGeneratorTest.swift
Normal file
79
passKitTests/Passwords/PasswordGeneratorTest.swift
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
//
|
||||
// PasswordGeneratorTest.swift
|
||||
// passKitTests
|
||||
//
|
||||
// Created by Danny Moesch on 26.02.20.
|
||||
// Copyright © 2020 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
@testable import passKit
|
||||
|
||||
class PasswordGeneratorTest: XCTestCase {
|
||||
|
||||
func testLimitedLength() {
|
||||
[
|
||||
PasswordGenerator(length: 15),
|
||||
PasswordGenerator(length: -3),
|
||||
PasswordGenerator(length: 128),
|
||||
].forEach { generator in
|
||||
XCTAssertLessThanOrEqual(generator.limitedLength, generator.flavor.lengthLimits.max)
|
||||
XCTAssertGreaterThanOrEqual(generator.limitedLength, generator.flavor.lengthLimits.min)
|
||||
}
|
||||
}
|
||||
|
||||
func testAcceptableGroups() {
|
||||
[
|
||||
(15, 4),
|
||||
(19, 4),
|
||||
(9, 5),
|
||||
(11, 6),
|
||||
(259, 13),
|
||||
].forEach { length, groups in
|
||||
XCTAssertTrue(PasswordGenerator(length: length).isAcceptable(groups: groups))
|
||||
}
|
||||
}
|
||||
|
||||
func testNotAcceptableGroups() {
|
||||
[
|
||||
(15, 0),
|
||||
(19, 20),
|
||||
(9, 9),
|
||||
(11, -1),
|
||||
].forEach { length, groups in
|
||||
XCTAssertFalse(PasswordGenerator(length: length).isAcceptable(groups: groups))
|
||||
}
|
||||
}
|
||||
|
||||
func testGroupsAreNotcceptableForXKCDStyle() {
|
||||
var generator = PasswordGenerator(length: 15)
|
||||
|
||||
XCTAssertTrue(generator.isAcceptable(groups: 4))
|
||||
|
||||
generator.flavor = .xkcd
|
||||
XCTAssertFalse(generator.isAcceptable(groups: 4))
|
||||
}
|
||||
|
||||
func testRandomPasswordLength() {
|
||||
[
|
||||
PasswordGenerator(),
|
||||
PasswordGenerator(groups: 1),
|
||||
PasswordGenerator(length: 25),
|
||||
PasswordGenerator(length: 47, groups: 12),
|
||||
PasswordGenerator(useDigits: true),
|
||||
].forEach { generator in
|
||||
XCTAssertEqual(generator.generate().count, generator.length)
|
||||
}
|
||||
}
|
||||
|
||||
func testXKCDPasswordGeneration() {
|
||||
let typicalPassword = PasswordGenerator(flavor: .xkcd).generate()
|
||||
XCTAssertFalse(typicalPassword.isEmpty)
|
||||
XCTAssertFalse(typicalPassword.trimmingCharacters(in: .letters).isEmpty)
|
||||
|
||||
let passwordWithoutSeparators = PasswordGenerator(flavor: .xkcd, useDigits: false, useSpecialSymbols: false).generate()
|
||||
XCTAssertFalse(passwordWithoutSeparators.isEmpty)
|
||||
XCTAssertTrue(passwordWithoutSeparators.trimmingCharacters(in: .letters).isEmpty)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue