diff --git a/.gitignore b/.gitignore index ce3a65b..dd4cb7c 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,7 @@ xcuserdata/ ## Other *.moved-aside *.xcuserstate +.vscode ## Obj-C/Swift specific *.hmap @@ -63,4 +64,4 @@ fastlane/report.xml fastlane/Preview.html fastlane/screenshots fastlane/test_output -docs/ \ No newline at end of file +docs/ diff --git a/Cely Demo/AppDelegate.swift b/Cely Demo/AppDelegate.swift index 313d0c9..e1e68e6 100644 --- a/Cely Demo/AppDelegate.swift +++ b/Cely Demo/AppDelegate.swift @@ -15,14 +15,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { Cely.setup(with: window!, forModel: User(), requiredProperties: [.token], withOptions: [ - .loginCompletionBlock: { (username: String, password: String) in - if username == "asdf", password == "asdf" { - if User.save("FAKETOKEN:\(username)\(password)", as: .token) == .success { - Cely.changeStatus(to: .loggedIn) - } - } - }, - .celyAnimator: CustomAnimator(), + .loginViewController: UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "BiometricLoginViewController"), ]) return true diff --git a/Cely Demo/Base.lproj/Main.storyboard b/Cely Demo/Base.lproj/Main.storyboard index 3177021..83c8412 100644 --- a/Cely Demo/Base.lproj/Main.storyboard +++ b/Cely Demo/Base.lproj/Main.storyboard @@ -1,11 +1,9 @@ - - - - + + - + @@ -22,7 +20,7 @@ + + + @@ -88,7 +97,7 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/Cely Demo/BiometricLoginViewController.swift b/Cely Demo/BiometricLoginViewController.swift new file mode 100644 index 0000000..383d0a5 --- /dev/null +++ b/Cely Demo/BiometricLoginViewController.swift @@ -0,0 +1,74 @@ +// +// BiometricLoginViewController.swift +// Cely Demo +// +// Created by Fabian Buentello on 10/5/19. +// Copyright © 2019 Fabian Buentello. All rights reserved. +// + +import Cely +import UIKit + +class BiometricLoginViewController: UIViewController, UITextFieldDelegate { + @IBOutlet weak var usernameField: UITextField? + @IBOutlet weak var passwordField: UITextField? + + @IBOutlet var textFields: [UITextField]? + + var credentialsExist = false + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + let credentialsResult = Cely.credentials.get() + if case let .success(creds) = credentialsResult { + print(creds) + credentialsExist = true + usernameField?.text = creds.username + passwordField?.text = creds.password + loginButtonPressed() + } + } + + override func viewDidLoad() { + super.viewDidLoad() + + usernameField?.delegate = self + passwordField?.delegate = self + } + + @IBAction func loginButtonPressed() { + if let username = usernameField?.text, let password = passwordField?.text { + let tokenResult = User.save("FAKETOKEN:\(username)\(password)", as: .token) + if case let .failure(error) = tokenResult { + return print("tokenResult Error: \(error)") + } + + if !credentialsExist { + let credentialResult = Cely.credentials.set( + username: username, + password: password, + server: "api.example.com", + options: [.biometricsIfPossible] + ) + + if case let .failure(error) = credentialResult { + return print("Cely store credentials error: \(error)") + } + } + + Cely.changeStatus(to: .loggedIn) + } + } + + func textFieldShouldReturn(_ textField: UITextField) -> Bool { + let nextTag = textField.tag + 1 + + if let next = textFields?.filter({ $0.tag == nextTag }).first { + next.becomeFirstResponder() + return true + } + + textField.resignFirstResponder() + return true + } +} diff --git a/Cely Demo/Cely Demo.entitlements b/Cely Demo/Cely Demo.entitlements index d067d0d..0c67376 100644 --- a/Cely Demo/Cely Demo.entitlements +++ b/Cely Demo/Cely Demo.entitlements @@ -1,10 +1,5 @@ - - keychain-access-groups - - $(AppIdentifierPrefix)com.cely.Cely-Demo - - + diff --git a/Cely Demo/Info.plist b/Cely Demo/Info.plist index 1e04636..d63dc4d 100644 --- a/Cely Demo/Info.plist +++ b/Cely Demo/Info.plist @@ -20,6 +20,8 @@ 1 LSRequiresIPhoneOS + NSFaceIDUsageDescription + Cely Demo UILaunchStoryboardName LaunchScreen UIMainStoryboardFile diff --git a/Cely Demo/LoginStyles.swift b/Cely Demo/LoginStyles.swift index 4113f2d..9eddcdc 100644 --- a/Cely Demo/LoginStyles.swift +++ b/Cely Demo/LoginStyles.swift @@ -15,6 +15,17 @@ struct LoginStyles: CelyStyle { } } +extension UIWindow { + func setCurrentViewController(to viewController: UIViewController?) { + let previousViewController = rootViewController + rootViewController = viewController + + if let previousViewController = previousViewController { + previousViewController.dismiss(animated: false) + } + } +} + struct CustomAnimator: CelyAnimator { func loginTransition(to destinationVC: UIViewController?, with celyWindow: UIWindow) { guard let snapshot = celyWindow.snapshotView(afterScreenUpdates: true) else { diff --git a/Cely Demo/SettingsViewController.swift b/Cely Demo/SettingsViewController.swift index dfd163a..92301ad 100644 --- a/Cely Demo/SettingsViewController.swift +++ b/Cely Demo/SettingsViewController.swift @@ -11,6 +11,20 @@ import UIKit class SettingsViewController: UIViewController { @IBAction func LogoutButtonPressed() { - Cely.logout() + let result = Cely.logout() + if case let .failure(error) = result { + print(error) + } + } + + @IBAction func getCredentialsClicked(_: Any) { + let result = Cely.credentials.get() + switch result { + case let .success(credentials): + print(credentials) + case let .failure(error): + print("failed to get credentials") + print(error) + } } } diff --git a/Cely Demo/User.swift b/Cely Demo/User.swift index de9f1e9..22896a9 100644 --- a/Cely Demo/User.swift +++ b/Cely Demo/User.swift @@ -33,7 +33,7 @@ struct User: CelyUser { } } - @discardableResult func save(_ value: Any) -> StorageResult { + @discardableResult func save(_ value: Any) -> Result { return Cely.save(value, forKey: rawValue, securely: securely(), persisted: persisted()) } @@ -46,7 +46,7 @@ struct User: CelyUser { // MARK: - Save/Get User Properties extension User { - @discardableResult static func save(_ value: Any, as property: Property) -> StorageResult { + @discardableResult static func save(_ value: Any, as property: Property) -> Result { return property.save(value) } diff --git a/Cely.xcodeproj/project.pbxproj b/Cely.xcodeproj/project.pbxproj index 3a3bbbd..3a83c85 100644 --- a/Cely.xcodeproj/project.pbxproj +++ b/Cely.xcodeproj/project.pbxproj @@ -17,7 +17,6 @@ 1F2864451E89593700EE8180 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1F68B01C1E89519A001A0161 /* Main.storyboard */; }; 1F28644E1E895C2100EE8180 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F28644A1E895C2100EE8180 /* User.swift */; }; 1F28644F1E895C2100EE8180 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F28644B1E895C2100EE8180 /* ViewController.swift */; }; - 1F2864501E895C2100EE8180 /* LoginStyles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F28644C1E895C2100EE8180 /* LoginStyles.swift */; }; 1F2864511E895C2100EE8180 /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F28644D1E895C2100EE8180 /* SettingsViewController.swift */; }; 1F2864531E895C4B00EE8180 /* TestStoryboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1F2864521E895C4B00EE8180 /* TestStoryboard.storyboard */; }; 1F68AFDD1E89505A001A0161 /* Cely.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F68AFD41E89505A001A0161 /* Cely.swift */; }; @@ -33,17 +32,10 @@ 1F68AFE71E89505A001A0161 /* CelyProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F68AFD61E89505A001A0161 /* CelyProtocols.swift */; }; 1F68AFE81E89505A001A0161 /* CelyProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F68AFD61E89505A001A0161 /* CelyProtocols.swift */; }; 1F68AFE91E89505A001A0161 /* CelyStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F68AFD71E89505A001A0161 /* CelyStorage.swift */; }; - 1F68AFEA1E89505A001A0161 /* CelyStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F68AFD71E89505A001A0161 /* CelyStorage.swift */; }; - 1F68AFEB1E89505A001A0161 /* CelyStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F68AFD71E89505A001A0161 /* CelyStorage.swift */; }; - 1F68AFEC1E89505A001A0161 /* CelyStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F68AFD71E89505A001A0161 /* CelyStorage.swift */; }; 1F68AFED1E89505A001A0161 /* CelyWindowManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F68AFD81E89505A001A0161 /* CelyWindowManager.swift */; }; 1F68AFEE1E89505A001A0161 /* CelyWindowManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F68AFD81E89505A001A0161 /* CelyWindowManager.swift */; }; 1F68AFEF1E89505A001A0161 /* CelyWindowManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F68AFD81E89505A001A0161 /* CelyWindowManager.swift */; }; 1F68AFF01E89505A001A0161 /* CelyWindowManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F68AFD81E89505A001A0161 /* CelyWindowManager.swift */; }; - 1F68AFF91E89505A001A0161 /* LocksmithError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F68AFDB1E89505A001A0161 /* LocksmithError.swift */; }; - 1F68AFFA1E89505A001A0161 /* LocksmithError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F68AFDB1E89505A001A0161 /* LocksmithError.swift */; }; - 1F68AFFB1E89505A001A0161 /* LocksmithError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F68AFDB1E89505A001A0161 /* LocksmithError.swift */; }; - 1F68AFFC1E89505A001A0161 /* LocksmithError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F68AFDB1E89505A001A0161 /* LocksmithError.swift */; }; 1F68B0041E895069001A0161 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1F68B0011E895069001A0161 /* Assets.xcassets */; }; 1F68B0051E895069001A0161 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1F68B0011E895069001A0161 /* Assets.xcassets */; }; 1F68B0061E895069001A0161 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1F68B0011E895069001A0161 /* Assets.xcassets */; }; @@ -74,9 +66,17 @@ 1F68B0451E8951AA001A0161 /* TestStoryboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1F68B0441E8951AA001A0161 /* TestStoryboard.storyboard */; }; 1F68B0461E8951AA001A0161 /* TestStoryboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1F68B0441E8951AA001A0161 /* TestStoryboard.storyboard */; }; 1F68B0471E8951AA001A0161 /* TestStoryboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1F68B0441E8951AA001A0161 /* TestStoryboard.storyboard */; }; - 1FB2CEF11E8D3E2D00E1A5F6 /* Cely.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3549BB211DA389CD00C63030 /* Cely.framework */; }; - 31817D1122FBCA15004FF30F /* CelyKeychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31817D1022FBCA15004FF30F /* CelyKeychain.swift */; }; + 31238F162331019400D6CBC3 /* Cely.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3549BB211DA389CD00C63030 /* Cely.framework */; }; + 31238F172331019400D6CBC3 /* Cely.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3549BB211DA389CD00C63030 /* Cely.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 31238F1A233103D500D6CBC3 /* CelyStorageError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31238F19233103D500D6CBC3 /* CelyStorageError.swift */; }; + 31394AD223495B19007D16C6 /* BiometricLoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31394AD123495B19007D16C6 /* BiometricLoginViewController.swift */; }; + 3157AA5923DCD0B000E1EEA8 /* LoginStyles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F28644C1E895C2100EE8180 /* LoginStyles.swift */; }; + 317D983923416BC0008E2016 /* CelyCredentialStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317D983823416BC0008E2016 /* CelyCredentialStoreTests.swift */; }; + 317D983B234239BE008E2016 /* KeychainObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317D983A234239BE008E2016 /* KeychainObject.swift */; }; 31FAEB8022ECC4DD003EC60A /* CelySecureStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31FAEB7F22ECC4DD003EC60A /* CelySecureStorage.swift */; }; + 31FEDF6C2342E4FC000DD646 /* CelyStorageErrorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31FEDF6B2342E4FC000DD646 /* CelyStorageErrorTests.swift */; }; + 31FF19AA233FC22300B4B7F1 /* CelyCredentialStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31FF19A9233FC22300B4B7F1 /* CelyCredentialStore.swift */; }; + 31FF19AE233FCE7100B4B7F1 /* CelyKeychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31FF19AD233FCE7100B4B7F1 /* CelyKeychain.swift */; }; 3549BB501DA38A2000C63030 /* Cely.h in Headers */ = {isa = PBXBuildFile; fileRef = 3549BB181DA3890B00C63030 /* Cely.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3549BB511DA38A2000C63030 /* Cely.h in Headers */ = {isa = PBXBuildFile; fileRef = 3549BB181DA3890B00C63030 /* Cely.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3549BB521DA38A2100C63030 /* Cely.h in Headers */ = {isa = PBXBuildFile; fileRef = 3549BB181DA3890B00C63030 /* Cely.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -117,6 +117,20 @@ }; /* End PBXContainerItemProxy section */ +/* Begin PBXCopyFilesBuildPhase section */ + 31238F182331019400D6CBC3 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 31238F172331019400D6CBC3 /* Cely.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ 1F28642B1E89578700EE8180 /* Cely Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Cely Demo.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 1F28642D1E89578700EE8180 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -134,10 +148,9 @@ 1F68AFD61E89505A001A0161 /* CelyProtocols.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CelyProtocols.swift; sourceTree = ""; }; 1F68AFD71E89505A001A0161 /* CelyStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CelyStorage.swift; sourceTree = ""; }; 1F68AFD81E89505A001A0161 /* CelyWindowManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CelyWindowManager.swift; sourceTree = ""; }; - 1F68AFDB1E89505A001A0161 /* LocksmithError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocksmithError.swift; sourceTree = ""; }; 1F68B0011E895069001A0161 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = "Supporting Files/Assets.xcassets"; sourceTree = ""; }; 1F68B0021E895069001A0161 /* Cely.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = Cely.storyboard; path = "Supporting Files/Cely.storyboard"; sourceTree = ""; }; - 1F68B0031E895069001A0161 /* CelyLoginViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CelyLoginViewController.swift; sourceTree = ""; }; + 1F68B0031E895069001A0161 /* CelyLoginViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CelyLoginViewController.swift; path = "Supporting Files/CelyLoginViewController.swift"; sourceTree = ""; }; 1F68B0111E89519A001A0161 /* CelyStorageTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CelyStorageTests.swift; sourceTree = ""; }; 1F68B0121E89519A001A0161 /* CelyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CelyTests.swift; sourceTree = ""; }; 1F68B0131E89519A001A0161 /* CelyWindowManagerTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CelyWindowManagerTest.swift; sourceTree = ""; }; @@ -146,15 +159,21 @@ 1F68B01C1E89519A001A0161 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; 1F68B0441E8951AA001A0161 /* TestStoryboard.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = TestStoryboard.storyboard; path = Frameworks/TestStoryboard.storyboard; sourceTree = ""; }; 1F9D24071E8D49C700FB4BE6 /* Cely Demo.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Cely Demo.entitlements"; sourceTree = ""; }; - 31817D1022FBCA15004FF30F /* CelyKeychain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CelyKeychain.swift; sourceTree = ""; }; + 31238F19233103D500D6CBC3 /* CelyStorageError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CelyStorageError.swift; sourceTree = ""; }; + 31394AD123495B19007D16C6 /* BiometricLoginViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BiometricLoginViewController.swift; sourceTree = ""; }; + 317D983823416BC0008E2016 /* CelyCredentialStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CelyCredentialStoreTests.swift; sourceTree = ""; }; + 317D983A234239BE008E2016 /* KeychainObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainObject.swift; sourceTree = ""; }; 31FAEB7F22ECC4DD003EC60A /* CelySecureStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CelySecureStorage.swift; sourceTree = ""; }; - 3549BB171DA3890B00C63030 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 3549BB181DA3890B00C63030 /* Cely.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Cely.h; sourceTree = ""; }; + 31FEDF6B2342E4FC000DD646 /* CelyStorageErrorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CelyStorageErrorTests.swift; sourceTree = ""; }; + 31FF19A9233FC22300B4B7F1 /* CelyCredentialStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CelyCredentialStore.swift; sourceTree = ""; }; + 31FF19AD233FCE7100B4B7F1 /* CelyKeychain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CelyKeychain.swift; sourceTree = ""; }; + 3549BB171DA3890B00C63030 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = "Supporting Files/Info.plist"; sourceTree = ""; }; + 3549BB181DA3890B00C63030 /* Cely.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Cely.h; path = "Supporting Files/Cely.h"; sourceTree = ""; }; 3549BB211DA389CD00C63030 /* Cely.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Cely.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 3549BB2E1DA389DB00C63030 /* Cely.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Cely.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 3549BB3B1DA389E800C63030 /* Cely.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Cely.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 3549BB481DA389F300C63030 /* Cely.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Cely.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 3549BB551DA38A5E00C63030 /* Info-tvOS.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Info-tvOS.plist"; sourceTree = ""; }; + 3549BB551DA38A5E00C63030 /* Info-tvOS.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "Info-tvOS.plist"; path = "Supporting Files/Info-tvOS.plist"; sourceTree = ""; }; 3549BB571DA38A8800C63030 /* Info-tvOS.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Info-tvOS.plist"; sourceTree = ""; }; 3549BB581DA38A8800C63030 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 3549BB611DA38ADB00C63030 /* CelyTests-iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "CelyTests-iOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -191,7 +210,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 1FB2CEF11E8D3E2D00E1A5F6 /* Cely.framework in Frameworks */, + 31238F162331019400D6CBC3 /* Cely.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -264,10 +283,36 @@ 1F2864371E89578700EE8180 /* Assets.xcassets */, 1F2864391E89578700EE8180 /* LaunchScreen.storyboard */, 1F28643C1E89578700EE8180 /* Info.plist */, + 31394AD123495B19007D16C6 /* BiometricLoginViewController.swift */, ); path = "Cely Demo"; sourceTree = ""; }; + 31186A4023DCCA7D00A66C3C /* Core */ = { + isa = PBXGroup; + children = ( + 311B5E3023310901002166E1 /* Storage */, + 1F68AFD41E89505A001A0161 /* Cely.swift */, + 1F68AFD51E89505A001A0161 /* CelyConstants.swift */, + 1F68AFD61E89505A001A0161 /* CelyProtocols.swift */, + 1F68AFD81E89505A001A0161 /* CelyWindowManager.swift */, + ); + path = Core; + sourceTree = ""; + }; + 311B5E3023310901002166E1 /* Storage */ = { + isa = PBXGroup; + children = ( + 1F68AFD71E89505A001A0161 /* CelyStorage.swift */, + 31FAEB7F22ECC4DD003EC60A /* CelySecureStorage.swift */, + 31238F19233103D500D6CBC3 /* CelyStorageError.swift */, + 31FF19A9233FC22300B4B7F1 /* CelyCredentialStore.swift */, + 31FF19AD233FCE7100B4B7F1 /* CelyKeychain.swift */, + 317D983A234239BE008E2016 /* KeychainObject.swift */, + ); + path = Storage; + sourceTree = ""; + }; 3549BAFB1DA387DB00C63030 = { isa = PBXGroup; children = ( @@ -298,7 +343,7 @@ 3549BB161DA3890B00C63030 /* Sources */ = { isa = PBXGroup; children = ( - 3549BB5C1DA38AA500C63030 /* Core */, + 31186A4023DCCA7D00A66C3C /* Core */, 3549BB541DA38A4000C63030 /* Supporting Files */, ); path = Sources; @@ -307,6 +352,8 @@ 3549BB191DA3890B00C63030 /* Tests */ = { isa = PBXGroup; children = ( + 31FEDF6B2342E4FC000DD646 /* CelyStorageErrorTests.swift */, + 317D983823416BC0008E2016 /* CelyCredentialStoreTests.swift */, 1F68B0111E89519A001A0161 /* CelyStorageTests.swift */, 1F68B0121E89519A001A0161 /* CelyTests.swift */, 1F68B0131E89519A001A0161 /* CelyWindowManagerTest.swift */, @@ -341,21 +388,6 @@ name = "Supporting Files"; sourceTree = ""; }; - 3549BB5C1DA38AA500C63030 /* Core */ = { - isa = PBXGroup; - children = ( - 1F68AFD41E89505A001A0161 /* Cely.swift */, - 1F68AFD51E89505A001A0161 /* CelyConstants.swift */, - 1F68AFD61E89505A001A0161 /* CelyProtocols.swift */, - 1F68AFD71E89505A001A0161 /* CelyStorage.swift */, - 31FAEB7F22ECC4DD003EC60A /* CelySecureStorage.swift */, - 1F68AFD81E89505A001A0161 /* CelyWindowManager.swift */, - 1F68AFDB1E89505A001A0161 /* LocksmithError.swift */, - 31817D1022FBCA15004FF30F /* CelyKeychain.swift */, - ); - name = Core; - sourceTree = ""; - }; 357D954B1DA3F2B400AEC55F /* Frameworks */ = { isa = PBXGroup; children = ( @@ -505,6 +537,7 @@ 1F2864271E89578700EE8180 /* Sources */, 1F2864281E89578700EE8180 /* Frameworks */, 1F2864291E89578700EE8180 /* Resources */, + 31238F182331019400D6CBC3 /* Embed Frameworks */, ); buildRules = ( ); @@ -655,7 +688,7 @@ TargetAttributes = { 1F28642A1E89578700EE8180 = { CreatedOnToolsVersion = 8.2.1; - DevelopmentTeam = 7CX9JN6SPG; + DevelopmentTeam = H367AL682C; LastSwiftMigration = 0900; ProvisioningStyle = Automatic; SystemCapabilities = { @@ -831,10 +864,11 @@ buildActionMask = 2147483647; files = ( 1F28642E1E89578700EE8180 /* AppDelegate.swift in Sources */, - 1F2864501E895C2100EE8180 /* LoginStyles.swift in Sources */, 1F28644F1E895C2100EE8180 /* ViewController.swift in Sources */, + 31394AD223495B19007D16C6 /* BiometricLoginViewController.swift in Sources */, 1F28644E1E895C2100EE8180 /* User.swift in Sources */, 1F2864511E895C2100EE8180 /* SettingsViewController.swift in Sources */, + 3157AA5923DCD0B000E1EEA8 /* LoginStyles.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -845,12 +879,14 @@ 1F68AFE51E89505A001A0161 /* CelyProtocols.swift in Sources */, 1F68AFE11E89505A001A0161 /* CelyConstants.swift in Sources */, 31FAEB8022ECC4DD003EC60A /* CelySecureStorage.swift in Sources */, + 31238F1A233103D500D6CBC3 /* CelyStorageError.swift in Sources */, + 317D983B234239BE008E2016 /* KeychainObject.swift in Sources */, 1F68B00C1E895069001A0161 /* CelyLoginViewController.swift in Sources */, 1F68AFE91E89505A001A0161 /* CelyStorage.swift in Sources */, - 31817D1122FBCA15004FF30F /* CelyKeychain.swift in Sources */, 1F68AFED1E89505A001A0161 /* CelyWindowManager.swift in Sources */, 1F68AFDD1E89505A001A0161 /* Cely.swift in Sources */, - 1F68AFF91E89505A001A0161 /* LocksmithError.swift in Sources */, + 31FF19AE233FCE7100B4B7F1 /* CelyKeychain.swift in Sources */, + 31FF19AA233FC22300B4B7F1 /* CelyCredentialStore.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -861,10 +897,8 @@ 1F68AFE81E89505A001A0161 /* CelyProtocols.swift in Sources */, 1F68AFE41E89505A001A0161 /* CelyConstants.swift in Sources */, 1F68B00F1E895069001A0161 /* CelyLoginViewController.swift in Sources */, - 1F68AFEC1E89505A001A0161 /* CelyStorage.swift in Sources */, 1F68AFF01E89505A001A0161 /* CelyWindowManager.swift in Sources */, 1F68AFE01E89505A001A0161 /* Cely.swift in Sources */, - 1F68AFFC1E89505A001A0161 /* LocksmithError.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -875,10 +909,8 @@ 1F68AFE61E89505A001A0161 /* CelyProtocols.swift in Sources */, 1F68AFE21E89505A001A0161 /* CelyConstants.swift in Sources */, 1F68B00D1E895069001A0161 /* CelyLoginViewController.swift in Sources */, - 1F68AFEA1E89505A001A0161 /* CelyStorage.swift in Sources */, 1F68AFEE1E89505A001A0161 /* CelyWindowManager.swift in Sources */, 1F68AFDE1E89505A001A0161 /* Cely.swift in Sources */, - 1F68AFFA1E89505A001A0161 /* LocksmithError.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -889,10 +921,8 @@ 1F68AFE71E89505A001A0161 /* CelyProtocols.swift in Sources */, 1F68AFE31E89505A001A0161 /* CelyConstants.swift in Sources */, 1F68B00E1E895069001A0161 /* CelyLoginViewController.swift in Sources */, - 1F68AFEB1E89505A001A0161 /* CelyStorage.swift in Sources */, 1F68AFEF1E89505A001A0161 /* CelyWindowManager.swift in Sources */, 1F68AFDF1E89505A001A0161 /* Cely.swift in Sources */, - 1F68AFFB1E89505A001A0161 /* LocksmithError.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -903,7 +933,9 @@ 1F68B03E1E89519A001A0161 /* LoginViewControllerTests.swift in Sources */, 1F68B0231E89519A001A0161 /* CelyTests.swift in Sources */, 1F68B02C1E89519A001A0161 /* ConstantsAndProtocolTests.swift in Sources */, + 317D983923416BC0008E2016 /* CelyCredentialStoreTests.swift in Sources */, 1F68B0201E89519A001A0161 /* CelyStorageTests.swift in Sources */, + 31FEDF6C2342E4FC000DD646 /* CelyStorageErrorTests.swift in Sources */, 1F68B0261E89519A001A0161 /* CelyWindowManagerTest.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -982,13 +1014,16 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "Cely Demo/Cely Demo.entitlements"; + CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - DEVELOPMENT_TEAM = 7CX9JN6SPG; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = H367AL682C; INFOPLIST_FILE = "Cely Demo/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = "com.Cely.Cely-Demo"; + PRODUCT_BUNDLE_IDENTIFIER = "com.xcode.cely-tools.Cely-Demo"; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_SWIFT3_OBJC_INFERENCE = Default; }; name = Debug; @@ -998,13 +1033,16 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "Cely Demo/Cely Demo.entitlements"; + CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - DEVELOPMENT_TEAM = 7CX9JN6SPG; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = H367AL682C; INFOPLIST_FILE = "Cely Demo/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = "com.Cely.Cely-Demo"; + PRODUCT_BUNDLE_IDENTIFIER = "com.xcode.cely-tools.Cely-Demo"; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_SWIFT3_OBJC_INFERENCE = Default; }; name = Release; @@ -1135,11 +1173,11 @@ DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; - INFOPLIST_FILE = Sources/Info.plist; + INFOPLIST_FILE = "Sources/Supporting Files/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.Cely.Cely.Cely; + PRODUCT_BUNDLE_IDENTIFIER = "com.cely-tools.Cely"; PRODUCT_NAME = Cely; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -1158,11 +1196,11 @@ DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; - INFOPLIST_FILE = Sources/Info.plist; + INFOPLIST_FILE = "Sources/Supporting Files/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.Cely.Cely.Cely; + PRODUCT_BUNDLE_IDENTIFIER = "com.cely-tools.Cely"; PRODUCT_NAME = Cely; SKIP_INSTALL = YES; SWIFT_SWIFT3_OBJC_INFERENCE = Default; @@ -1180,10 +1218,10 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = Sources/Info.plist; + INFOPLIST_FILE = "Sources/Supporting Files/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.Cely.Cely.Cely; + PRODUCT_BUNDLE_IDENTIFIER = "com.cely-tools.Cely"; PRODUCT_NAME = Cely; SDKROOT = watchos; SKIP_INSTALL = YES; @@ -1205,10 +1243,10 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = Sources/Info.plist; + INFOPLIST_FILE = "Sources/Supporting Files/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.Cely.Cely.Cely; + PRODUCT_BUNDLE_IDENTIFIER = "com.cely-tools.Cely"; PRODUCT_NAME = Cely; SDKROOT = watchos; SKIP_INSTALL = YES; @@ -1230,11 +1268,11 @@ DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_VERSION = A; - INFOPLIST_FILE = Sources/Info.plist; + INFOPLIST_FILE = "Sources/Supporting Files/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.10; - PRODUCT_BUNDLE_IDENTIFIER = com.Cely.Cely.Cely; + PRODUCT_BUNDLE_IDENTIFIER = "com.cely-tools.Cely"; PRODUCT_NAME = Cely; SDKROOT = macosx; SKIP_INSTALL = YES; @@ -1255,11 +1293,11 @@ DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_VERSION = A; - INFOPLIST_FILE = Sources/Info.plist; + INFOPLIST_FILE = "Sources/Supporting Files/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.10; - PRODUCT_BUNDLE_IDENTIFIER = com.Cely.Cely.Cely; + PRODUCT_BUNDLE_IDENTIFIER = "com.cely-tools.Cely"; PRODUCT_NAME = Cely; SDKROOT = macosx; SKIP_INSTALL = YES; @@ -1277,10 +1315,10 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = "Sources/Info-tvOS.plist"; + INFOPLIST_FILE = "Sources/Supporting Files/Info-tvOS.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.Cely.Cely.Cely; + PRODUCT_BUNDLE_IDENTIFIER = "com.cely-tools.Cely"; PRODUCT_NAME = Cely; SDKROOT = appletvos; SKIP_INSTALL = YES; @@ -1301,10 +1339,10 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = "Sources/Info-tvOS.plist"; + INFOPLIST_FILE = "Sources/Supporting Files/Info-tvOS.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.Cely.Cely.Cely; + PRODUCT_BUNDLE_IDENTIFIER = "com.cely-tools.Cely"; PRODUCT_NAME = Cely; SDKROOT = appletvos; SKIP_INSTALL = YES; @@ -1326,7 +1364,7 @@ INFOPLIST_FILE = Tests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; OTHER_SWIFT_FLAGS = "-D DEBUG -D TEST"; - PRODUCT_BUNDLE_IDENTIFIER = com.Cely.Cely.CelyTests; + PRODUCT_BUNDLE_IDENTIFIER = "com.cely-tools.CelyTests"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; @@ -1345,7 +1383,7 @@ FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = Tests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.Cely.Cely.CelyTests; + PRODUCT_BUNDLE_IDENTIFIER = "com.cely-tools.CelyTests"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Cely Demo.app/Cely Demo"; @@ -1361,7 +1399,7 @@ FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = "Tests/Info-tvOS.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.Cely.Cely.CelyTests; + PRODUCT_BUNDLE_IDENTIFIER = "com.cely-tools.CelyTests"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -1379,7 +1417,7 @@ FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = "Tests/Info-tvOS.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.Cely.Cely.CelyTests; + PRODUCT_BUNDLE_IDENTIFIER = "com.cely-tools.CelyTests"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; SWIFT_VERSION = 3.0; @@ -1399,7 +1437,7 @@ INFOPLIST_FILE = Tests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.10; - PRODUCT_BUNDLE_IDENTIFIER = com.Cely.Cely.CelyTests; + PRODUCT_BUNDLE_IDENTIFIER = "com.cely-tools.CelyTests"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -1419,7 +1457,7 @@ INFOPLIST_FILE = Tests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.10; - PRODUCT_BUNDLE_IDENTIFIER = com.Cely.Cely.CelyTests; + PRODUCT_BUNDLE_IDENTIFIER = "com.cely-tools.CelyTests"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; SWIFT_VERSION = 3.0; diff --git a/Cely.xcodeproj/xcshareddata/xcschemes/Cely Demo.xcscheme b/Cely.xcodeproj/xcshareddata/xcschemes/Cely Demo.xcscheme new file mode 100644 index 0000000..6da19ff --- /dev/null +++ b/Cely.xcodeproj/xcshareddata/xcschemes/Cely Demo.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Cely.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Cely.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/Cely.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Sources/CelyKeychain.swift b/Sources/CelyKeychain.swift deleted file mode 100644 index eeb1089..0000000 --- a/Sources/CelyKeychain.swift +++ /dev/null @@ -1,78 +0,0 @@ -// -// CelyKeychain.swift -// Cely-iOS -// -// Created by Fabian Buentello on 8/7/19. -// Copyright © 2019 Fabian Buentello. All rights reserved. -// - -import Foundation - -internal struct CelyKeychain { - // query to identify Cely Credentials - private let baseQuery: [String: Any] = [ - kSecClass as String: kSecClassInternetPassword, - kSecAttrLabel as String: "cely.secure.store.key", - kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlocked, - ] - - private var searchQuery: [String: Any] { - var queryCopy = baseQuery - queryCopy[kSecMatchLimit as String] = kSecMatchLimitOne - queryCopy[kSecReturnAttributes as String] = true - queryCopy[kSecReturnData as String] = true - return queryCopy - } - - func clearKeychain() -> StorageResult { - let status = SecItemDelete(baseQuery as CFDictionary) - let errorStatus = LocksmithError(fromStatusCode: Int(status)) - guard errorStatus == .noError - else { return StorageResult.fail(errorStatus) } - return .success - } - - func getCredentials() throws -> [String: Any] { - var item: CFTypeRef? - let status = SecItemCopyMatching(searchQuery as CFDictionary, &item) - guard status != errSecItemNotFound else { throw LocksmithError.notFound } - guard status == errSecSuccess else { throw LocksmithError.undefined } - - do { - guard let existingItem = item as? [String: Any], - let secureData = existingItem[kSecValueData as String] as? Data, - let loadedDictionary = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(secureData) as? [String: Any] - else { throw LocksmithError.decode } - return loadedDictionary - } - } - - func set(_ secrets: [String: Any]) -> StorageResult { - var queryCopy = baseQuery - let storeData = NSKeyedArchiver.archivedData(withRootObject: secrets) - queryCopy[kSecValueData as String] = storeData - - // try adding first - let status: OSStatus = SecItemAdd(queryCopy as CFDictionary, nil) - let err = LocksmithError(fromStatusCode: Int(status)) - if err == .noError { - return .success - } else if err == .duplicate { - // already exists, should update instead - return update(secrets: secrets) - } - - return .fail(err) - } - - private func update(secrets: [String: Any]) -> StorageResult { - let secretData = NSKeyedArchiver.archivedData(withRootObject: secrets) - let updateDictionary = [kSecValueData as String: secretData] - let status: OSStatus = SecItemUpdate(baseQuery as CFDictionary, updateDictionary as CFDictionary) - let err = LocksmithError(fromStatusCode: Int(status)) - if err == .noError { - return .success - } - return .fail(err) - } -} diff --git a/Sources/CelySecureStorage.swift b/Sources/CelySecureStorage.swift deleted file mode 100644 index 67173f3..0000000 --- a/Sources/CelySecureStorage.swift +++ /dev/null @@ -1,50 +0,0 @@ -// -// CelySecureStorage.swift -// Cely-iOS -// -// Created by Fabian Buentello on 7/27/19. -// Copyright © 2019 Fabian Buentello. All rights reserved. -// - -import Foundation - -internal class CelySecureStorage { - var store: [String: Any] = [:] - private let _celyKeychain = CelyKeychain() - - init() { - do { - let credentials = try _celyKeychain.getCredentials() - store = credentials - } catch let error as LocksmithError { - print("Failed to retrieve store from keychain") - print(error) - } catch { - print("Failed to retrieve store from keychain") - print(error) - } - } - - func clearStorage() { - let status = _celyKeychain.clearKeychain() - if status != .success { - print("Failed to clear keychain, error: \(status)") - } else { - store = [:] - } - } - - func set(_ value: Any, forKey key: String) -> StorageResult { - var storeCopy = store - storeCopy[key] = value - let result = _celyKeychain.set(storeCopy) - if result == .success { - store[key] = value - } - return result - } - - func get(_ key: String) -> Any? { - return store[key] - } -} diff --git a/Sources/Cely.swift b/Sources/Core/Cely.swift similarity index 80% rename from Sources/Cely.swift rename to Sources/Core/Cely.swift index ebc477e..d8e332c 100644 --- a/Sources/Cely.swift +++ b/Sources/Core/Cely.swift @@ -13,13 +13,16 @@ public struct Cely { fileprivate init() {} public typealias CelyLoginCompletion = (_ username: String, _ password: String) -> Void /// Properties that are needed inorder for user to stay logged in. - public static var requiredProperties: [CelyProperty] = [] + public internal(set) static var requiredProperties: [CelyProperty] = [] /// A `Storage` instance - public static var store: CelyStorageProtocol = CelyStorage.sharedInstance + public private(set) static var store: CelyStorageProtocol = CelyStorage.sharedInstance /// A Completion Block that is expecting a `username:String` and a `password:String` - public static var loginCompletionBlock: CelyLoginCompletion? + private(set) static var loginCompletionBlock: CelyLoginCompletion? + + /// `CelyCredentialsStore` instance + public private(set) static var credentials = CelyCredentialStore.sharedInstance /// Sets up Cely within your application /// @@ -84,8 +87,14 @@ extension Cely { /// - parameter persist: `Boolean`: Keep data after logout /// /// - returns: `Boolean`: Whether or not your value was successfully set. - @discardableResult public static func save(_ value: Any?, forKey key: String, toStorage store: CelyStorageProtocol = store, securely secure: Bool = false, persisted persist: Bool = false) -> StorageResult { - return store.set(value, forKey: key, securely: secure, persisted: persist) + @discardableResult + public static func save(_ value: Any?, forKey key: String, toStorage store: CelyStorageProtocol = store, securely secure: Bool = false, persisted persist: Bool = false) -> Result { + do { + try store.set(value, forKey: key, securely: secure, persisted: persist) + return .success(()) + } catch { + return .failure(error) + } } /// Perform action like `LoggedIn` or `LoggedOut` @@ -98,9 +107,15 @@ extension Cely { /// Log user out /// /// - parameter store: Storage `Cely` will be using. Defaulted to `CelyStorageProtocol` - public static func logout(useStorage store: CelyStorageProtocol = store) { - store.removeAllData() - changeStatus(to: .loggedOut) + @discardableResult + public static func logout(useStorage store: CelyStorageProtocol = store) -> Result { + do { + try store.clearStorage() + changeStatus(to: .loggedOut) + return .success(()) + } catch { + return .failure(error) + } } /// Returns whether or not the user is logged in diff --git a/Sources/CelyConstants.swift b/Sources/Core/CelyConstants.swift similarity index 80% rename from Sources/CelyConstants.swift rename to Sources/Core/CelyConstants.swift index 032802f..c29e122 100644 --- a/Sources/CelyConstants.swift +++ b/Sources/Core/CelyConstants.swift @@ -34,25 +34,6 @@ public enum CelyOptions { case celyAnimator } -// enum result on whether or not Cely successfully saved your data -public enum StorageResult: Equatable { - case success - case fail(LocksmithError) -} - -public func == (lhs: StorageResult, rhs: StorageResult) -> Bool { - switch (lhs, rhs) { - case let (.fail(error1), .fail(error2)): - return error1 == error2 - - case (.success, .success): - return true - - default: - return false - } -} - internal extension UITextField { @IBInspectable var leftSpacer: CGFloat { get { @@ -67,7 +48,7 @@ internal extension UITextField { } } -public extension UIWindow { +internal extension UIWindow { func setCurrentViewController(to viewController: UIViewController?) { let previousViewController = rootViewController rootViewController = viewController @@ -77,3 +58,8 @@ public extension UIWindow { } } } + +public enum AccessControlOptions { + case biometricsIfPossible + case thisDeviceOnly +} diff --git a/Sources/CelyProtocols.swift b/Sources/Core/CelyProtocols.swift similarity index 97% rename from Sources/CelyProtocols.swift rename to Sources/Core/CelyProtocols.swift index 4d37ea3..ca0cc86 100644 --- a/Sources/CelyProtocols.swift +++ b/Sources/Core/CelyProtocols.swift @@ -16,9 +16,9 @@ public protocol CelyUser { /// Protocol a storage class must abide by in order for Cely to use it public protocol CelyStorageProtocol { - func set(_ value: Any?, forKey key: String, securely secure: Bool, persisted persist: Bool) -> StorageResult + func set(_ value: Any?, forKey key: String, securely secure: Bool, persisted persist: Bool) throws func get(_ key: String) -> Any? - func removeAllData() + func clearStorage() throws } /// Protocol that allows styles to be applied to Cely's default LoginViewController diff --git a/Sources/CelyWindowManager.swift b/Sources/Core/CelyWindowManager.swift similarity index 100% rename from Sources/CelyWindowManager.swift rename to Sources/Core/CelyWindowManager.swift diff --git a/Sources/Core/Storage/CelyCredentialStore.swift b/Sources/Core/Storage/CelyCredentialStore.swift new file mode 100644 index 0000000..b5164f9 --- /dev/null +++ b/Sources/Core/Storage/CelyCredentialStore.swift @@ -0,0 +1,76 @@ +// +// CelyCredentialStore.swift +// Cely-iOS +// +// Created by Fabian Buentello on 9/28/19. +// Copyright © 2019 Fabian Buentello. All rights reserved. +// + +import Foundation + +public struct CelyCredentials { + public let username: String + public let password: String + public let server: String +} + +/// CelyCredentialStore - object that stores user credentials +public struct CelyCredentialStore { + static let sharedInstance = CelyCredentialStore() + + let keychain: KeychainProtocol + + init(keychain: KeychainProtocol = CelyKeychain()) { + self.keychain = keychain + } + + func setCredentialsLookupKey(keyDictionary: [CFString: Any]) throws { + try Cely.store.set(keyDictionary, forKey: kCelyCredentialsLookupKey, securely: false, persisted: true) + } + + func getCredentialsLookupKey() -> [CFString: Any]? { + return Cely.get(key: kCelyCredentialsLookupKey) as? [CFString: Any] + } + + /// Set user credentials + /// - Parameters: + /// - username: username for user + /// - password: password for user + /// - server: api uri for account + /// - options: Array of AccessControlOptions for credentials to be saved with + @discardableResult + public func set(username: String, password: String, server: String, options: [AccessControlOptions] = []) -> Result { + guard let passwordData = password.data(using: .utf8) else { + return .failure(CelyStorageError.invalidValue) + } + + do { + let keychainQuery = KeychainObject(account: username, server: server, value: passwordData, options: options) + try setCredentialsLookupKey(keyDictionary: keychainQuery.toLookupMap()) + try keychain.set(query: keychainQuery) + return .success(()) + } catch { + return .failure(error) + } + } + + /// Return credentials + public func get() -> Result { + guard let lookupQuery = getCredentialsLookupKey() else { + return .failure(CelyStorageError.unexpectedError) + } + guard + case let .success(item) = keychain.get(query: KeychainObject.buildFromKeychain(dictionary: lookupQuery as CFTypeRef)), + let username = item.account, + let server = item.server, + let passwordData = item.value else { + return .failure(CelyStorageError.itemNotFound) + } + + guard let password = String(data: passwordData, encoding: .utf8) else { + return .failure(CelyStorageError.invalidValue) + } + + return .success(CelyCredentials(username: username, password: password, server: server)) + } +} diff --git a/Sources/Core/Storage/CelyKeychain.swift b/Sources/Core/Storage/CelyKeychain.swift new file mode 100644 index 0000000..32ea028 --- /dev/null +++ b/Sources/Core/Storage/CelyKeychain.swift @@ -0,0 +1,66 @@ +// +// CelyKeychain.swift +// Cely-iOS +// +// Created by Fabian Buentello on 9/28/19. +// Copyright © 2019 Fabian Buentello. All rights reserved. +// + +import Foundation + +protocol KeychainProtocol { + func get(query: KeychainObject) -> Result + func set(query: KeychainObject) throws + func delete(query: KeychainObject) throws +} + +internal struct CelyKeychain: KeychainProtocol { + func get(query: KeychainObject) -> Result { + let newQuery = query.toGetMap() + var someItem: RawDictionary? + let status = SecItemCopyMatching(newQuery as CFDictionary, &someItem) + guard status == errSecSuccess else { + return .failure(CelyStorageError(status: status)) + } + + guard let item = someItem else { + return .failure(CelyStorageError.missingValue) + } + let celyQuery = KeychainObject.buildFromKeychain(dictionary: item) + return .success(celyQuery) + } + + func set(query: KeychainObject) throws { + // try adding first + let newQuery = query.toSetMap(withValue: true) + let status: OSStatus = SecItemAdd(newQuery as CFDictionary, nil) + let code = CelyStorageError(status: status) + switch code { + case .noError: return + case .duplicateItem: return try update(query) + default: throw code + } + } + + private func update(_ query: KeychainObject) throws { + guard let _ = query.value as CFData? else { throw CelyStorageError.invalidValue } + var newDictionary = query.toSetMap(withValue: true) + for key in query.toGetMap().keys { + newDictionary.removeValue(forKey: key) + } + + let status: OSStatus = SecItemUpdate(query.toLookupMap() as CFDictionary, newDictionary as CFDictionary) + let code = CelyStorageError(status: status) + guard code == .noError else { + throw code + } + } + + func delete(query: KeychainObject) throws { + let status = SecItemDelete(query.toLookupMap() as CFDictionary) + let errorStatus = CelyStorageError(status: status) + guard errorStatus == .noError else { + throw errorStatus + } + } +} diff --git a/Sources/Core/Storage/CelySecureStorage.swift b/Sources/Core/Storage/CelySecureStorage.swift new file mode 100644 index 0000000..52fc557 --- /dev/null +++ b/Sources/Core/Storage/CelySecureStorage.swift @@ -0,0 +1,48 @@ +// +// CelySecureStorage.swift +// Cely-iOS +// +// Created by Fabian Buentello on 7/27/19. +// Copyright © 2019 Fabian Buentello. All rights reserved. +// + +import Foundation + +internal class CelySecureStorage: CelyStorageProtocol { + var store: [String: Any] = [:] + private let celyKeychain = CelyKeychain() + + init() { + let protectedDataQuery: Any = [ + kSecClass: kSecClassInternetPassword, + kSecAttrAccount: kProtectedDataAccount, + kSecAttrLabel: kCelySecureStoreLabel, + ] + + let object = KeychainObject.buildFromKeychain(dictionary: protectedDataQuery as AnyObject) + let existingResult = celyKeychain.get(query: object) + + if case let .success(protectedData) = existingResult, + let protectedStoreData = protectedData.value, + let protectedStore = NSKeyedUnarchiver.unarchiveObject(with: protectedStoreData) as? [String: Any] { + store = protectedStore + } + } + + func set(_ value: Any?, forKey key: String, securely _: Bool = true, persisted _: Bool = true) throws { + var storeCopy = store + storeCopy[key] = value + let object = KeychainObject(account: kProtectedDataAccount, value: NSKeyedArchiver.archivedData(withRootObject: storeCopy)) + try celyKeychain.set(query: object) + store[key] = value + } + + func clearStorage() throws { + try celyKeychain.delete(query: KeychainObject()) + store = [:] + } + + func get(_ key: String) -> Any? { + return store[key] + } +} diff --git a/Sources/CelyStorage.swift b/Sources/Core/Storage/CelyStorage.swift similarity index 84% rename from Sources/CelyStorage.swift rename to Sources/Core/Storage/CelyStorage.swift index 9758a16..a7bdbfd 100644 --- a/Sources/CelyStorage.swift +++ b/Sources/Core/Storage/CelyStorage.swift @@ -12,8 +12,11 @@ internal let kCelyDomain = "cely.storage" internal let kStore = "store" internal let kPersisted = "persisted" internal let kLaunchedBefore = "launchedBefore" +internal let kProtectedDataAccount = "cely.secure.store.protected.data" +internal let kCelySecureStoreLabel = "cely.secure.store.key" +internal let kCelyCredentialsLookupKey = "credentials-lookup" -public class CelyStorage: CelyStorageProtocol { +internal class CelyStorage: CelyStorageProtocol { // MARK: - Variables static let sharedInstance = CelyStorage() @@ -35,7 +38,9 @@ public class CelyStorage: CelyStorageProtocol { UserDefaults.standard.synchronize() // Clear Secure Storage - secureStore.clearStorage() + do { + try secureStore.clearStorage() + } catch {} } setupStorage() @@ -51,11 +56,11 @@ public class CelyStorage: CelyStorageProtocol { } /// Removes all data from both `secureStorage` and regular `storage` - public func removeAllData() { + public func clearStorage() throws { CelyStorage.sharedInstance.storage[kStore] = [:] UserDefaults.standard.setPersistentDomain(CelyStorage.sharedInstance.storage, forName: kCelyDomain) UserDefaults.standard.synchronize() - CelyStorage.sharedInstance.secureStore.clearStorage() + try CelyStorage.sharedInstance.secureStore.clearStorage() } /// Saves data to storage @@ -66,10 +71,10 @@ public class CelyStorage: CelyStorageProtocol { /// - parameter persisted: `Boolean`: Keep data after logout /// /// - returns: `Boolean` on whether or not it successfully saved - public func set(_ value: Any?, forKey key: String, securely secure: Bool = false, persisted: Bool = false) -> StorageResult { - guard let val = value else { return .fail(.undefined) } + public func set(_ value: Any?, forKey key: String, securely secure: Bool = false, persisted: Bool = false) throws { + guard let val = value else { throw CelyStorageError.param } if secure { - return secureStore.set(val, forKey: key) + return try secureStore.set(val, forKey: key) } else { if persisted { CelyStorage.sharedInstance.storage[kPersisted]?[key] = val @@ -81,7 +86,6 @@ public class CelyStorage: CelyStorageProtocol { UserDefaults.standard.setPersistentDomain(CelyStorage.sharedInstance.storage, forName: kCelyDomain) UserDefaults.standard.synchronize() } - return .success } /// Retrieve user data from key diff --git a/Sources/Core/Storage/CelyStorageError.swift b/Sources/Core/Storage/CelyStorageError.swift new file mode 100644 index 0000000..3018899 --- /dev/null +++ b/Sources/Core/Storage/CelyStorageError.swift @@ -0,0 +1,1250 @@ +// +// CelyStorageError.swift +// Cely-iOS +// +// Created by Fabian Buentello on 9/17/19. +// Copyright © 2019 Fabian Buentello. All rights reserved. +// + +import Foundation + +// https://github.com/kishikawakatsumi/KeychainAccess/blob/3a9c83cf8b8cfaecd1097916fae803e1b1d6447f/Lib/KeychainAccess/Keychain.swift#L1695 + +public enum CelyStorageError: OSStatus, Error { + case noError = 0 + case unimplemented = -4 + case diskFull = -34 + case io = -36 + case opWr = -49 + case param = -50 + case wrPerm = -61 + case allocate = -108 + case userCanceled = -128 + case badReq = -909 + case internalComponent = -2070 + case notAvailable = -25291 + case readOnly = -25292 + case authFailed = -25293 + case noSuchKeychain = -25294 + case invalidKeychain = -25295 + case duplicateKeychain = -25296 + case duplicateCallback = -25297 + case invalidCallback = -25298 + case duplicateItem = -25299 + case itemNotFound = -25300 + case bufferTooSmall = -25301 + case dataTooLarge = -25302 + case noSuchAttr = -25303 + case invalidItemRef = -25304 + case invalidSearchRef = -25305 + case noSuchClass = -25306 + case noDefaultKeychain = -25307 + case interactionNotAllowed = -25308 + case readOnlyAttr = -25309 + case wrongSecVersion = -25310 + case keySizeNotAllowed = -25311 + case noStorageModule = -25312 + case noCertificateModule = -25313 + case noPolicyModule = -25314 + case interactionRequired = -25315 + case dataNotAvailable = -25316 + case dataNotModifiable = -25317 + case createChainFailed = -25318 + case invalidPrefsDomain = -25319 + case inDarkWake = -25320 + case aclNotSimple = -25240 + case policyNotFound = -25241 + case invalidTrustSetting = -25242 + case noAccessForItem = -25243 + case invalidOwnerEdit = -25244 + case trustNotAvailable = -25245 + case unsupportedFormat = -25256 + case unknownFormat = -25257 + case keyIsSensitive = -25258 + case multiplePrivKeys = -25259 + case passphraseRequired = -25260 + case invalidPasswordRef = -25261 + case invalidTrustSettings = -25262 + case noTrustSettings = -25263 + case pkcs12VerifyFailure = -25264 + case invalidCertificate = -26265 + case notSigner = -26267 + case policyDenied = -26270 + case invalidKey = -26274 + case decode = -26275 + case `internal` = -26276 + case unsupportedAlgorithm = -26268 + case unsupportedOperation = -26271 + case unsupportedPadding = -26273 + case itemInvalidKey = -34000 + case itemInvalidKeyType = -34001 + case itemInvalidValue = -34002 + case itemClassMissing = -34003 + case itemMatchUnsupported = -34004 + case useItemListUnsupported = -34005 + case useKeychainUnsupported = -34006 + case useKeychainListUnsupported = -34007 + case returnDataUnsupported = -34008 + case returnAttributesUnsupported = -34009 + case returnRefUnsupported = -34010 + case returnPersitentRefUnsupported = -34011 + case valueRefUnsupported = -34012 + case valuePersistentRefUnsupported = -34013 + case returnMissingPointer = -34014 + case matchLimitUnsupported = -34015 + case itemIllegalQuery = -34016 + case waitForCallback = -34017 + case missingEntitlement = -34018 + case upgradePending = -34019 + case mpSignatureInvalid = -25327 + case otrTooOld = -25328 + case otrIDTooNew = -25329 + case serviceNotAvailable = -67585 + case insufficientClientID = -67586 + case deviceReset = -67587 + case deviceFailed = -67588 + case appleAddAppACLSubject = -67589 + case applePublicKeyIncomplete = -67590 + case appleSignatureMismatch = -67591 + case appleInvalidKeyStartDate = -67592 + case appleInvalidKeyEndDate = -67593 + case conversionError = -67594 + case appleSSLv2Rollback = -67595 + case quotaExceeded = -67596 + case fileTooBig = -67597 + case invalidDatabaseBlob = -67598 + case invalidKeyBlob = -67599 + case incompatibleDatabaseBlob = -67600 + case incompatibleKeyBlob = -67601 + case hostNameMismatch = -67602 + case unknownCriticalExtensionFlag = -67603 + case noBasicConstraints = -67604 + case noBasicConstraintsCA = -67605 + case invalidAuthorityKeyID = -67606 + case invalidSubjectKeyID = -67607 + case invalidKeyUsageForPolicy = -67608 + case invalidExtendedKeyUsage = -67609 + case invalidIDLinkage = -67610 + case pathLengthConstraintExceeded = -67611 + case invalidRoot = -67612 + case crlExpired = -67613 + case crlNotValidYet = -67614 + case crlNotFound = -67615 + case crlServerDown = -67616 + case crlBadURI = -67617 + case unknownCertExtension = -67618 + case unknownCRLExtension = -67619 + case crlNotTrusted = -67620 + case crlPolicyFailed = -67621 + case idpFailure = -67622 + case smimeEmailAddressesNotFound = -67623 + case smimeBadExtendedKeyUsage = -67624 + case smimeBadKeyUsage = -67625 + case smimeKeyUsageNotCritical = -67626 + case smimeNoEmailAddress = -67627 + case smimeSubjAltNameNotCritical = -67628 + case sslBadExtendedKeyUsage = -67629 + case ocspBadResponse = -67630 + case ocspBadRequest = -67631 + case ocspUnavailable = -67632 + case ocspStatusUnrecognized = -67633 + case endOfData = -67634 + case incompleteCertRevocationCheck = -67635 + case networkFailure = -67636 + case ocspNotTrustedToAnchor = -67637 + case recordModified = -67638 + case ocspSignatureError = -67639 + case ocspNoSigner = -67640 + case ocspResponderMalformedReq = -67641 + case ocspResponderInternalError = -67642 + case ocspResponderTryLater = -67643 + case ocspResponderSignatureRequired = -67644 + case ocspResponderUnauthorized = -67645 + case ocspResponseNonceMismatch = -67646 + case codeSigningBadCertChainLength = -67647 + case codeSigningNoBasicConstraints = -67648 + case codeSigningBadPathLengthConstraint = -67649 + case codeSigningNoExtendedKeyUsage = -67650 + case codeSigningDevelopment = -67651 + case resourceSignBadCertChainLength = -67652 + case resourceSignBadExtKeyUsage = -67653 + case trustSettingDeny = -67654 + case invalidSubjectName = -67655 + case unknownQualifiedCertStatement = -67656 + case mobileMeRequestQueued = -67657 + case mobileMeRequestRedirected = -67658 + case mobileMeServerError = -67659 + case mobileMeServerNotAvailable = -67660 + case mobileMeServerAlreadyExists = -67661 + case mobileMeServerServiceErr = -67662 + case mobileMeRequestAlreadyPending = -67663 + case mobileMeNoRequestPending = -67664 + case mobileMeCSRVerifyFailure = -67665 + case mobileMeFailedConsistencyCheck = -67666 + case notInitialized = -67667 + case invalidHandleUsage = -67668 + case pvcReferentNotFound = -67669 + case functionIntegrityFail = -67670 + case internalError = -67671 + case memoryError = -67672 + case invalidData = -67673 + case mdsError = -67674 + case invalidPointer = -67675 + case selfCheckFailed = -67676 + case functionFailed = -67677 + case moduleManifestVerifyFailed = -67678 + case invalidGUID = -67679 + case invalidHandle = -67680 + case invalidDBList = -67681 + case invalidPassthroughID = -67682 + case invalidNetworkAddress = -67683 + case crlAlreadySigned = -67684 + case invalidNumberOfFields = -67685 + case verificationFailure = -67686 + case unknownTag = -67687 + case invalidSignature = -67688 + case invalidName = -67689 + case invalidCertificateRef = -67690 + case invalidCertificateGroup = -67691 + case tagNotFound = -67692 + case invalidQuery = -67693 + case invalidValue = -67694 + case callbackFailed = -67695 + case aclDeleteFailed = -67696 + case aclReplaceFailed = -67697 + case aclAddFailed = -67698 + case aclChangeFailed = -67699 + case invalidAccessCredentials = -67700 + case invalidRecord = -67701 + case invalidACL = -67702 + case invalidSampleValue = -67703 + case incompatibleVersion = -67704 + case privilegeNotGranted = -67705 + case invalidScope = -67706 + case pvcAlreadyConfigured = -67707 + case invalidPVC = -67708 + case emmLoadFailed = -67709 + case emmUnloadFailed = -67710 + case addinLoadFailed = -67711 + case invalidKeyRef = -67712 + case invalidKeyHierarchy = -67713 + case addinUnloadFailed = -67714 + case libraryReferenceNotFound = -67715 + case invalidAddinFunctionTable = -67716 + case invalidServiceMask = -67717 + case moduleNotLoaded = -67718 + case invalidSubServiceID = -67719 + case attributeNotInContext = -67720 + case moduleManagerInitializeFailed = -67721 + case moduleManagerNotFound = -67722 + case eventNotificationCallbackNotFound = -67723 + case inputLengthError = -67724 + case outputLengthError = -67725 + case privilegeNotSupported = -67726 + case deviceError = -67727 + case attachHandleBusy = -67728 + case notLoggedIn = -67729 + case algorithmMismatch = -67730 + case keyUsageIncorrect = -67731 + case keyBlobTypeIncorrect = -67732 + case keyHeaderInconsistent = -67733 + case unsupportedKeyFormat = -67734 + case unsupportedKeySize = -67735 + case invalidKeyUsageMask = -67736 + case unsupportedKeyUsageMask = -67737 + case invalidKeyAttributeMask = -67738 + case unsupportedKeyAttributeMask = -67739 + case invalidKeyLabel = -67740 + case unsupportedKeyLabel = -67741 + case invalidKeyFormat = -67742 + case unsupportedVectorOfBuffers = -67743 + case invalidInputVector = -67744 + case invalidOutputVector = -67745 + case invalidContext = -67746 + case invalidAlgorithm = -67747 + case invalidAttributeKey = -67748 + case missingAttributeKey = -67749 + case invalidAttributeInitVector = -67750 + case missingAttributeInitVector = -67751 + case invalidAttributeSalt = -67752 + case missingAttributeSalt = -67753 + case invalidAttributePadding = -67754 + case missingAttributePadding = -67755 + case invalidAttributeRandom = -67756 + case missingAttributeRandom = -67757 + case invalidAttributeSeed = -67758 + case missingAttributeSeed = -67759 + case invalidAttributePassphrase = -67760 + case missingAttributePassphrase = -67761 + case invalidAttributeKeyLength = -67762 + case missingAttributeKeyLength = -67763 + case invalidAttributeBlockSize = -67764 + case missingAttributeBlockSize = -67765 + case invalidAttributeOutputSize = -67766 + case missingAttributeOutputSize = -67767 + case invalidAttributeRounds = -67768 + case missingAttributeRounds = -67769 + case invalidAlgorithmParms = -67770 + case missingAlgorithmParms = -67771 + case invalidAttributeLabel = -67772 + case missingAttributeLabel = -67773 + case invalidAttributeKeyType = -67774 + case missingAttributeKeyType = -67775 + case invalidAttributeMode = -67776 + case missingAttributeMode = -67777 + case invalidAttributeEffectiveBits = -67778 + case missingAttributeEffectiveBits = -67779 + case invalidAttributeStartDate = -67780 + case missingAttributeStartDate = -67781 + case invalidAttributeEndDate = -67782 + case missingAttributeEndDate = -67783 + case invalidAttributeVersion = -67784 + case missingAttributeVersion = -67785 + case invalidAttributePrime = -67786 + case missingAttributePrime = -67787 + case invalidAttributeBase = -67788 + case missingAttributeBase = -67789 + case invalidAttributeSubprime = -67790 + case missingAttributeSubprime = -67791 + case invalidAttributeIterationCount = -67792 + case missingAttributeIterationCount = -67793 + case invalidAttributeDLDBHandle = -67794 + case missingAttributeDLDBHandle = -67795 + case invalidAttributeAccessCredentials = -67796 + case missingAttributeAccessCredentials = -67797 + case invalidAttributePublicKeyFormat = -67798 + case missingAttributePublicKeyFormat = -67799 + case invalidAttributePrivateKeyFormat = -67800 + case missingAttributePrivateKeyFormat = -67801 + case invalidAttributeSymmetricKeyFormat = -67802 + case missingAttributeSymmetricKeyFormat = -67803 + case invalidAttributeWrappedKeyFormat = -67804 + case missingAttributeWrappedKeyFormat = -67805 + case stagedOperationInProgress = -67806 + case stagedOperationNotStarted = -67807 + case verifyFailed = -67808 + case querySizeUnknown = -67809 + case blockSizeMismatch = -67810 + case publicKeyInconsistent = -67811 + case deviceVerifyFailed = -67812 + case invalidLoginName = -67813 + case alreadyLoggedIn = -67814 + case invalidDigestAlgorithm = -67815 + case invalidCRLGroup = -67816 + case certificateCannotOperate = -67817 + case certificateExpired = -67818 + case certificateNotValidYet = -67819 + case certificateRevoked = -67820 + case certificateSuspended = -67821 + case insufficientCredentials = -67822 + case invalidAction = -67823 + case invalidAuthority = -67824 + case verifyActionFailed = -67825 + case invalidCertAuthority = -67826 + case invaldCRLAuthority = -67827 + case invalidCRLEncoding = -67828 + case invalidCRLType = -67829 + case invalidCRL = -67830 + case invalidFormType = -67831 + case invalidID = -67832 + case invalidIdentifier = -67833 + case invalidIndex = -67834 + case invalidPolicyIdentifiers = -67835 + case invalidTimeString = -67836 + case invalidReason = -67837 + case invalidRequestInputs = -67838 + case invalidResponseVector = -67839 + case invalidStopOnPolicy = -67840 + case invalidTuple = -67841 + case multipleValuesUnsupported = -67842 + case notTrusted = -67843 + case noDefaultAuthority = -67844 + case rejectedForm = -67845 + case requestLost = -67846 + case requestRejected = -67847 + case unsupportedAddressType = -67848 + case unsupportedService = -67849 + case invalidTupleGroup = -67850 + case invalidBaseACLs = -67851 + case invalidTupleCredendtials = -67852 + case invalidEncoding = -67853 + case invalidValidityPeriod = -67854 + case invalidRequestor = -67855 + case requestDescriptor = -67856 + case invalidBundleInfo = -67857 + case invalidCRLIndex = -67858 + case noFieldValues = -67859 + case unsupportedFieldFormat = -67860 + case unsupportedIndexInfo = -67861 + case unsupportedLocality = -67862 + case unsupportedNumAttributes = -67863 + case unsupportedNumIndexes = -67864 + case unsupportedNumRecordTypes = -67865 + case fieldSpecifiedMultiple = -67866 + case incompatibleFieldFormat = -67867 + case invalidParsingModule = -67868 + case databaseLocked = -67869 + case datastoreIsOpen = -67870 + case missingValue = -67871 + case unsupportedQueryLimits = -67872 + case unsupportedNumSelectionPreds = -67873 + case unsupportedOperator = -67874 + case invalidDBLocation = -67875 + case invalidAccessRequest = -67876 + case invalidIndexInfo = -67877 + case invalidNewOwner = -67878 + case invalidModifyMode = -67879 + case missingRequiredExtension = -67880 + case extendedKeyUsageNotCritical = -67881 + case timestampMissing = -67882 + case timestampInvalid = -67883 + case timestampNotTrusted = -67884 + case timestampServiceNotAvailable = -67885 + case timestampBadAlg = -67886 + case timestampBadRequest = -67887 + case timestampBadDataFormat = -67888 + case timestampTimeNotAvailable = -67889 + case timestampUnacceptedPolicy = -67890 + case timestampUnacceptedExtension = -67891 + case timestampAddInfoNotAvailable = -67892 + case timestampSystemFailure = -67893 + case signingTimeMissing = -67894 + case timestampRejection = -67895 + case timestampWaiting = -67896 + case timestampRevocationWarning = -67897 + case timestampRevocationNotification = -67898 + case unexpectedError = -99999 +} + +extension CelyStorageError: RawRepresentable, CustomStringConvertible { + public init(status: OSStatus) { + if let mappedStatus = CelyStorageError(rawValue: status) { + self = mappedStatus + } else { + self = .unexpectedError + } + } + + public var description: String { + switch self { + case .noError: + return "No error." + case .unimplemented: + return "Function or operation not implemented." + case .diskFull: + return "The disk is full." + case .io: + return "I/O error (bummers)" + case .opWr: + return "file already open with with write permission" + case .param: + return "One or more parameters passed to a function were not valid." + case .wrPerm: + return "write permissions error" + case .allocate: + return "Failed to allocate memory." + case .userCanceled: + return "User canceled the operation." + case .badReq: + return "Bad parameter or invalid state for operation." + case .internalComponent: + return "" + case .notAvailable: + return "No keychain is available. You may need to restart your computer." + case .readOnly: + return "This keychain cannot be modified." + case .authFailed: + return "The user name or passphrase you entered is not correct." + case .noSuchKeychain: + return "The specified keychain could not be found." + case .invalidKeychain: + return "The specified keychain is not a valid keychain file." + case .duplicateKeychain: + return "A keychain with the same name already exists." + case .duplicateCallback: + return "The specified callback function is already installed." + case .invalidCallback: + return "The specified callback function is not valid." + case .duplicateItem: + return "The specified item already exists in the keychain." + case .itemNotFound: + return "The specified item could not be found in the keychain." + case .bufferTooSmall: + return "There is not enough memory available to use the specified item." + case .dataTooLarge: + return "This item contains information which is too large or in a format that cannot be displayed." + case .noSuchAttr: + return "The specified attribute does not exist." + case .invalidItemRef: + return "The specified item is no longer valid. It may have been deleted from the keychain." + case .invalidSearchRef: + return "Unable to search the current keychain." + case .noSuchClass: + return "The specified item does not appear to be a valid keychain item." + case .noDefaultKeychain: + return "A default keychain could not be found." + case .interactionNotAllowed: + return "User interaction is not allowed." + case .readOnlyAttr: + return "The specified attribute could not be modified." + case .wrongSecVersion: + return "This keychain was created by a different version of the system software and cannot be opened." + case .keySizeNotAllowed: + return "This item specifies a key size which is too large." + case .noStorageModule: + return "A required component (data storage module) could not be loaded. You may need to restart your computer." + case .noCertificateModule: + return "A required component (certificate module) could not be loaded. You may need to restart your computer." + case .noPolicyModule: + return "A required component (policy module) could not be loaded. You may need to restart your computer." + case .interactionRequired: + return "User interaction is required, but is currently not allowed." + case .dataNotAvailable: + return "The contents of this item cannot be retrieved." + case .dataNotModifiable: + return "The contents of this item cannot be modified." + case .createChainFailed: + return "One or more certificates required to validate this certificate cannot be found." + case .invalidPrefsDomain: + return "The specified preferences domain is not valid." + case .inDarkWake: + return "In dark wake, no UI possible" + case .aclNotSimple: + return "The specified access control list is not in standard (simple) form." + case .policyNotFound: + return "The specified policy cannot be found." + case .invalidTrustSetting: + return "The specified trust setting is invalid." + case .noAccessForItem: + return "The specified item has no access control." + case .invalidOwnerEdit: + return "Invalid attempt to change the owner of this item." + case .trustNotAvailable: + return "No trust results are available." + case .unsupportedFormat: + return "Import/Export format unsupported." + case .unknownFormat: + return "Unknown format in import." + case .keyIsSensitive: + return "Key material must be wrapped for export." + case .multiplePrivKeys: + return "An attempt was made to import multiple private keys." + case .passphraseRequired: + return "Passphrase is required for import/export." + case .invalidPasswordRef: + return "The password reference was invalid." + case .invalidTrustSettings: + return "The Trust Settings Record was corrupted." + case .noTrustSettings: + return "No Trust Settings were found." + case .pkcs12VerifyFailure: + return "MAC verification failed during PKCS12 import (wrong password?)" + case .invalidCertificate: + return "This certificate could not be decoded." + case .notSigner: + return "A certificate was not signed by its proposed parent." + case .policyDenied: + return "The certificate chain was not trusted due to a policy not accepting it." + case .invalidKey: + return "The provided key material was not valid." + case .decode: + return "Unable to decode the provided data." + case .internal: + return "An internal error occurred in the Security framework." + case .unsupportedAlgorithm: + return "An unsupported algorithm was encountered." + case .unsupportedOperation: + return "The operation you requested is not supported by this key." + case .unsupportedPadding: + return "The padding you requested is not supported." + case .itemInvalidKey: + return "A string key in dictionary is not one of the supported keys." + case .itemInvalidKeyType: + return "A key in a dictionary is neither a CFStringRef nor a CFNumberRef." + case .itemInvalidValue: + return "A value in a dictionary is an invalid (or unsupported) CF type." + case .itemClassMissing: + return "No kSecItemClass key was specified in a dictionary." + case .itemMatchUnsupported: + return "The caller passed one or more kSecMatch keys to a function which does not support matches." + case .useItemListUnsupported: + return "The caller passed in a kSecUseItemList key to a function which does not support it." + case .useKeychainUnsupported: + return "The caller passed in a kSecUseKeychain key to a function which does not support it." + case .useKeychainListUnsupported: + return "The caller passed in a kSecUseKeychainList key to a function which does not support it." + case .returnDataUnsupported: + return "The caller passed in a kSecReturnData key to a function which does not support it." + case .returnAttributesUnsupported: + return "The caller passed in a kSecReturnAttributes key to a function which does not support it." + case .returnRefUnsupported: + return "The caller passed in a kSecReturnRef key to a function which does not support it." + case .returnPersitentRefUnsupported: + return "The caller passed in a kSecReturnPersistentRef key to a function which does not support it." + case .valueRefUnsupported: + return "The caller passed in a kSecValueRef key to a function which does not support it." + case .valuePersistentRefUnsupported: + return "The caller passed in a kSecValuePersistentRef key to a function which does not support it." + case .returnMissingPointer: + return "The caller passed asked for something to be returned but did not pass in a result pointer." + case .matchLimitUnsupported: + return "The caller passed in a kSecMatchLimit key to a call which does not support limits." + case .itemIllegalQuery: + return "The caller passed in a query which contained too many keys." + case .waitForCallback: + return "This operation is incomplete, until the callback is invoked (not an error)." + case .missingEntitlement: + return "Internal error when a required entitlement isn't present, client has neither application-identifier nor keychain-access-groups entitlements." + case .upgradePending: + return "Error returned if keychain database needs a schema migration but the device is locked, clients should wait for a device unlock notification and retry the command." + case .mpSignatureInvalid: + return "Signature invalid on MP message" + case .otrTooOld: + return "Message is too old to use" + case .otrIDTooNew: + return "Key ID is too new to use! Message from the future?" + case .serviceNotAvailable: + return "The required service is not available." + case .insufficientClientID: + return "The client ID is not correct." + case .deviceReset: + return "A device reset has occurred." + case .deviceFailed: + return "A device failure has occurred." + case .appleAddAppACLSubject: + return "Adding an application ACL subject failed." + case .applePublicKeyIncomplete: + return "The public key is incomplete." + case .appleSignatureMismatch: + return "A signature mismatch has occurred." + case .appleInvalidKeyStartDate: + return "The specified key has an invalid start date." + case .appleInvalidKeyEndDate: + return "The specified key has an invalid end date." + case .conversionError: + return "A conversion error has occurred." + case .appleSSLv2Rollback: + return "A SSLv2 rollback error has occurred." + case .quotaExceeded: + return "The quota was exceeded." + case .fileTooBig: + return "The file is too big." + case .invalidDatabaseBlob: + return "The specified database has an invalid blob." + case .invalidKeyBlob: + return "The specified database has an invalid key blob." + case .incompatibleDatabaseBlob: + return "The specified database has an incompatible blob." + case .incompatibleKeyBlob: + return "The specified database has an incompatible key blob." + case .hostNameMismatch: + return "A host name mismatch has occurred." + case .unknownCriticalExtensionFlag: + return "There is an unknown critical extension flag." + case .noBasicConstraints: + return "No basic constraints were found." + case .noBasicConstraintsCA: + return "No basic CA constraints were found." + case .invalidAuthorityKeyID: + return "The authority key ID is not valid." + case .invalidSubjectKeyID: + return "The subject key ID is not valid." + case .invalidKeyUsageForPolicy: + return "The key usage is not valid for the specified policy." + case .invalidExtendedKeyUsage: + return "The extended key usage is not valid." + case .invalidIDLinkage: + return "The ID linkage is not valid." + case .pathLengthConstraintExceeded: + return "The path length constraint was exceeded." + case .invalidRoot: + return "The root or anchor certificate is not valid." + case .crlExpired: + return "The CRL has expired." + case .crlNotValidYet: + return "The CRL is not yet valid." + case .crlNotFound: + return "The CRL was not found." + case .crlServerDown: + return "The CRL server is down." + case .crlBadURI: + return "The CRL has a bad Uniform Resource Identifier." + case .unknownCertExtension: + return "An unknown certificate extension was encountered." + case .unknownCRLExtension: + return "An unknown CRL extension was encountered." + case .crlNotTrusted: + return "The CRL is not trusted." + case .crlPolicyFailed: + return "The CRL policy failed." + case .idpFailure: + return "The issuing distribution point was not valid." + case .smimeEmailAddressesNotFound: + return "An email address mismatch was encountered." + case .smimeBadExtendedKeyUsage: + return "The appropriate extended key usage for SMIME was not found." + case .smimeBadKeyUsage: + return "The key usage is not compatible with SMIME." + case .smimeKeyUsageNotCritical: + return "The key usage extension is not marked as critical." + case .smimeNoEmailAddress: + return "No email address was found in the certificate." + case .smimeSubjAltNameNotCritical: + return "The subject alternative name extension is not marked as critical." + case .sslBadExtendedKeyUsage: + return "The appropriate extended key usage for SSL was not found." + case .ocspBadResponse: + return "The OCSP response was incorrect or could not be parsed." + case .ocspBadRequest: + return "The OCSP request was incorrect or could not be parsed." + case .ocspUnavailable: + return "OCSP service is unavailable." + case .ocspStatusUnrecognized: + return "The OCSP server did not recognize this certificate." + case .endOfData: + return "An end-of-data was detected." + case .incompleteCertRevocationCheck: + return "An incomplete certificate revocation check occurred." + case .networkFailure: + return "A network failure occurred." + case .ocspNotTrustedToAnchor: + return "The OCSP response was not trusted to a root or anchor certificate." + case .recordModified: + return "The record was modified." + case .ocspSignatureError: + return "The OCSP response had an invalid signature." + case .ocspNoSigner: + return "The OCSP response had no signer." + case .ocspResponderMalformedReq: + return "The OCSP responder was given a malformed request." + case .ocspResponderInternalError: + return "The OCSP responder encountered an internal error." + case .ocspResponderTryLater: + return "The OCSP responder is busy, try again later." + case .ocspResponderSignatureRequired: + return "The OCSP responder requires a signature." + case .ocspResponderUnauthorized: + return "The OCSP responder rejected this request as unauthorized." + case .ocspResponseNonceMismatch: + return "The OCSP response nonce did not match the request." + case .codeSigningBadCertChainLength: + return "Code signing encountered an incorrect certificate chain length." + case .codeSigningNoBasicConstraints: + return "Code signing found no basic constraints." + case .codeSigningBadPathLengthConstraint: + return "Code signing encountered an incorrect path length constraint." + case .codeSigningNoExtendedKeyUsage: + return "Code signing found no extended key usage." + case .codeSigningDevelopment: + return "Code signing indicated use of a development-only certificate." + case .resourceSignBadCertChainLength: + return "Resource signing has encountered an incorrect certificate chain length." + case .resourceSignBadExtKeyUsage: + return "Resource signing has encountered an error in the extended key usage." + case .trustSettingDeny: + return "The trust setting for this policy was set to Deny." + case .invalidSubjectName: + return "An invalid certificate subject name was encountered." + case .unknownQualifiedCertStatement: + return "An unknown qualified certificate statement was encountered." + case .mobileMeRequestQueued: + return "The MobileMe request will be sent during the next connection." + case .mobileMeRequestRedirected: + return "The MobileMe request was redirected." + case .mobileMeServerError: + return "A MobileMe server error occurred." + case .mobileMeServerNotAvailable: + return "The MobileMe server is not available." + case .mobileMeServerAlreadyExists: + return "The MobileMe server reported that the item already exists." + case .mobileMeServerServiceErr: + return "A MobileMe service error has occurred." + case .mobileMeRequestAlreadyPending: + return "A MobileMe request is already pending." + case .mobileMeNoRequestPending: + return "MobileMe has no request pending." + case .mobileMeCSRVerifyFailure: + return "A MobileMe CSR verification failure has occurred." + case .mobileMeFailedConsistencyCheck: + return "MobileMe has found a failed consistency check." + case .notInitialized: + return "A function was called without initializing CSSM." + case .invalidHandleUsage: + return "The CSSM handle does not match with the service type." + case .pvcReferentNotFound: + return "A reference to the calling module was not found in the list of authorized callers." + case .functionIntegrityFail: + return "A function address was not within the verified module." + case .internalError: + return "An internal error has occurred." + case .memoryError: + return "A memory error has occurred." + case .invalidData: + return "Invalid data was encountered." + case .mdsError: + return "A Module Directory Service error has occurred." + case .invalidPointer: + return "An invalid pointer was encountered." + case .selfCheckFailed: + return "Self-check has failed." + case .functionFailed: + return "A function has failed." + case .moduleManifestVerifyFailed: + return "A module manifest verification failure has occurred." + case .invalidGUID: + return "An invalid GUID was encountered." + case .invalidHandle: + return "An invalid handle was encountered." + case .invalidDBList: + return "An invalid DB list was encountered." + case .invalidPassthroughID: + return "An invalid passthrough ID was encountered." + case .invalidNetworkAddress: + return "An invalid network address was encountered." + case .crlAlreadySigned: + return "The certificate revocation list is already signed." + case .invalidNumberOfFields: + return "An invalid number of fields were encountered." + case .verificationFailure: + return "A verification failure occurred." + case .unknownTag: + return "An unknown tag was encountered." + case .invalidSignature: + return "An invalid signature was encountered." + case .invalidName: + return "An invalid name was encountered." + case .invalidCertificateRef: + return "An invalid certificate reference was encountered." + case .invalidCertificateGroup: + return "An invalid certificate group was encountered." + case .tagNotFound: + return "The specified tag was not found." + case .invalidQuery: + return "The specified query was not valid." + case .invalidValue: + return "An invalid value was detected." + case .callbackFailed: + return "A callback has failed." + case .aclDeleteFailed: + return "An ACL delete operation has failed." + case .aclReplaceFailed: + return "An ACL replace operation has failed." + case .aclAddFailed: + return "An ACL add operation has failed." + case .aclChangeFailed: + return "An ACL change operation has failed." + case .invalidAccessCredentials: + return "Invalid access credentials were encountered." + case .invalidRecord: + return "An invalid record was encountered." + case .invalidACL: + return "An invalid ACL was encountered." + case .invalidSampleValue: + return "An invalid sample value was encountered." + case .incompatibleVersion: + return "An incompatible version was encountered." + case .privilegeNotGranted: + return "The privilege was not granted." + case .invalidScope: + return "An invalid scope was encountered." + case .pvcAlreadyConfigured: + return "The PVC is already configured." + case .invalidPVC: + return "An invalid PVC was encountered." + case .emmLoadFailed: + return "The EMM load has failed." + case .emmUnloadFailed: + return "The EMM unload has failed." + case .addinLoadFailed: + return "The add-in load operation has failed." + case .invalidKeyRef: + return "An invalid key was encountered." + case .invalidKeyHierarchy: + return "An invalid key hierarchy was encountered." + case .addinUnloadFailed: + return "The add-in unload operation has failed." + case .libraryReferenceNotFound: + return "A library reference was not found." + case .invalidAddinFunctionTable: + return "An invalid add-in function table was encountered." + case .invalidServiceMask: + return "An invalid service mask was encountered." + case .moduleNotLoaded: + return "A module was not loaded." + case .invalidSubServiceID: + return "An invalid subservice ID was encountered." + case .attributeNotInContext: + return "An attribute was not in the context." + case .moduleManagerInitializeFailed: + return "A module failed to initialize." + case .moduleManagerNotFound: + return "A module was not found." + case .eventNotificationCallbackNotFound: + return "An event notification callback was not found." + case .inputLengthError: + return "An input length error was encountered." + case .outputLengthError: + return "An output length error was encountered." + case .privilegeNotSupported: + return "The privilege is not supported." + case .deviceError: + return "A device error was encountered." + case .attachHandleBusy: + return "The CSP handle was busy." + case .notLoggedIn: + return "You are not logged in." + case .algorithmMismatch: + return "An algorithm mismatch was encountered." + case .keyUsageIncorrect: + return "The key usage is incorrect." + case .keyBlobTypeIncorrect: + return "The key blob type is incorrect." + case .keyHeaderInconsistent: + return "The key header is inconsistent." + case .unsupportedKeyFormat: + return "The key header format is not supported." + case .unsupportedKeySize: + return "The key size is not supported." + case .invalidKeyUsageMask: + return "The key usage mask is not valid." + case .unsupportedKeyUsageMask: + return "The key usage mask is not supported." + case .invalidKeyAttributeMask: + return "The key attribute mask is not valid." + case .unsupportedKeyAttributeMask: + return "The key attribute mask is not supported." + case .invalidKeyLabel: + return "The key label is not valid." + case .unsupportedKeyLabel: + return "The key label is not supported." + case .invalidKeyFormat: + return "The key format is not valid." + case .unsupportedVectorOfBuffers: + return "The vector of buffers is not supported." + case .invalidInputVector: + return "The input vector is not valid." + case .invalidOutputVector: + return "The output vector is not valid." + case .invalidContext: + return "An invalid context was encountered." + case .invalidAlgorithm: + return "An invalid algorithm was encountered." + case .invalidAttributeKey: + return "A key attribute was not valid." + case .missingAttributeKey: + return "A key attribute was missing." + case .invalidAttributeInitVector: + return "An init vector attribute was not valid." + case .missingAttributeInitVector: + return "An init vector attribute was missing." + case .invalidAttributeSalt: + return "A salt attribute was not valid." + case .missingAttributeSalt: + return "A salt attribute was missing." + case .invalidAttributePadding: + return "A padding attribute was not valid." + case .missingAttributePadding: + return "A padding attribute was missing." + case .invalidAttributeRandom: + return "A random number attribute was not valid." + case .missingAttributeRandom: + return "A random number attribute was missing." + case .invalidAttributeSeed: + return "A seed attribute was not valid." + case .missingAttributeSeed: + return "A seed attribute was missing." + case .invalidAttributePassphrase: + return "A passphrase attribute was not valid." + case .missingAttributePassphrase: + return "A passphrase attribute was missing." + case .invalidAttributeKeyLength: + return "A key length attribute was not valid." + case .missingAttributeKeyLength: + return "A key length attribute was missing." + case .invalidAttributeBlockSize: + return "A block size attribute was not valid." + case .missingAttributeBlockSize: + return "A block size attribute was missing." + case .invalidAttributeOutputSize: + return "An output size attribute was not valid." + case .missingAttributeOutputSize: + return "An output size attribute was missing." + case .invalidAttributeRounds: + return "The number of rounds attribute was not valid." + case .missingAttributeRounds: + return "The number of rounds attribute was missing." + case .invalidAlgorithmParms: + return "An algorithm parameters attribute was not valid." + case .missingAlgorithmParms: + return "An algorithm parameters attribute was missing." + case .invalidAttributeLabel: + return "A label attribute was not valid." + case .missingAttributeLabel: + return "A label attribute was missing." + case .invalidAttributeKeyType: + return "A key type attribute was not valid." + case .missingAttributeKeyType: + return "A key type attribute was missing." + case .invalidAttributeMode: + return "A mode attribute was not valid." + case .missingAttributeMode: + return "A mode attribute was missing." + case .invalidAttributeEffectiveBits: + return "An effective bits attribute was not valid." + case .missingAttributeEffectiveBits: + return "An effective bits attribute was missing." + case .invalidAttributeStartDate: + return "A start date attribute was not valid." + case .missingAttributeStartDate: + return "A start date attribute was missing." + case .invalidAttributeEndDate: + return "An end date attribute was not valid." + case .missingAttributeEndDate: + return "An end date attribute was missing." + case .invalidAttributeVersion: + return "A version attribute was not valid." + case .missingAttributeVersion: + return "A version attribute was missing." + case .invalidAttributePrime: + return "A prime attribute was not valid." + case .missingAttributePrime: + return "A prime attribute was missing." + case .invalidAttributeBase: + return "A base attribute was not valid." + case .missingAttributeBase: + return "A base attribute was missing." + case .invalidAttributeSubprime: + return "A subprime attribute was not valid." + case .missingAttributeSubprime: + return "A subprime attribute was missing." + case .invalidAttributeIterationCount: + return "An iteration count attribute was not valid." + case .missingAttributeIterationCount: + return "An iteration count attribute was missing." + case .invalidAttributeDLDBHandle: + return "A database handle attribute was not valid." + case .missingAttributeDLDBHandle: + return "A database handle attribute was missing." + case .invalidAttributeAccessCredentials: + return "An access credentials attribute was not valid." + case .missingAttributeAccessCredentials: + return "An access credentials attribute was missing." + case .invalidAttributePublicKeyFormat: + return "A public key format attribute was not valid." + case .missingAttributePublicKeyFormat: + return "A public key format attribute was missing." + case .invalidAttributePrivateKeyFormat: + return "A private key format attribute was not valid." + case .missingAttributePrivateKeyFormat: + return "A private key format attribute was missing." + case .invalidAttributeSymmetricKeyFormat: + return "A symmetric key format attribute was not valid." + case .missingAttributeSymmetricKeyFormat: + return "A symmetric key format attribute was missing." + case .invalidAttributeWrappedKeyFormat: + return "A wrapped key format attribute was not valid." + case .missingAttributeWrappedKeyFormat: + return "A wrapped key format attribute was missing." + case .stagedOperationInProgress: + return "A staged operation is in progress." + case .stagedOperationNotStarted: + return "A staged operation was not started." + case .verifyFailed: + return "A cryptographic verification failure has occurred." + case .querySizeUnknown: + return "The query size is unknown." + case .blockSizeMismatch: + return "A block size mismatch occurred." + case .publicKeyInconsistent: + return "The public key was inconsistent." + case .deviceVerifyFailed: + return "A device verification failure has occurred." + case .invalidLoginName: + return "An invalid login name was detected." + case .alreadyLoggedIn: + return "The user is already logged in." + case .invalidDigestAlgorithm: + return "An invalid digest algorithm was detected." + case .invalidCRLGroup: + return "An invalid CRL group was detected." + case .certificateCannotOperate: + return "The certificate cannot operate." + case .certificateExpired: + return "An expired certificate was detected." + case .certificateNotValidYet: + return "The certificate is not yet valid." + case .certificateRevoked: + return "The certificate was revoked." + case .certificateSuspended: + return "The certificate was suspended." + case .insufficientCredentials: + return "Insufficient credentials were detected." + case .invalidAction: + return "The action was not valid." + case .invalidAuthority: + return "The authority was not valid." + case .verifyActionFailed: + return "A verify action has failed." + case .invalidCertAuthority: + return "The certificate authority was not valid." + case .invaldCRLAuthority: + return "The CRL authority was not valid." + case .invalidCRLEncoding: + return "The CRL encoding was not valid." + case .invalidCRLType: + return "The CRL type was not valid." + case .invalidCRL: + return "The CRL was not valid." + case .invalidFormType: + return "The form type was not valid." + case .invalidID: + return "The ID was not valid." + case .invalidIdentifier: + return "The identifier was not valid." + case .invalidIndex: + return "The index was not valid." + case .invalidPolicyIdentifiers: + return "The policy identifiers are not valid." + case .invalidTimeString: + return "The time specified was not valid." + case .invalidReason: + return "The trust policy reason was not valid." + case .invalidRequestInputs: + return "The request inputs are not valid." + case .invalidResponseVector: + return "The response vector was not valid." + case .invalidStopOnPolicy: + return "The stop-on policy was not valid." + case .invalidTuple: + return "The tuple was not valid." + case .multipleValuesUnsupported: + return "Multiple values are not supported." + case .notTrusted: + return "The trust policy was not trusted." + case .noDefaultAuthority: + return "No default authority was detected." + case .rejectedForm: + return "The trust policy had a rejected form." + case .requestLost: + return "The request was lost." + case .requestRejected: + return "The request was rejected." + case .unsupportedAddressType: + return "The address type is not supported." + case .unsupportedService: + return "The service is not supported." + case .invalidTupleGroup: + return "The tuple group was not valid." + case .invalidBaseACLs: + return "The base ACLs are not valid." + case .invalidTupleCredendtials: + return "The tuple credentials are not valid." + case .invalidEncoding: + return "The encoding was not valid." + case .invalidValidityPeriod: + return "The validity period was not valid." + case .invalidRequestor: + return "The requestor was not valid." + case .requestDescriptor: + return "The request descriptor was not valid." + case .invalidBundleInfo: + return "The bundle information was not valid." + case .invalidCRLIndex: + return "The CRL index was not valid." + case .noFieldValues: + return "No field values were detected." + case .unsupportedFieldFormat: + return "The field format is not supported." + case .unsupportedIndexInfo: + return "The index information is not supported." + case .unsupportedLocality: + return "The locality is not supported." + case .unsupportedNumAttributes: + return "The number of attributes is not supported." + case .unsupportedNumIndexes: + return "The number of indexes is not supported." + case .unsupportedNumRecordTypes: + return "The number of record types is not supported." + case .fieldSpecifiedMultiple: + return "Too many fields were specified." + case .incompatibleFieldFormat: + return "The field format was incompatible." + case .invalidParsingModule: + return "The parsing module was not valid." + case .databaseLocked: + return "The database is locked." + case .datastoreIsOpen: + return "The data store is open." + case .missingValue: + return "A missing value was detected." + case .unsupportedQueryLimits: + return "The query limits are not supported." + case .unsupportedNumSelectionPreds: + return "The number of selection predicates is not supported." + case .unsupportedOperator: + return "The operator is not supported." + case .invalidDBLocation: + return "The database location is not valid." + case .invalidAccessRequest: + return "The access request is not valid." + case .invalidIndexInfo: + return "The index information is not valid." + case .invalidNewOwner: + return "The new owner is not valid." + case .invalidModifyMode: + return "The modify mode is not valid." + case .missingRequiredExtension: + return "A required certificate extension is missing." + case .extendedKeyUsageNotCritical: + return "The extended key usage extension was not marked critical." + case .timestampMissing: + return "A timestamp was expected but was not found." + case .timestampInvalid: + return "The timestamp was not valid." + case .timestampNotTrusted: + return "The timestamp was not trusted." + case .timestampServiceNotAvailable: + return "The timestamp service is not available." + case .timestampBadAlg: + return "An unrecognized or unsupported Algorithm Identifier in timestamp." + case .timestampBadRequest: + return "The timestamp transaction is not permitted or supported." + case .timestampBadDataFormat: + return "The timestamp data submitted has the wrong format." + case .timestampTimeNotAvailable: + return "The time source for the Timestamp Authority is not available." + case .timestampUnacceptedPolicy: + return "The requested policy is not supported by the Timestamp Authority." + case .timestampUnacceptedExtension: + return "The requested extension is not supported by the Timestamp Authority." + case .timestampAddInfoNotAvailable: + return "The additional information requested is not available." + case .timestampSystemFailure: + return "The timestamp request cannot be handled due to system failure." + case .signingTimeMissing: + return "A signing time was expected but was not found." + case .timestampRejection: + return "A timestamp transaction was rejected." + case .timestampWaiting: + return "A timestamp transaction is waiting." + case .timestampRevocationWarning: + return "A timestamp authority revocation warning was issued." + case .timestampRevocationNotification: + return "A timestamp authority revocation notification was issued." + case .unexpectedError: + return "Unexpected error has occurred." + } + } +} + +extension CelyStorageError: CustomNSError, CaseIterable { + public static let errorDomain = "com.cely-tools.Cely.error" + + public var errorCode: Int { + return Int(rawValue) + } + + public var errorUserInfo: [String: Any] { + return [NSLocalizedDescriptionKey: description] + } +} diff --git a/Sources/Core/Storage/KeychainObject.swift b/Sources/Core/Storage/KeychainObject.swift new file mode 100644 index 0000000..ceb93a2 --- /dev/null +++ b/Sources/Core/Storage/KeychainObject.swift @@ -0,0 +1,89 @@ +// +// KeychainObject.swift +// Cely-iOS +// +// Created by Fabian Buentello on 9/30/19. +// Copyright © 2019 Fabian Buentello. All rights reserved. +// + +import Foundation + +typealias RawDictionary = AnyObject + +struct KeychainObject { + var baseQuery: [CFString: Any] = [ + kSecClass: kSecClassInternetPassword, + kSecAttrLabel: kCelySecureStoreLabel, + ] + + let account: String? + let server: String? + let value: Data? + private let accessControlOptions: [AccessControlOptions] + + init(account: String? = nil, server: String? = nil, value: Data? = nil, options: [AccessControlOptions] = []) { + self.account = account + self.server = server + self.value = value + accessControlOptions = options + } + + static func buildFromKeychain(dictionary: RawDictionary) -> KeychainObject { + return KeychainObject( + account: dictionary[kSecAttrAccount] as? String, + server: dictionary[kSecAttrServer] as? String, + value: dictionary[kSecValueData] as? Data + ) + } + + func toLookupMap() -> [CFString: Any] { + var lookupMap: [CFString: Any] = [:] + + if let account = account { + lookupMap[kSecAttrAccount] = account + } + + if let server = server { + lookupMap[kSecAttrServer] = server + } + + return baseQuery.merging(lookupMap) { _, new in new } + } + + func toGetMap() -> [CFString: Any] { + let limitQuery: [CFString: Any] = [ + kSecMatchLimit: kSecMatchLimitOne, + kSecReturnAttributes: true, + kSecReturnData: true, + ] + + let query = toLookupMap() + return query.merging(limitQuery) { _, new in new } + } + + func getAccessControlOptions() -> CFString { + let isThisDeviceOnly = accessControlOptions.contains(.thisDeviceOnly) + return isThisDeviceOnly ? kSecAttrAccessibleWhenUnlockedThisDeviceOnly : kSecAttrAccessibleWhenUnlocked + } + + func toSetMap(withValue: Bool) -> [CFString: Any] { + var userMap = toLookupMap() + + if let value = value, withValue { + userMap[kSecValueData] = value + } + + if accessControlOptions.contains(.biometricsIfPossible) { + let options = getAccessControlOptions() + var error: Unmanaged? + let access = SecAccessControlCreateWithFlags(nil, + options, + .userPresence, + &error) + + userMap[kSecAttrAccessControl] = access + } + + return baseQuery.merging(userMap) { _, new in new } + } +} diff --git a/Sources/LocksmithError.swift b/Sources/LocksmithError.swift deleted file mode 100755 index a62ba88..0000000 --- a/Sources/LocksmithError.swift +++ /dev/null @@ -1,55 +0,0 @@ -// -// LocksmithError.swift -// Cely -// -// Created by Matthew Palmer on 7/31/16. -// Copyright © 2016 Matthew Palmer. All rights reserved. -// - -import Foundation - -// MARK: Locksmith Error - -public enum LocksmithError: String, Error { - case allocate = "Failed to allocate memory." - case authFailed = "Authorization/Authentication failed." - case decode = "Unable to decode the provided data." - case duplicate = "The item already exists." - case interactionNotAllowed = "Interaction with the Security Server is not allowed." - case noError = "No error." - case notAvailable = "No trust results are available." - case notFound = "The item cannot be found." - case param = "One or more parameters passed to the function were not valid." - case requestNotSet = "The request was not set" - case typeNotFound = "The type was not found" - case unableToClear = "Unable to clear the keychain" - case undefined = "An undefined error occurred" - case unimplemented = "Function or operation not implemented." - - public init(fromStatusCode code: Int) { - switch code { - case Int(errSecSuccess): - self = .noError - case Int(errSecAllocate): - self = .allocate - case Int(errSecAuthFailed): - self = .authFailed - case Int(errSecDecode): - self = .decode - case Int(errSecDuplicateItem): - self = .duplicate - case Int(errSecInteractionNotAllowed): - self = .interactionNotAllowed - case Int(errSecItemNotFound): - self = .notFound - case Int(errSecNotAvailable): - self = .notAvailable - case Int(errSecParam): - self = .param - case Int(errSecUnimplemented): - self = .unimplemented - default: - self = .undefined - } - } -} diff --git a/Sources/Cely.h b/Sources/Supporting Files/Cely.h similarity index 100% rename from Sources/Cely.h rename to Sources/Supporting Files/Cely.h diff --git a/Sources/CelyLoginViewController.swift b/Sources/Supporting Files/CelyLoginViewController.swift similarity index 100% rename from Sources/CelyLoginViewController.swift rename to Sources/Supporting Files/CelyLoginViewController.swift diff --git a/Sources/Info-tvOS.plist b/Sources/Supporting Files/Info-tvOS.plist similarity index 100% rename from Sources/Info-tvOS.plist rename to Sources/Supporting Files/Info-tvOS.plist diff --git a/Sources/Info.plist b/Sources/Supporting Files/Info.plist similarity index 100% rename from Sources/Info.plist rename to Sources/Supporting Files/Info.plist diff --git a/Tests/CelyCredentialStoreTests.swift b/Tests/CelyCredentialStoreTests.swift new file mode 100644 index 0000000..50533c7 --- /dev/null +++ b/Tests/CelyCredentialStoreTests.swift @@ -0,0 +1,99 @@ +// +// CelyCredentialStoreTests.swift +// CelyTests-iOS +// +// Created by Fabian Buentello on 9/29/19. +// Copyright © 2019 Fabian Buentello. All rights reserved. +// + +@testable import Cely +import XCTest + +func == (lhs: KeychainObject, rhs: KeychainObject) -> Bool { + return lhs.account == rhs.account && lhs.server == rhs.server +} + +class MockKeychainStore: KeychainProtocol { + var mockStore: [KeychainObject] = [] + + func get(query: KeychainObject) -> Result { + let foundResult = mockStore.first { $0 == query } + + guard let item = foundResult else { + return .failure(CelyStorageError.itemNotFound) + } + + return .success(KeychainObject(account: item.account, server: item.server, value: item.value)) + } + + func set(query: KeychainObject) throws { + mockStore.append(query) + } + + func delete(query: KeychainObject) throws { + mockStore.removeAll { $0 == query } + } +} + +class CelyCredentialStoreTests: XCTestCase { + func testSettingGettingCredentials() { + let store = CelyCredentialStore(keychain: MockKeychainStore()) + let result = store.set(username: "valid-username", password: "valid-password", server: "someserver.com") + if case let .failure(error) = result { + XCTFail("Failed to store credentials in store: \(error)") + } + + let foundResult = store.get() + + switch foundResult { + case let .success(creds): + XCTAssert(creds.password == "valid-password", "Failed to retrieve password: \(creds.password)") + XCTAssert(creds.username == "valid-username", "Failed to retrieve username: \(creds.username)") + XCTAssert(creds.server == "someserver.com", "Failed to retrieve server: \(creds.server)") + case let .failure(error): + XCTFail("Failed to retrieve credentials: \(error)") + } + } + + func testUnsetCredentialsLookup() { + try! Cely.store.set("some-fail-string", forKey: kCelyCredentialsLookupKey, securely: false, persisted: true) + let store = CelyCredentialStore(keychain: MockKeychainStore()) + let result = store.get() + guard case let .failure(error) = result else { + return XCTFail("Should have received an error since credential lookup is not a dictionary") + } + + print(error.localizedDescription) + XCTAssert(error.localizedDescription == CelyStorageError.unexpectedError.description, "Should have received an `.unexpectedError`: \(error)") + } + + func testMissingCredentialsLookup() { + try! Cely.store.set([:], forKey: kCelyCredentialsLookupKey, securely: false, persisted: true) + let store = CelyCredentialStore(keychain: MockKeychainStore()) + let result = store.get() + guard case let .failure(error) = result else { + return XCTFail("Should have received an error") + } + + print(error.localizedDescription) + XCTAssert(error.localizedDescription == CelyStorageError.itemNotFound.description, "Should have received an `.itemNotFound`: \(error)") + } + + func testIncludesBiometrics() { + let object = KeychainObject(account: "valid-username", server: "someserver.com", value: "valid-password".data(using: .utf8)!, options: [.biometricsIfPossible]) + let getMap = object.toSetMap(withValue: false) + let foundValue = getMap[kSecAttrAccessControl] + XCTAssert(foundValue != nil, "Failed to find biometrics flag") + let options = object.getAccessControlOptions() + XCTAssert(options == kSecAttrAccessibleWhenUnlocked, "Failed to set correct `accessibility`: \(options)") + } + + func testThisDeviceOnly() { + let object = KeychainObject(account: "valid-username", server: "someserver.com", value: "valid-password".data(using: .utf8)!, options: [.biometricsIfPossible, .thisDeviceOnly]) + let getMap = object.toSetMap(withValue: false) + let foundValue = getMap[kSecAttrAccessControl] + XCTAssert(foundValue != nil, "Failed to find biometrics flag") + let options = object.getAccessControlOptions() + XCTAssert(options == kSecAttrAccessibleWhenUnlockedThisDeviceOnly, "Failed to set correct `accessibility`: \(options)") + } +} diff --git a/Tests/CelyStorageErrorTests.swift b/Tests/CelyStorageErrorTests.swift new file mode 100644 index 0000000..ca94401 --- /dev/null +++ b/Tests/CelyStorageErrorTests.swift @@ -0,0 +1,44 @@ +// +// CelyStorageErrorTests.swift +// CelyTests-iOS +// +// Created by Fabian Buentello on 9/30/19. +// Copyright © 2019 Fabian Buentello. All rights reserved. +// + +@testable import Cely +import XCTest + +class CelyStorageErrorTests: XCTestCase { + func testDescription() {} + + func testErrorCode() { + let error = CelyStorageError(status: -4) + XCTAssert(error.errorCode == -4, "Failed to return correct error code") + } + + func testErrorUserInfo() { + let error = CelyStorageError(status: -4) + guard let userInfoDescription = error.errorUserInfo[NSLocalizedDescriptionKey] as? String else { + XCTFail("Failed to return description from `errorUserInfo`") + return + } + + XCTAssert(userInfoDescription == CelyStorageError.unimplemented.description, "Failed to return correct description") + } + + func testOSStatusValues() { + CelyStorageError.allCases.forEach { currentCase in + guard let userInfoDescription = currentCase.errorUserInfo[NSLocalizedDescriptionKey] as? String else { + XCTFail("Failed to return description from `errorUserInfo`") + return + } + XCTAssert(currentCase.description == userInfoDescription, "Failed to return correct description") + } + } + + func testInvalidOSStatusValue() { + let error = CelyStorageError(status: -12345) + XCTAssert(error.description == CelyStorageError.unexpectedError.description, "Failed to return correct description") + } +} diff --git a/Tests/CelyStorageTests.swift b/Tests/CelyStorageTests.swift index 90da82b..072275d 100644 --- a/Tests/CelyStorageTests.swift +++ b/Tests/CelyStorageTests.swift @@ -82,8 +82,6 @@ class StorageTests: XCTestCase { Dummy(key: "testFloat_secure", value: 1.058, storeSecurely: true), Dummy(key: "testInt", value: 100, storeSecurely: false), Dummy(key: "testInt_secure", value: 100, storeSecurely: true), - Dummy(key: "testNil", value: nil, storeSecurely: false), - Dummy(key: "testNil_secure", value: nil, storeSecurely: true), Dummy(key: "testArrayOfStrings", value: ["string1 success", "string2 success"], storeSecurely: false), Dummy(key: "testArrayOfStrings_secure", value: ["string1 success", "string2 success"], storeSecurely: true), Dummy(key: "testArrayOfNumbers", value: [50, 48.5, 895.5], storeSecurely: false), @@ -100,12 +98,10 @@ class StorageTests: XCTestCase { func testSavingData() { dummyData.forEach { dummy in - let success = store.set(dummy.value, forKey: dummy.key, securely: dummy.storeSecurely, persisted: dummy.persisted) - if dummy.value != nil { - let successStatus = success == StorageResult.success - XCTAssert(successStatus, dummy.failedToSet()) - } else { - XCTAssert(StorageResult.fail(.undefined) == success, "You're not supposed to be able to set nil in the storage.") + do { + try store.set(dummy.value, forKey: dummy.key, securely: dummy.storeSecurely, persisted: dummy.persisted) + } catch { + XCTFail(dummy.failedToSet()) } } } @@ -139,7 +135,7 @@ class StorageTests: XCTestCase { XCTAssert(secureCount == 5, "Did not add all entries inside of 'secureStorage': \(secureCount)") XCTAssert(storageCount == 5, "Did not add all entries inside of 'storage': \(storageCount)") - store.removeAllData() + try! store.clearStorage() secureCount = store.secureStorage.count diff --git a/Tests/CelyTests.swift b/Tests/CelyTests.swift index 6f524c9..24eeb35 100644 --- a/Tests/CelyTests.swift +++ b/Tests/CelyTests.swift @@ -24,17 +24,16 @@ class DummyStorage: CelyStorageProtocol { static var successful_setCalls = 0 static var successful_removeCalls = 0 - func set(_ value: Any?, forKey _: String, securely _: Bool = true, persisted _: Bool = false) -> StorageResult { - if value == nil { return .fail(.undefined) } + func set(_ value: Any?, forKey _: String, securely _: Bool = true, persisted _: Bool = false) throws { + if value == nil { throw CelyStorageError.param } DummyStorage.successful_setCalls += 1 - return .success } func get(_ key: String) -> Any? { return dummyStorage[key] ?? nil } - func removeAllData() { + func clearStorage() { DummyStorage.successful_removeCalls += 1 } } @@ -135,10 +134,23 @@ class CelyTests: XCTestCase { } func testSaveProperty() { - XCTAssert(Cely.save(3, forKey: "number") == StorageResult.success, "failed to save Number") - XCTAssert(Cely.save("string", forKey: "string") == StorageResult.success, "failed to save string") - XCTAssert(Cely.save(nil, forKey: "nilValue") == StorageResult.fail(.undefined), "failed to save nilValue") - XCTAssert(Cely.save("token", forKey: "tokenString") == StorageResult.success, "failed to save tokenString") + do { + try Cely.save(3, forKey: "number").get() + } catch { + XCTFail("failed to save Number") + } + + do { + try Cely.save("string", forKey: "string").get() + } catch { + XCTFail("failed to save string") + } + + do { + try Cely.save("token", forKey: "tokenString").get() + } catch { + XCTFail("failed to save tokenString") + } XCTAssert(DummyStorage.successful_setCalls == 3, "`Cely.store` and `DummyStorage` are not consistent") } diff --git a/Tests/ConstantsAndProtocolTests.swift b/Tests/ConstantsAndProtocolTests.swift index 746650f..c636798 100644 --- a/Tests/ConstantsAndProtocolTests.swift +++ b/Tests/ConstantsAndProtocolTests.swift @@ -42,13 +42,6 @@ extension ConstantsAndProtocolTests { XCTAssertEqual(fakeTextField.leftSpacer, 0, "leftSpacer was supposed to return 0") } - - func testStorageResultEquatable() { - let successResult = StorageResult.success - let failureResult = StorageResult.fail(.undefined) - - XCTAssertNotEqual(successResult, failureResult, "Results were not supposed to be equal") - } } // MARK: - Test Protocols