diff --git a/KeyClip.xcodeproj/project.pbxproj b/KeyClip.xcodeproj/project.pbxproj index c6a8e05..3943192 100644 --- a/KeyClip.xcodeproj/project.pbxproj +++ b/KeyClip.xcodeproj/project.pbxproj @@ -360,7 +360,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0800; - LastUpgradeCheck = 0820; + LastUpgradeCheck = 1020; ORGANIZATIONNAME = "Shinichiro Aska"; TargetAttributes = { 38E53E0E1D880BF800D7F626 = { @@ -374,8 +374,6 @@ }; 38E53E251D880C1300D7F626 = { CreatedOnToolsVersion = 8.0; - DevelopmentTeam = ERYSSE5R77; - ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.Keychain = { enabled = 1; @@ -408,7 +406,7 @@ }; buildConfigurationList = 6A6127191A299E6400C18E1D /* Build configuration list for PBXProject "KeyClip" */; compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; + developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, @@ -610,7 +608,6 @@ PRODUCT_BUNDLE_IDENTIFIER = "pw.aska.TestApp-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_VERSION = 3.0; }; name = Debug; }; @@ -633,7 +630,6 @@ PRODUCT_BUNDLE_IDENTIFIER = "pw.aska.TestApp-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - SWIFT_VERSION = 3.0; }; name = Release; }; @@ -646,10 +642,8 @@ CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; CODE_SIGN_ENTITLEMENTS = "TestApp Mac/TestApp Mac.entitlements"; - CODE_SIGN_IDENTITY = "-"; COMBINE_HIDPI_IMAGES = YES; DEBUG_INFORMATION_FORMAT = dwarf; - DEVELOPMENT_TEAM = ERYSSE5R77; INFOPLIST_FILE = "TestApp Mac/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.11; @@ -657,7 +651,6 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_VERSION = 3.0; }; name = Debug; }; @@ -670,11 +663,9 @@ CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; CODE_SIGN_ENTITLEMENTS = "TestApp Mac/TestApp Mac.entitlements"; - CODE_SIGN_IDENTITY = "-"; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = ERYSSE5R77; INFOPLIST_FILE = "TestApp Mac/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.11; @@ -682,7 +673,6 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - SWIFT_VERSION = 3.0; }; name = Release; }; @@ -690,18 +680,27 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; @@ -730,6 +729,7 @@ ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -740,18 +740,27 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; @@ -771,6 +780,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; + SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; @@ -781,6 +791,7 @@ 6A6127361A299E6400C18E1D /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; @@ -797,13 +808,13 @@ PROVISIONING_PROFILE_SPECIFIER = ""; SDKROOT = iphoneos; SKIP_INSTALL = YES; - SWIFT_VERSION = 3.0; }; name = Debug; }; 6A6127371A299E6400C18E1D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; @@ -821,7 +832,6 @@ SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - SWIFT_VERSION = 3.0; }; name = Release; }; @@ -841,7 +851,6 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SDKROOT = iphoneos; - SWIFT_VERSION = 3.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TestApp iOS.app/TestApp iOS"; }; name = Debug; @@ -859,7 +868,6 @@ PROVISIONING_PROFILE_SPECIFIER = ""; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - SWIFT_VERSION = 3.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TestApp iOS.app/TestApp iOS"; }; name = Release; @@ -886,7 +894,6 @@ PRODUCT_NAME = "$(PROJECT_NAME)"; SDKROOT = macosx; SKIP_INSTALL = YES; - SWIFT_VERSION = 3.0; }; name = Debug; }; @@ -910,7 +917,6 @@ SDKROOT = macosx; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - SWIFT_VERSION = 3.0; }; name = Release; }; @@ -930,7 +936,6 @@ PRODUCT_BUNDLE_IDENTIFIER = "pw.aska.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; - SWIFT_VERSION = 3.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TestApp Mac.app/Contents/MacOS/TestApp Mac"; }; name = Debug; @@ -949,7 +954,6 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - SWIFT_VERSION = 3.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TestApp Mac.app/Contents/MacOS/TestApp Mac"; }; name = Release; diff --git a/KeyClip.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/KeyClip.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/KeyClip.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/KeyClip.xcodeproj/xcshareddata/xcschemes/KeyClip Mac Tests.xcscheme b/KeyClip.xcodeproj/xcshareddata/xcschemes/KeyClip Mac Tests.xcscheme index ae8c426..ce8dc80 100644 --- a/KeyClip.xcodeproj/xcshareddata/xcschemes/KeyClip Mac Tests.xcscheme +++ b/KeyClip.xcodeproj/xcshareddata/xcschemes/KeyClip Mac Tests.xcscheme @@ -1,6 +1,6 @@ - - - - diff --git a/KeyClip.xcodeproj/xcshareddata/xcschemes/KeyClip Mac.xcscheme b/KeyClip.xcodeproj/xcshareddata/xcschemes/KeyClip Mac.xcscheme index c8cdac3..42dced1 100644 --- a/KeyClip.xcodeproj/xcshareddata/xcschemes/KeyClip Mac.xcscheme +++ b/KeyClip.xcodeproj/xcshareddata/xcschemes/KeyClip Mac.xcscheme @@ -1,6 +1,6 @@ Void)? = nil) -> Bool { - return Static.instance.exists(key, failure: failure) - } - - open class func save(_ key: String, data: Data, failure: ((NSError) -> Void)? = nil) -> Bool { - return Static.instance.save(key, data: data, failure: failure) - } - - open class func save(_ key: String, string: String, failure: ((NSError) -> Void)? = nil) -> Bool { - return Static.instance.save(key, string: string, failure: failure) + open class func exists(_ key: String) throws -> Bool { + return try KeyClip.shared.exists(key) } - open class func save(_ key: String, dictionary: NSDictionary, failure: ((NSError) -> Void)? = nil) -> Bool { - return Static.instance.save(key, dictionary: dictionary, failure: failure) + open class func save(data: Data, forKey key: String) throws { + return try KeyClip.shared.save(data: data, forKey: key) } - open class func load(_ key: String, failure: ((NSError) -> Void)? = nil) -> Data? { - return Static.instance.load(key, failure: failure) + open class func save(string: String, forKey key: String) throws { + return try KeyClip.shared.save(string: string, forKey: key) } - open class func load(_ key: String, failure: ((NSError) -> Void)? = nil) -> NSDictionary? { - return Static.instance.load(key, failure: failure) + open class func save(dictionary: [AnyHashable: Any], forKey key: String) throws { + return try KeyClip.shared.save(dictionary: dictionary, forKey: key) } - open class func load(_ key: String, failure: ((NSError) -> Void)? = nil) -> String? { - return Static.instance.load(key, failure: failure) + open class func data(forKey key: String) throws -> Data? { + return try KeyClip.shared.data(forKey: key) } - open class func load(_ key: String, success: (NSDictionary) -> T, failure: ((NSError) -> Void)?) -> T? { - return Static.instance.load(key, success: success, failure: failure) + open class func dictionary(forKey key: String) throws -> [AnyHashable: Any]? { + return try KeyClip.shared.dictionary(forKey: key) } - open class func load(_ key: String, success: (NSDictionary) -> T) -> T? { - return Static.instance.load(key, success: success, failure: nil) + open class func string(forKey key: String) throws -> String? { + return try KeyClip.shared.string(forKey:key) } - open class func delete(_ key: String, failure: ((NSError) -> Void)? = nil) -> Bool { - return Static.instance.delete(key, failure: failure) + open class func load(_ key: String, success: ([AnyHashable: Any]) -> T) throws -> T? { + return try KeyClip.shared.load(key, success: success) } - open class func clear(_ failure: ((NSError) -> Void)? = nil) -> Bool { - return Static.instance.clear(failure) + @discardableResult + open class func delete(_ key: String) throws -> Bool { + return try KeyClip.shared.delete(key) } - open class func printError(_ printError: Bool) { - Static.printError = printError + @discardableResult + open class func clear() throws -> Bool { + return try KeyClip.shared.clear() } // MARK: Debug Methods diff --git a/KeyClip/Ring.swift b/KeyClip/Ring.swift index 7af859b..d228aae 100644 --- a/KeyClip/Ring.swift +++ b/KeyClip/Ring.swift @@ -9,13 +9,63 @@ import Foundation public extension KeyClip { - public class Ring { - + enum KeyClipError: Error, CustomDebugStringConvertible { + case stringEncoding + case dataLoading + case unhandledError(status: OSStatus) + + // /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/System/Library/Frameworks/Security.framework/Headers/SecBase.h + var message: String { + switch self { + case .stringEncoding: + return "Failed to encode string to utf8" + + case .dataLoading: + return "Failed to load data from keychain" + + case .unhandledError(let status): + #if os(iOS) + switch status { + case errSecUnimplemented: + return "Function or operation not implemented." + case errSecParam: + return "One or more parameters passed to a function where not valid." + case errSecAllocate: + return "Failed to allocate memory." + case errSecNotAvailable: + return "No keychain is available. You may need to restart your computer." + case errSecDuplicateItem: + return "The specified item already exists in the keychain." + case errSecItemNotFound: + return "The specified item could not be found in the keychain." + case errSecInteractionNotAllowed: + return "User interaction is not allowed." + case errSecDecode: + return "Unable to decode the provided data." + case errSecAuthFailed: + return "The user name or passphrase you entered is not correct." + case -25243: // errSecNoAccessForItem https://developer.apple.com/library/ios/samplecode/GenericKeychain/Listings/Classes_KeychainItemWrapper_m.html + return "Ignore the access group if running on the iPhone simulator." + default: + return "Refer to SecBase.h for description (status:\(status))" + } + #elseif os(OSX) + return "Refer to MacErrors.h for description (status:\(status))" + #endif + } + } + + public var debugDescription: String { + return message + } + } + + class Ring { let accessGroup: String? let service: String let accessible: String - // MARK: Initializer + // MARK: Init init(accessGroup: String?, service: String, accessible: String) { self.accessGroup = accessGroup @@ -23,9 +73,9 @@ public extension KeyClip { self.accessible = accessible } - // MARK: Public Methods + // MARK: Exists - open func exists(_ key: String, failure: ((NSError) -> Void)? = nil) -> Bool { + open func exists(_ key: String) throws -> Bool { var query: [String: AnyObject] = [ kSecAttrService as String : self.service as AnyObject, kSecClass as String : kSecClassGenericPassword, @@ -44,12 +94,13 @@ public extension KeyClip { case errSecItemNotFound: return false default: - self.failure(status: status, failure: failure) - return false + throw KeyClipError.unhandledError(status: status) } } + + // MARK: - Saving - open func save(_ key: String, data: Data, failure: ((NSError) -> Void)? = nil) -> Bool { + open func save(data: Data, forKey key: String) throws { var query: [String: AnyObject] = [ kSecAttrService as String : self.service as AnyObject, kSecClass as String : kSecClassGenericPassword, @@ -62,7 +113,7 @@ public extension KeyClip { var status: OSStatus - if self.exists(key, failure: failure) { + if try self.exists(key) { status = SecItemUpdate(query as CFDictionary, [kSecValueData as String: data] as CFDictionary) } else { query[kSecAttrAccessible as String] = self.accessible as AnyObject? @@ -70,33 +121,26 @@ public extension KeyClip { status = SecItemAdd(query as CFDictionary, nil) } - switch status { - case errSecSuccess: - return true - default: - self.failure(status: status, failure: failure) - return false + guard status == errSecSuccess else { + throw KeyClipError.unhandledError(status: status) } } - open func save(_ key: String, string: String, failure: ((NSError) -> Void)? = nil) -> Bool { - if let data = string.data(using: String.Encoding.utf8, allowLossyConversion: false) { - return self.save(key, data: data, failure: failure) + open func save(string: String, forKey key: String) throws { + guard let data = string.data(using: String.Encoding.utf8, allowLossyConversion: false) else { + throw KeyClipError.stringEncoding } - return false + + try save(data: data, forKey: key) } - open func save(_ key: String, dictionary: NSDictionary, failure: ((NSError) -> Void)? = nil) -> Bool { - do { - let data = try JSONSerialization.data(withJSONObject: dictionary, options: []) - return self.save(key, data: data, failure: failure) - } catch let error as NSError { - self.failure(error: error, failure: failure) - } - return false + open func save(dictionary: [AnyHashable: Any], forKey key: String) throws { + try save(data: try JSONSerialization.data(withJSONObject: dictionary, options: []), forKey: key) } - - open func load(_ key: String, failure: ((NSError) -> Void)? = nil) -> Data? { + + // MARK: - Loading + + open func data(forKey key: String) throws -> Data? { var query: [String: AnyObject] = [ kSecAttrService as String : self.service as AnyObject, kSecClass as String : kSecClassGenericPassword, @@ -112,53 +156,56 @@ public extension KeyClip { var result: AnyObject? let status = withUnsafeMutablePointer(to: &result) { SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0)) } - switch status { - case errSecSuccess: - if let data = result as? Data { - return data + guard status == errSecSuccess else { + if status == errSecItemNotFound { + return nil } - return nil - case errSecItemNotFound: - return nil - default: - self.failure(status: status, failure: failure) - return nil + + throw KeyClipError.unhandledError(status: status) } - } - - open func load(_ key: String, failure: ((NSError) -> Void)? = nil) -> NSDictionary? { - if let data: Data = self.load(key, failure: failure) { - do { - let json: Any = try JSONSerialization.jsonObject(with: data, options: []) - return json as? NSDictionary - } catch let error as NSError { - self.failure(error: error, failure: failure) - } + + guard let data = result as? Data else { + throw KeyClipError.dataLoading } - return nil + + return data } - open func load(_ key: String, failure: ((NSError) -> Void)? = nil) -> String? { - if let data: Data = self.load(key, failure: failure) { - if let string = NSString(data: data, encoding: String.Encoding.utf8.rawValue) { - return string as String - } + open func dictionary(forKey key: String) throws -> [AnyHashable: Any]? { + guard let data = try data(forKey: key) else { + return nil } - return nil + + let json = try JSONSerialization.jsonObject(with: data, options: []) + + return json as? [AnyHashable: Any] } - open func load(_ key: String, success: (NSDictionary) -> T, failure: ((NSError) -> Void)?) -> T? { - if let dictionary: NSDictionary = self.load(key) { - return success(dictionary) + open func string(forKey key: String) throws -> String? { + guard let data = try data(forKey: key) else { + return nil + } + + guard let string = String(data: data, encoding: .utf8) else { + throw KeyClipError.stringEncoding } - return nil + + return string } - open func load(_ key: String, success: (NSDictionary) -> T) -> T? { - return self.load(key, success: success, failure: nil) + // MARK: - Loading and converting + + open func load(_ key: String, success: ([AnyHashable: Any]) -> T) throws -> T? { + guard let dictionary = try dictionary(forKey: key) else { + return nil + } + + return success(dictionary) } + + // MARK: - Deleting - open func delete(_ key: String, failure: ((NSError) -> Void)? = nil) -> Bool { + open func delete(_ key: String) throws -> Bool { var query: [String: AnyObject] = [ kSecAttrService as String : self.service as AnyObject, kSecClass as String : kSecClassGenericPassword, @@ -171,18 +218,18 @@ public extension KeyClip { let status = SecItemDelete(query as CFDictionary) - switch status { - case errSecSuccess: - return true - case errSecItemNotFound: - return false - default: - self.failure(status: status, failure: failure) - return false + guard status == errSecSuccess else { + if status == errSecItemNotFound { + return false + } + + throw KeyClipError.unhandledError(status: status) } + + return true } - open func clear(_ failure: ((NSError) -> Void)? = nil) -> Bool { + open func clear() throws -> Bool { var query: [String: AnyObject] = [ kSecAttrService as String : self.service as AnyObject, kSecClass as String : kSecClassGenericPassword ] @@ -192,64 +239,16 @@ public extension KeyClip { } let status = SecItemDelete(query as CFDictionary) - - switch status { - case errSecSuccess: - return true - case errSecItemNotFound: - return false - default: - self.failure(status: status, failure: failure) - return false - } - } - - // MARK: Private Methods - - fileprivate func failure(status: OSStatus, function: String = #function, line: Int = #line, failure: ((NSError) -> Void)?) { - let userInfo = [ NSLocalizedDescriptionKey : statusMessage(status) ] - self.failure(error: NSError(domain: "pw.aska.KeyClip", code: Int(status), userInfo: userInfo), function: function, line: line, failure: failure) - } - - fileprivate func failure(error: NSError, function: String = #function, line: Int = #line, failure: ((NSError) -> Void)?) { - failure?(error) - - if KeyClip.printError { - NSLog("[KeyClip] function:\(function) line:\(line) \(error.debugDescription)") - } - } - - // /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/System/Library/Frameworks/Security.framework/Headers/SecBase.h - // swiftlint:disable:next cyclomatic_complexity - fileprivate func statusMessage(_ status: OSStatus) -> String { - #if os(iOS) - switch status { - case errSecUnimplemented: - return "Function or operation not implemented." - case errSecParam: - return "One or more parameters passed to a function where not valid." - case errSecAllocate: - return "Failed to allocate memory." - case errSecNotAvailable: - return "No keychain is available. You may need to restart your computer." - case errSecDuplicateItem: - return "The specified item already exists in the keychain." - case errSecItemNotFound: - return "The specified item could not be found in the keychain." - case errSecInteractionNotAllowed: - return "User interaction is not allowed." - case errSecDecode: - return "Unable to decode the provided data." - case errSecAuthFailed: - return "The user name or passphrase you entered is not correct." - case -25243: // errSecNoAccessForItem https://developer.apple.com/library/ios/samplecode/GenericKeychain/Listings/Classes_KeychainItemWrapper_m.html - return "Ignore the access group if running on the iPhone simulator." - default: - return "Refer to SecBase.h for description (status:\(status))" + + guard status == errSecSuccess else { + if status == errSecItemNotFound { + return false } - #elseif os(OSX) - return "Refer to MacErrors.h for description (status:\(status))" - #endif + + throw KeyClipError.unhandledError(status: status) + } + + return true } } } diff --git a/KeyClipTests/KeyClipTests.swift b/KeyClipTests/KeyClipTests.swift index b5b1c15..86959dd 100644 --- a/KeyClipTests/KeyClipTests.swift +++ b/KeyClipTests/KeyClipTests.swift @@ -20,12 +20,12 @@ class Account { let name: String let password: String - init(_ dictionary: NSDictionary) { + init(_ dictionary: [AnyHashable: Any]) { self.name = dictionary[Constants.name] as! String self.password = dictionary[Constants.password] as! String } - var dictionaryValue: [String: String] { + var dictionaryValue: [AnyHashable: String] { return [Constants.name: name, Constants.password: password] } } @@ -34,12 +34,13 @@ class KeyClipTests: XCTestCase { override func setUp() { super.setUp() - let _ = KeyClip.clear() - KeyClip.printError(true) + + try! KeyClip.clear() } override func tearDown() { - let _ = KeyClip.clear() + try! KeyClip.clear() + super.tearDown() } @@ -48,102 +49,125 @@ class KeyClipTests: XCTestCase { let key2 = "testSaveLoadKey2" let saveData = "data" - XCTAssertTrue((KeyClip.load(key1) as String?) == nil) - XCTAssertTrue((KeyClip.load(key2) as String?) == nil) + do { + XCTAssertTrue(try KeyClip.string(forKey: key1) == nil) + XCTAssertTrue(try KeyClip.string(forKey: key2) == nil) - XCTAssertTrue(KeyClip.save(key1, string: saveData)) + try KeyClip.save(string: saveData, forKey: key1) - XCTAssertTrue((KeyClip.load(key1) as String?) != nil) - XCTAssertTrue((KeyClip.load(key2) as String?) == nil) + XCTAssertFalse(try KeyClip.string(forKey: key1) == nil) + XCTAssertTrue(try KeyClip.string(forKey: key2) == nil) - let loadData = KeyClip.load(key1) ?? "" + let loadData = try KeyClip.string(forKey: key1) - XCTAssertEqual(loadData, saveData) + XCTAssertEqual(loadData, saveData) + } catch { + XCTFail("\(error)") + } } func testDictionary() { let key1 = "testSaveLoadKey1" let key2 = "testSaveLoadKey2" let saveAccount = Account([Account.Constants.name: "aska", Account.Constants.password: "********"]) + + do { + XCTAssertTrue(try KeyClip.string(forKey: key1) == nil) + XCTAssertTrue(try KeyClip.string(forKey: key2) == nil) - XCTAssertTrue((KeyClip.load(key1) as String?) == nil) - XCTAssertTrue((KeyClip.load(key2) as String?) == nil) - - XCTAssertTrue(KeyClip.save(key1, dictionary: saveAccount.dictionaryValue as NSDictionary)) + try KeyClip.save(dictionary: saveAccount.dictionaryValue, forKey: key1) - XCTAssertTrue((KeyClip.load(key1) as String?) != nil) - XCTAssertTrue((KeyClip.load(key2) as String?) == nil) + XCTAssertFalse(try KeyClip.string(forKey: key1) == nil) + XCTAssertTrue(try KeyClip.string(forKey: key2) == nil) - let loadAccount = KeyClip.load(key1) { (dictionary) -> Account in - return Account(dictionary) - } - XCTAssertEqual(loadAccount!.name, saveAccount.name) + let loadAccount = try KeyClip.load(key1) { (dictionary) -> Account in + return Account(dictionary) + } + XCTAssertEqual(loadAccount!.name, saveAccount.name) - let ring = KeyClip.Builder().build() - let loadAccount2 = ring.load(key1) { (dictionary) -> Account in - return Account(dictionary) - } - XCTAssertEqual(loadAccount2!.name, saveAccount.name) + let ring = KeyClip.Builder().build() + let loadAccount2 = try ring.load(key1) { (dictionary) -> Account in + return Account(dictionary) + } + XCTAssertEqual(loadAccount2!.name, saveAccount.name) - let success = { (dictionary) -> Account in - return Account(dictionary) + let success = { (dictionary) -> Account in + return Account(dictionary) + } + let loadAccount3 = try ring.load(key1, success: success) + XCTAssertEqual(loadAccount3!.name, saveAccount.name) + + try KeyClip.save(string: "dummy", forKey: key1) + + do { + let _ = try KeyClip.dictionary(forKey: key1) + + XCTFail("JSON parsing should throw error") + } catch { + // Do nothing + } + } catch { + XCTFail("\(error)") } - let loadAccount3 = ring.load(key1, success: success) - XCTAssertEqual(loadAccount3!.name, saveAccount.name) - - XCTAssertTrue(KeyClip.save(key1, string: "dummy")) - var hasError = false - let data: NSDictionary? = KeyClip.load(key1, failure: { (error: NSError) in - hasError = true - }) - - XCTAssertEqual(data, nil) - XCTAssertTrue(hasError) } func testDelete() { let key1 = "testDeleteKey1" let key2 = "testDeleteKey2" let saveData = "testDeleteData" + + do { + try KeyClip.save(string: saveData, forKey: key1) + try KeyClip.save(string: saveData, forKey: key2) - XCTAssertTrue(KeyClip.save(key1, string: saveData)) - XCTAssertTrue(KeyClip.save(key2, string: saveData)) + XCTAssertFalse(try KeyClip.string(forKey: key1) == nil) + XCTAssertFalse(try KeyClip.string(forKey: key2) == nil) - XCTAssertTrue((KeyClip.load(key1) as String?) != nil) - XCTAssertTrue((KeyClip.load(key2) as String?) != nil) + XCTAssertTrue(try KeyClip.delete(key1)) - XCTAssertTrue(KeyClip.delete(key1)) - - XCTAssertTrue((KeyClip.load(key1) as String?) == nil) - XCTAssertTrue((KeyClip.load(key2) as String?) != nil) + XCTAssertTrue(try KeyClip.string(forKey: key1) == nil) + XCTAssertFalse(try KeyClip.string(forKey: key2) == nil) + } catch { + XCTFail("\(error)") + } } func testExists() { let key1 = "testDeleteKey1" let key2 = "testDeleteKey2" let saveData = "testDeleteData" + + do { + try KeyClip.save(string: saveData, forKey: key1) + try KeyClip.save(string: saveData, forKey: key2) - XCTAssertTrue(KeyClip.save(key1, string: saveData)) - XCTAssertTrue(KeyClip.save(key2, string: saveData)) - - XCTAssertTrue(KeyClip.exists(key1)) - XCTAssertTrue(KeyClip.exists(key2)) + XCTAssertTrue(try KeyClip.exists(key1)) + XCTAssertTrue(try KeyClip.exists(key2)) - XCTAssertTrue(KeyClip.delete(key1)) + XCTAssertTrue(try KeyClip.delete(key1)) - XCTAssertTrue(!KeyClip.exists(key1)) - XCTAssertTrue(KeyClip.exists(key2)) + XCTAssertFalse(try KeyClip.exists(key1)) + XCTAssertTrue(try KeyClip.exists(key2)) + } catch { + XCTFail("\(error)") + } } func testClear() { let key = "testClearKey" let saveData = "testClearData" - - XCTAssertTrue(KeyClip.save(key, string: saveData)) - XCTAssertTrue((KeyClip.load(key) as String?) != nil) - - XCTAssertTrue(KeyClip.clear()) - XCTAssertTrue((KeyClip.load(key) as String?) == nil) + + do { + try KeyClip.save(string: saveData, forKey: key) + + XCTAssertFalse(try KeyClip.string(forKey: key) == nil) + + try KeyClip.clear() + + XCTAssertTrue(try KeyClip.string(forKey: key) == nil) + } catch { + XCTFail("\(error)") + } } func testService() { @@ -154,11 +178,15 @@ class KeyClipTests: XCTestCase { let ring1 = KeyClip.Builder().service("Service1").build() let ring2 = KeyClip.Builder().service("Service2").build() - XCTAssertTrue(ring1.save(key, string: val1)) - XCTAssertTrue(ring2.save(key, string: val2)) + do { + try ring1.save(string: val1, forKey: key) + try ring2.save(string: val2, forKey: key) - XCTAssertTrue(ring1.load(key) == val1) - XCTAssertTrue(ring2.load(key) == val2) + XCTAssertTrue(try ring1.string(forKey: key) == val1) + XCTAssertTrue(try ring2.string(forKey: key) == val2) + } catch { + XCTFail("\(error)") + } XCTAssertEqual(ring1.service, "Service1") XCTAssertEqual(ring2.service, "Service2") @@ -169,10 +197,14 @@ class KeyClipTests: XCTestCase { let val = "testSetServiceVal" let ring = KeyClip.Builder().accessible(kSecAttrAccessibleAfterFirstUnlock as String).build() + + do { + try ring.save(string: val, forKey: key) - XCTAssertTrue(ring.save(key, string: val)) - - XCTAssertTrue(ring.load(key) == val) + XCTAssertTrue(try ring.string(forKey: key) == val) + } catch { + XCTFail("\(error)") + } XCTAssertEqual(ring.accessible, kSecAttrAccessibleAfterFirstUnlock as String) @@ -187,27 +219,21 @@ class KeyClipTests: XCTestCase { #if os(iOS) let key = "testSetServiceKey" let val1 = "testSetServiceVal1" - let val2 = "testSetServiceVal2" // kSecAttrAccessGroup is always "com.apple.token" on iOS 9 simulator's keychain let defaultAccessGroup = KeyClip.defaultAccessGroup() let ring1 = KeyClip.Builder().accessGroup(defaultAccessGroup).build() - let ring2 = KeyClip.Builder() - .accessGroup("test.dummy") // always failure - .build() - XCTAssertTrue(ring1.save(key, string: val1)) + do { + try ring1.save(string: val1, forKey: key) - XCTAssertFalse(ring2.save(key, string: val2)) - - XCTAssertTrue(ring1.exists(key)) - - XCTAssertTrue(ring1.load(key) == val1) - - XCTAssertNil(ring2.load(key) as String?) + XCTAssertTrue(try ring1.exists(key)) + XCTAssertTrue(try ring1.string(forKey: key) == val1) + } catch { + XCTFail("\(error)") + } XCTAssertEqual(ring1.accessGroup!, defaultAccessGroup) - XCTAssertEqual(ring2.accessGroup!, "test.dummy") #endif } @@ -219,20 +245,17 @@ class KeyClipTests: XCTestCase { func testAccessGroupError() { #if os(iOS) - var errorCount = 0 let ring = KeyClip.Builder() .accessGroup("test.dummy") .build() - - let ret = ring.save("hoge", string: "bar") { error -> Void in - errorCount += 1 - let status = error.code // OSStatus - let defaultAccessGroup = KeyClip.defaultAccessGroup() - NSLog("[KeyClip] Error status:\(status) App Identifier:\(defaultAccessGroup)") + + do { + try ring.save(string: "bar", forKey: "foo") + } catch KeyClip.KeyClipError.unhandledError(let status) { + XCTAssertTrue(status == -34018) + } catch { + XCTFail("\(error)") } - XCTAssertFalse(ret) - - XCTAssertTrue(errorCount == 1) #endif } } diff --git a/TestApp iOS/AppDelegate.swift b/TestApp iOS/AppDelegate.swift index c2b231a..ec6983a 100644 --- a/TestApp iOS/AppDelegate.swift +++ b/TestApp iOS/AppDelegate.swift @@ -14,7 +14,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. return true }