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:
Yishi Lin 2017-06-13 11:42:49 +08:00
parent 850dc75820
commit d2ba620ae4
45 changed files with 1062 additions and 523 deletions

View 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."
}
}
}

View 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")
}

View 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
}
}

View 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")
}

View 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
View 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)
}
}