Move codes to an embed framework
- Move bundle/group identifiers to passKit/Global - Fix Core Data - Change Defaults to SharedDefaults
This commit is contained in:
parent
850dc75820
commit
d2ba620ae4
45 changed files with 1062 additions and 523 deletions
40
passKit/Helpers/AppError.swift
Normal file
40
passKit/Helpers/AppError.swift
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
//
|
||||
// AppError.swift
|
||||
// pass
|
||||
//
|
||||
// Created by Mingshen Sun on 30/4/2017.
|
||||
// Copyright © 2017 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum AppError: Error {
|
||||
case RepositoryNotSetError
|
||||
case RepositoryRemoteMasterNotFoundError
|
||||
case KeyImportError
|
||||
case PasswordDuplicatedError
|
||||
case GitResetError
|
||||
case PGPPublicKeyNotExistError
|
||||
case UnknownError
|
||||
}
|
||||
|
||||
extension AppError: LocalizedError {
|
||||
public var errorDescription: String? {
|
||||
switch self {
|
||||
case .RepositoryNotSetError:
|
||||
return "Git repository is not set."
|
||||
case .RepositoryRemoteMasterNotFoundError:
|
||||
return "Cannot find remote branch origin/master."
|
||||
case .KeyImportError:
|
||||
return "Cannot import the key."
|
||||
case .PasswordDuplicatedError:
|
||||
return "Cannot add the password: password duplicated."
|
||||
case .GitResetError:
|
||||
return "Cannot identify the latest synced commit."
|
||||
case .PGPPublicKeyNotExistError:
|
||||
return "PGP public key doesn't exist."
|
||||
case .UnknownError:
|
||||
return "Unknown error."
|
||||
}
|
||||
}
|
||||
}
|
||||
43
passKit/Helpers/DefaultsKeys.swift
Normal file
43
passKit/Helpers/DefaultsKeys.swift
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
//
|
||||
// DefaultKeys.swift
|
||||
// pass
|
||||
//
|
||||
// Created by Mingshen Sun on 21/1/2017.
|
||||
// Copyright © 2017 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftyUserDefaults
|
||||
|
||||
public var SharedDefaults = UserDefaults(suiteName: Globals.groupIdentifier)!
|
||||
|
||||
public extension DefaultsKeys {
|
||||
static let pgpKeySource = DefaultsKey<String?>("pgpKeySource")
|
||||
static let pgpPublicKeyURL = DefaultsKey<URL?>("pgpPublicKeyURL")
|
||||
static let pgpPrivateKeyURL = DefaultsKey<URL?>("pgpPrivateKeyURL")
|
||||
|
||||
static let pgpPublicKeyArmor = DefaultsKey<String?>("pgpPublicKeyArmor")
|
||||
static let pgpPrivateKeyArmor = DefaultsKey<String?>("pgpPrivateKeyArmor")
|
||||
|
||||
static let gitURL = DefaultsKey<URL?>("gitURL")
|
||||
static let gitAuthenticationMethod = DefaultsKey<String?>("gitAuthenticationMethod")
|
||||
static let gitUsername = DefaultsKey<String?>("gitUsername")
|
||||
static let gitSSHPrivateKeyURL = DefaultsKey<URL?>("gitSSHPrivateKeyURL")
|
||||
static let gitSSHKeySource = DefaultsKey<String?>("gitSSHKeySource")
|
||||
static let gitSSHPrivateKeyArmor = DefaultsKey<String?>("gitSSHPrivateKeyArmor")
|
||||
static let gitSignatureName = DefaultsKey<String?>("gitSignatureName")
|
||||
static let gitSignatureEmail = DefaultsKey<String?>("gitSignatureEmail")
|
||||
|
||||
static let lastSyncedTime = DefaultsKey<Date?>("lastSyncedTime")
|
||||
|
||||
static let isTouchIDOn = DefaultsKey<Bool>("isTouchIDOn")
|
||||
static let passcodeKey = DefaultsKey<String?>("passcodeKey")
|
||||
|
||||
static let isHideUnknownOn = DefaultsKey<Bool>("isHideUnknownOn")
|
||||
static let isHideOTPOn = DefaultsKey<Bool>("isHideOTPOn")
|
||||
static let isRememberPassphraseOn = DefaultsKey<Bool>("isRememberPassphraseOn")
|
||||
static let isShowFolderOn = DefaultsKey<Bool>("isShowFolderOn")
|
||||
static let passwordGeneratorFlavor = DefaultsKey<String>("passwordGeneratorFlavor")
|
||||
|
||||
static let encryptInArmored = DefaultsKey<Bool>("encryptInArmored")
|
||||
}
|
||||
61
passKit/Helpers/Globals.swift
Normal file
61
passKit/Helpers/Globals.swift
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
//
|
||||
// Globals.swift
|
||||
// pass
|
||||
//
|
||||
// Created by Mingshen Sun on 21/1/2017.
|
||||
// Copyright © 2017 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
public class Globals {
|
||||
|
||||
// Legacy paths (not shared)
|
||||
public static let documentPathLegacy = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0];
|
||||
public static let libraryPathLegacy = NSSearchPathForDirectoriesInDomains(.libraryDirectory, .userDomainMask, true)[0];
|
||||
public static let pgpPublicKeyPathLegacy = "\(documentPathLegacy)/gpg_key.pub"
|
||||
public static let pgpPrivateKeyPathLegacy = "\(documentPathLegacy)/gpg_key"
|
||||
public static let gitSSHPrivateKeyPathLegacy = "\(documentPathLegacy)/ssh_key"
|
||||
public static let gitSSHPrivateKeyURLLegacy = URL(fileURLWithPath: gitSSHPrivateKeyPathLegacy)
|
||||
public static let repositoryPathLegacy = "\(libraryPathLegacy)/password-store"
|
||||
|
||||
public static let bundleIdentifier = "me.mssun.passforios"
|
||||
public static let groupIdentifier = "group." + bundleIdentifier
|
||||
public static let passKitBundleIdentifier = bundleIdentifier + ".passKit"
|
||||
|
||||
public static let sharedContainerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: groupIdentifier)!
|
||||
public static let documentPath = sharedContainerURL.appendingPathComponent("Documents").path
|
||||
public static let libraryPath = sharedContainerURL.appendingPathComponent("Library").path
|
||||
public static let pgpPublicKeyPath = documentPath + "/gpg_key.pub"
|
||||
public static let pgpPrivateKeyPath = documentPath + "/gpg_key"
|
||||
public static let gitSSHPrivateKeyPath = documentPath + "/ssh_key"
|
||||
public static let gitSSHPrivateKeyURL = URL(fileURLWithPath: gitSSHPrivateKeyPath)
|
||||
public static let repositoryPath = libraryPath + "/password-store"
|
||||
|
||||
public static let passwordDefaultLength = ["Random": (min: 4, max: 64, def: 16),
|
||||
"Apple": (min: 15, max: 15, def: 15)]
|
||||
|
||||
public static let gitSignatureDefaultName = "Pass for iOS"
|
||||
public static let gitSignatureDefaultEmail = "user@passforios"
|
||||
|
||||
public static let passwordDots = "••••••••••••"
|
||||
public static let oneTimePasswordDots = "••••••"
|
||||
public static let passwordFonts = "Menlo"
|
||||
|
||||
// UI related
|
||||
public static let red = UIColor(red:1.00, green:0.23, blue:0.19, alpha:1.0)
|
||||
public static let blue = UIColor(red:0.00, green:0.48, blue:1.00, alpha:1.0)
|
||||
public static let tableCellButtonSize = CGFloat(20.0)
|
||||
|
||||
private init() { }
|
||||
}
|
||||
|
||||
public extension Bundle {
|
||||
var releaseVersionNumber: String? {
|
||||
return infoDictionary?["CFBundleShortVersionString"] as? String
|
||||
}
|
||||
var buildVersionNumber: String? {
|
||||
return infoDictionary?["CFBundleVersion"] as? String
|
||||
}
|
||||
}
|
||||
19
passKit/Helpers/NotificationNames.swift
Normal file
19
passKit/Helpers/NotificationNames.swift
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
//
|
||||
// NotificationNames.swift
|
||||
// pass
|
||||
//
|
||||
// Created by Yishi Lin on 17/3/17.
|
||||
// Copyright © 2017 Yishi Lin, Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public extension Notification.Name {
|
||||
static let passwordStoreUpdated = Notification.Name("passwordStoreUpdated")
|
||||
static let passwordStoreErased = Notification.Name("passwordStoreErased")
|
||||
static let passwordStoreChangeDiscarded = Notification.Name("passwordStoreChangeDiscarded")
|
||||
static let passwordSearch = Notification.Name("passwordSearch")
|
||||
|
||||
static let passwordDisplaySettingChanged = Notification.Name("passwordDisplaySettingChanged")
|
||||
static let passwordDetailDisplaySettingChanged = Notification.Name("passwordDetailDisplaySettingChanged")
|
||||
}
|
||||
34
passKit/Helpers/UITextFieldExtension.swift
Normal file
34
passKit/Helpers/UITextFieldExtension.swift
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
//
|
||||
// UIViewControllerExtionsion.swift
|
||||
// pass
|
||||
//
|
||||
// Created by Yishi Lin on 5/4/17.
|
||||
// Copyright © 2017 Yishi Lin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
private var kAssociationKeyNextField: UInt8 = 0
|
||||
|
||||
extension UITextField {
|
||||
@IBOutlet var nextField: UITextField? {
|
||||
get {
|
||||
return objc_getAssociatedObject(self, &kAssociationKeyNextField) as? UITextField
|
||||
}
|
||||
set(newField) {
|
||||
objc_setAssociatedObject(self, &kAssociationKeyNextField, newField, .OBJC_ASSOCIATION_RETAIN)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension UIViewController {
|
||||
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
||||
if textField.nextField != nil {
|
||||
textField.nextField?.becomeFirstResponder()
|
||||
} else {
|
||||
textField.resignFirstResponder()
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
227
passKit/Helpers/Utils.swift
Normal file
227
passKit/Helpers/Utils.swift
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
//
|
||||
// Utils.swift
|
||||
// pass
|
||||
//
|
||||
// Created by Mingshen Sun on 8/2/2017.
|
||||
// Copyright © 2017 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftyUserDefaults
|
||||
import KeychainAccess
|
||||
import UIKit
|
||||
import SVProgressHUD
|
||||
|
||||
public class Utils {
|
||||
public static func removeFileIfExists(atPath path: String) {
|
||||
let fm = FileManager.default
|
||||
do {
|
||||
if fm.fileExists(atPath: path) {
|
||||
try fm.removeItem(atPath: path)
|
||||
}
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
public static func removeFileIfExists(at url: URL) {
|
||||
removeFileIfExists(atPath: url.path)
|
||||
}
|
||||
|
||||
public static func getLastSyncedTimeString() -> String {
|
||||
guard let lastSyncedTime = SharedDefaults[.lastSyncedTime] else {
|
||||
return "Oops! Sync again?"
|
||||
}
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateStyle = .medium
|
||||
formatter.timeStyle = .short
|
||||
return formatter.string(from: lastSyncedTime)
|
||||
}
|
||||
|
||||
public static func generatePassword(length: Int) -> String{
|
||||
switch SharedDefaults[.passwordGeneratorFlavor] {
|
||||
case "Random":
|
||||
return randomString(length: length)
|
||||
case "Apple":
|
||||
return Keychain.generatePassword()
|
||||
default:
|
||||
return randomString(length: length)
|
||||
}
|
||||
}
|
||||
|
||||
public static func randomString(length: Int) -> String {
|
||||
|
||||
let letters : NSString = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*_+-="
|
||||
let len = UInt32(letters.length)
|
||||
|
||||
var randomString = ""
|
||||
|
||||
for _ in 0 ..< length {
|
||||
let rand = arc4random_uniform(len)
|
||||
var nextChar = letters.character(at: Int(rand))
|
||||
randomString += NSString(characters: &nextChar, length: 1) as String
|
||||
}
|
||||
|
||||
return randomString
|
||||
}
|
||||
|
||||
public static func alert(title: String, message: String, controller: UIViewController, handler: ((UIAlertAction) -> Void)? = nil, completion: (() -> Void)? = nil) {
|
||||
SVProgressHUD.dismiss()
|
||||
let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.alert)
|
||||
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: handler))
|
||||
controller.present(alert, animated: true, completion: completion)
|
||||
}
|
||||
|
||||
public static func getPasswordFromKeychain(name: String) -> String? {
|
||||
let keychain = Keychain(service: Globals.bundleIdentifier)
|
||||
do {
|
||||
return try keychain.getString(name)
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
public static func addPasswordToKeychain(name: String, password: String?) {
|
||||
let keychain = Keychain(service: Globals.bundleIdentifier)
|
||||
keychain[name] = password
|
||||
}
|
||||
public static func removeKeychain(name: String) {
|
||||
let keychain = Keychain(service: Globals.bundleIdentifier)
|
||||
do {
|
||||
try keychain.remove(name)
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
public static func removeAllKeychain() {
|
||||
let keychain = Keychain(service: Globals.bundleIdentifier)
|
||||
do {
|
||||
try keychain.removeAll()
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
public static func copyToPasteboard(textToCopy: String?, expirationTime: Double = 45) {
|
||||
guard textToCopy != nil else {
|
||||
return
|
||||
}
|
||||
UIPasteboard.general.string = textToCopy
|
||||
DispatchQueue.global(qos: .background).asyncAfter(deadline: DispatchTime.now() + expirationTime) {
|
||||
let pasteboardString: String? = UIPasteboard.general.string
|
||||
if textToCopy == pasteboardString {
|
||||
UIPasteboard.general.string = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
public static func attributedPassword(plainPassword: String) -> NSAttributedString{
|
||||
let attributedPassword = NSMutableAttributedString.init(string: plainPassword)
|
||||
// draw all digits in the password into red
|
||||
// draw all punctuation characters in the password into blue
|
||||
for (index, element) in plainPassword.unicodeScalars.enumerated() {
|
||||
if NSCharacterSet.decimalDigits.contains(element) {
|
||||
attributedPassword.addAttribute(NSForegroundColorAttributeName, value: Globals.red, range: NSRange(location: index, length: 1))
|
||||
} else if !NSCharacterSet.letters.contains(element) {
|
||||
attributedPassword.addAttribute(NSForegroundColorAttributeName, value: Globals.blue, range: NSRange(location: index, length: 1))
|
||||
}
|
||||
}
|
||||
return attributedPassword
|
||||
}
|
||||
public static func initDefaultKeys() {
|
||||
if SharedDefaults[.passwordGeneratorFlavor] == "" {
|
||||
SharedDefaults[.passwordGeneratorFlavor] = "Random"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// https://gist.github.com/NikolaiRuhe/eeb135d20c84a7097516
|
||||
public extension FileManager {
|
||||
|
||||
/// This method calculates the accumulated size of a directory on the volume in bytes.
|
||||
///
|
||||
/// As there's no simple way to get this information from the file system it has to crawl the entire hierarchy,
|
||||
/// accumulating the overall sum on the way. The resulting value is roughly equivalent with the amount of bytes
|
||||
/// that would become available on the volume if the directory would be deleted.
|
||||
///
|
||||
/// - note: There are a couple of oddities that are not taken into account (like symbolic links, meta data of
|
||||
/// directories, hard links, ...).
|
||||
func allocatedSizeOfDirectoryAtURL(directoryURL : URL) throws -> UInt64 {
|
||||
|
||||
// We'll sum up content size here:
|
||||
var accumulatedSize = UInt64(0)
|
||||
|
||||
// prefetching some properties during traversal will speed up things a bit.
|
||||
let prefetchedProperties = [
|
||||
URLResourceKey.isRegularFileKey,
|
||||
URLResourceKey.fileAllocatedSizeKey,
|
||||
URLResourceKey.totalFileAllocatedSizeKey,
|
||||
]
|
||||
|
||||
// The error handler simply signals errors to outside code.
|
||||
var errorDidOccur: Error?
|
||||
let errorHandler: (URL, Error) -> Bool = { _, error in
|
||||
errorDidOccur = error
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
// We have to enumerate all directory contents, including subdirectories.
|
||||
let enumerator = self.enumerator(at: directoryURL,
|
||||
includingPropertiesForKeys: prefetchedProperties,
|
||||
options: FileManager.DirectoryEnumerationOptions(),
|
||||
errorHandler: errorHandler)
|
||||
precondition(enumerator != nil)
|
||||
|
||||
// Start the traversal:
|
||||
for item in enumerator! {
|
||||
let contentItemURL = item as! NSURL
|
||||
|
||||
// Bail out on errors from the errorHandler.
|
||||
if let error = errorDidOccur { throw error }
|
||||
|
||||
let resourceValueForKey: (URLResourceKey) throws -> NSNumber? = { key in
|
||||
var value: AnyObject?
|
||||
try contentItemURL.getResourceValue(&value, forKey: key)
|
||||
return value as? NSNumber
|
||||
}
|
||||
|
||||
// Get the type of this item, making sure we only sum up sizes of regular files.
|
||||
guard let isRegularFile = try resourceValueForKey(URLResourceKey.isRegularFileKey) else {
|
||||
preconditionFailure()
|
||||
}
|
||||
|
||||
guard isRegularFile.boolValue else {
|
||||
continue
|
||||
}
|
||||
|
||||
// To get the file's size we first try the most comprehensive value in terms of what the file may use on disk.
|
||||
// This includes metadata, compression (on file system level) and block size.
|
||||
var fileSize = try resourceValueForKey(URLResourceKey.totalFileAllocatedSizeKey)
|
||||
|
||||
// In case the value is unavailable we use the fallback value (excluding meta data and compression)
|
||||
// This value should always be available.
|
||||
fileSize = try fileSize ?? resourceValueForKey(URLResourceKey.fileAllocatedSizeKey)
|
||||
|
||||
guard let size = fileSize else {
|
||||
preconditionFailure("huh? NSURLFileAllocatedSizeKey should always return a value")
|
||||
}
|
||||
|
||||
// We're good, add up the value.
|
||||
accumulatedSize += size.uint64Value
|
||||
}
|
||||
|
||||
// Bail out on errors from the errorHandler.
|
||||
if let error = errorDidOccur { throw error }
|
||||
|
||||
// We finally got it.
|
||||
return accumulatedSize
|
||||
}
|
||||
}
|
||||
|
||||
public extension String {
|
||||
func stringByAddingPercentEncodingForRFC3986() -> String? {
|
||||
let unreserved = "-._~/?"
|
||||
var allowed = CharacterSet.alphanumerics
|
||||
allowed.insert(charactersIn: unreserved)
|
||||
return addingPercentEncoding(withAllowedCharacters: allowed)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue