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
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 {}
|
||||
Loading…
Add table
Add a link
Reference in a new issue