From 90d00012614fcbdb69876a4d7f6b3100142256f4 Mon Sep 17 00:00:00 2001 From: kishikawa katsumi Date: Sun, 1 Nov 2015 03:34:24 +0900 Subject: [PATCH 1/2] Add abilities to get/set any attributes --- Lib/KeychainAccess/Keychain.swift | 401 ++++++++++++------ .../KeychainAccessTests.swift | 244 ++++++++++- 2 files changed, 524 insertions(+), 121 deletions(-) diff --git a/Lib/KeychainAccess/Keychain.swift b/Lib/KeychainAccess/Keychain.swift index 73dc43fd0..83c96ee58 100644 --- a/Lib/KeychainAccess/Keychain.swift +++ b/Lib/KeychainAccess/Keychain.swift @@ -210,73 +210,106 @@ public struct AuthenticationPolicy : OptionSetType { } } -/** Class Key Constant */ -private let Class = kSecClass as String - -/** Attribute Key Constants */ -private let AttributeAccessible = kSecAttrAccessible as String - -@available(iOS 8.0, OSX 10.10, *) -private let AttributeAccessControl = kSecAttrAccessControl as String - -private let AttributeAccessGroup = kSecAttrAccessGroup as String -private let AttributeSynchronizable = kSecAttrSynchronizable as String -private let AttributeComment = kSecAttrComment as String -private let AttributeLabel = kSecAttrLabel as String -private let AttributeAccount = kSecAttrAccount as String -private let AttributeService = kSecAttrService as String -private let AttributeServer = kSecAttrServer as String -private let AttributeProtocol = kSecAttrProtocol as String -private let AttributeAuthenticationType = kSecAttrAuthenticationType as String -private let AttributePort = kSecAttrPort as String - -private let SynchronizableAny = kSecAttrSynchronizableAny - -/** Search Constants */ -private let MatchLimit = kSecMatchLimit as String -private let MatchLimitOne = kSecMatchLimitOne -private let MatchLimitAll = kSecMatchLimitAll - -/** Return Type Key Constants */ -private let ReturnData = kSecReturnData as String -private let ReturnAttributes = kSecReturnAttributes as String - -/** Value Type Key Constants */ -private let ValueData = kSecValueData as String - -/** Other Constants */ -@available(iOS 8.0, OSX 10.10, *) -private let UseOperationPrompt = kSecUseOperationPrompt as String - -#if os(iOS) -@available(iOS, introduced=8.0, deprecated=9.0, message="Use a UseAuthenticationUI instead.") -private let UseNoAuthenticationUI = kSecUseNoAuthenticationUI as String -#endif - -@available(iOS 9.0, OSX 10.11, *) -@available(watchOS, unavailable) -private let UseAuthenticationUI = kSecUseAuthenticationUI as String - -@available(iOS 9.0, OSX 10.11, *) -@available(watchOS, unavailable) -private let UseAuthenticationContext = kSecUseAuthenticationContext as String +public struct Attributes { + public var `class`: String? { + return attributes[Class] as? String + } + public var data: NSData? { + return attributes[ValueData] as? NSData + } + public var ref: NSData? { + return attributes[ValueRef] as? NSData + } + public var persistentRef: NSData? { + return attributes[ValuePersistentRef] as? NSData + } -@available(iOS 9.0, OSX 10.11, *) -@available(watchOS, unavailable) -private let UseAuthenticationUIAllow = kSecUseAuthenticationUIAllow as String + public var accessible: String? { + return attributes[AttributeAccessible] as? String + } + public var accessControl: SecAccessControl? { + if #available(OSX 10.10, *) { + if let accessControl = attributes[AttributeAccessControl] { + return (accessControl as! SecAccessControl) + } + return nil + } else { + return nil + } + } + public var accessGroup: String? { + return attributes[AttributeAccessGroup] as? String + } + public var synchronizable: Bool? { + return attributes[AttributeSynchronizable] as? Bool + } + public var creationDate: NSDate? { + return attributes[AttributeCreationDate] as? NSDate + } + public var modificationDate: NSDate? { + return attributes[AttributeModificationDate] as? NSDate + } + public var attributeDescription: String? { + return attributes[AttributeDescription] as? String + } + public var comment: String? { + return attributes[AttributeComment] as? String + } + public var creator: String? { + return attributes[AttributeCreator] as? String + } + public var type: String? { + return attributes[AttributeType] as? String + } + public var label: String? { + return attributes[AttributeLabel] as? String + } + public var isInvisible: Bool? { + return attributes[AttributeIsInvisible] as? Bool + } + public var isNegative: Bool? { + return attributes[AttributeIsNegative] as? Bool + } + public var account: String? { + return attributes[AttributeAccount] as? String + } + public var service: String? { + return attributes[AttributeService] as? String + } + public var generic: NSData? { + return attributes[AttributeGeneric] as? NSData + } + public var securityDomain: String? { + return attributes[AttributeSecurityDomain] as? String + } + public var server: String? { + return attributes[AttributeServer] as? String + } + public var `protocol`: String? { + return attributes[AttributeProtocol] as? String + } + public var authenticationType: String? { + return attributes[AttributeAuthenticationType] as? String + } + public var port: Int? { + return attributes[AttributePort] as? Int + } + public var path: String? { + return attributes[AttributePath] as? String + } -@available(iOS 9.0, OSX 10.11, *) -@available(watchOS, unavailable) -private let UseAuthenticationUIFail = kSecUseAuthenticationUIFail as String + private let attributes: [String: AnyObject] -@available(iOS 9.0, OSX 10.11, *) -@available(watchOS, unavailable) -private let UseAuthenticationUISkip = kSecUseAuthenticationUISkip as String + init(attributes: [String: AnyObject]) { + self.attributes = attributes + } -#if os(iOS) -/** Credential Key Constants */ -private let SharedPassword = kSecSharedPassword as String -#endif + public subscript(key: String) -> AnyObject? { + get { + return attributes[key] + } + } +} public class Keychain { public var itemClass: ItemClass { @@ -425,6 +458,12 @@ public class Keychain { return Keychain(options) } + public func attributes(attributes: [String: AnyObject]) -> Keychain { + var options = self.options + attributes.forEach { options.attributes.updateValue($1, forKey: $0) } + return Keychain(options) + } + @available(iOS 8.0, OSX 10.10, *) @available(watchOS, unavailable) public func authenticationPrompt(authenticationPrompt: String) -> Keychain { @@ -473,6 +512,34 @@ public class Keychain { } } + public func get(key: String, @noescape handler: Attributes? -> T) throws -> T { + var query = options.query() + + query[MatchLimit] = MatchLimitOne + + query[ReturnData] = true + query[ReturnAttributes] = true + query[ReturnRef] = true + query[ReturnPersistentRef] = true + + query[AttributeAccount] = key + + var result: AnyObject? + let status = withUnsafeMutablePointer(&result) { SecItemCopyMatching(query, UnsafeMutablePointer($0)) } + + switch status { + case errSecSuccess: + guard let attributes = result as? [String: AnyObject] else { + throw Status.UnexpectedError + } + return handler(Attributes(attributes: attributes)) + case errSecItemNotFound: + return handler(nil) + default: + throw securityError(status: status) + } + } + // MARK: public func set(value: String, key: String) throws { @@ -496,19 +563,21 @@ public class Keychain { query[UseAuthenticationUI] = UseAuthenticationUIFail } #endif - + var status = SecItemCopyMatching(query, nil) switch status { case errSecSuccess, errSecInteractionNotAllowed: var query = options.query() query[AttributeAccount] = key - - let (attributes, error) = options.attributes(key: nil, value: value) + + var (attributes, error) = options.attributes(key: nil, value: value) if let error = error { print(error.localizedDescription) throw error } + options.attributes.forEach { attributes.updateValue($1, forKey: $0) } + #if os(iOS) if status == errSecInteractionNotAllowed && floor(NSFoundationVersionNumber) <= floor(NSFoundationVersionNumber_iOS_8_0) { try remove(key) @@ -520,18 +589,20 @@ public class Keychain { } } #else - status = SecItemUpdate(query, attributes) - if status != errSecSuccess { - throw securityError(status: status) - } + status = SecItemUpdate(query, attributes) + if status != errSecSuccess { + throw securityError(status: status) + } #endif case errSecItemNotFound: - let (attributes, error) = options.attributes(key: key, value: value) + var (attributes, error) = options.attributes(key: key, value: value) if let error = error { print(error.localizedDescription) throw error } + options.attributes.forEach { attributes.updateValue($1, forKey: $0) } + status = SecItemAdd(attributes, nil) if status != errSecSuccess { throw securityError(status: status) @@ -962,10 +1033,92 @@ struct Options { var comment: String? var authenticationPrompt: String? - - init() {} + + var attributes = [String: AnyObject]() } +/** Class Key Constant */ +private let Class = String(kSecClass) + +/** Attribute Key Constants */ +private let AttributeAccessible = String(kSecAttrAccessible) + +@available(iOS 8.0, OSX 10.10, *) +private let AttributeAccessControl = String(kSecAttrAccessControl) + +private let AttributeAccessGroup = String(kSecAttrAccessGroup) +private let AttributeSynchronizable = String(kSecAttrSynchronizable) +private let AttributeCreationDate = String(kSecAttrCreationDate) +private let AttributeModificationDate = String(kSecAttrModificationDate) +private let AttributeDescription = String(kSecAttrDescription) +private let AttributeComment = String(kSecAttrComment) +private let AttributeCreator = String(kSecAttrCreator) +private let AttributeType = String(kSecAttrType) +private let AttributeLabel = String(kSecAttrLabel) +private let AttributeIsInvisible = String(kSecAttrIsInvisible) +private let AttributeIsNegative = String(kSecAttrIsNegative) +private let AttributeAccount = String(kSecAttrAccount) +private let AttributeService = String(kSecAttrService) +private let AttributeGeneric = String(kSecAttrGeneric) +private let AttributeSecurityDomain = String(kSecAttrSecurityDomain) +private let AttributeServer = String(kSecAttrServer) +private let AttributeProtocol = String(kSecAttrProtocol) +private let AttributeAuthenticationType = String(kSecAttrAuthenticationType) +private let AttributePort = String(kSecAttrPort) +private let AttributePath = String(kSecAttrPath) + +private let SynchronizableAny = kSecAttrSynchronizableAny + +/** Search Constants */ +private let MatchLimit = String(kSecMatchLimit) +private let MatchLimitOne = kSecMatchLimitOne +private let MatchLimitAll = kSecMatchLimitAll + +/** Return Type Key Constants */ +private let ReturnData = String(kSecReturnData) +private let ReturnAttributes = String(kSecReturnAttributes) +private let ReturnRef = String(kSecReturnRef) +private let ReturnPersistentRef = String(kSecReturnPersistentRef) + +/** Value Type Key Constants */ +private let ValueData = String(kSecValueData) +private let ValueRef = String(kSecValueRef) +private let ValuePersistentRef = String(kSecValuePersistentRef) + +/** Other Constants */ +@available(iOS 8.0, OSX 10.10, *) +private let UseOperationPrompt = String(kSecUseOperationPrompt) + +#if os(iOS) + @available(iOS, introduced=8.0, deprecated=9.0, message="Use a UseAuthenticationUI instead.") + private let UseNoAuthenticationUI = String(kSecUseNoAuthenticationUI) +#endif + +@available(iOS 9.0, OSX 10.11, *) +@available(watchOS, unavailable) +private let UseAuthenticationUI = String(kSecUseAuthenticationUI) + +@available(iOS 9.0, OSX 10.11, *) +@available(watchOS, unavailable) +private let UseAuthenticationContext = String(kSecUseAuthenticationContext) + +@available(iOS 9.0, OSX 10.11, *) +@available(watchOS, unavailable) +private let UseAuthenticationUIAllow = String(kSecUseAuthenticationUIAllow) + +@available(iOS 9.0, OSX 10.11, *) +@available(watchOS, unavailable) +private let UseAuthenticationUIFail = String(kSecUseAuthenticationUIFail) + +@available(iOS 9.0, OSX 10.11, *) +@available(watchOS, unavailable) +private let UseAuthenticationUISkip = String(kSecUseAuthenticationUISkip) + +#if os(iOS) + /** Credential Key Constants */ + private let SharedPassword = String(kSecSharedPassword) +#endif + extension Keychain : CustomStringConvertible, CustomDebugStringConvertible { public var description: String { let items = allItems() @@ -1064,6 +1217,16 @@ extension Options { // MARK: +extension Attributes : CustomStringConvertible, CustomDebugStringConvertible { + public var description: String { + return "\(attributes)" + } + + public var debugDescription: String { + return description + } +} + extension ItemClass : RawRepresentable, CustomStringConvertible { public init?(rawValue: String) { @@ -1170,67 +1333,67 @@ extension ProtocolType : RawRepresentable, CustomStringConvertible { public var rawValue: String { switch self { case FTP: - return kSecAttrProtocolFTP as String + return String(kSecAttrProtocolFTP) case FTPAccount: - return kSecAttrProtocolFTPAccount as String + return String(kSecAttrProtocolFTPAccount) case HTTP: - return kSecAttrProtocolHTTP as String + return String(kSecAttrProtocolHTTP) case IRC: - return kSecAttrProtocolIRC as String + return String(kSecAttrProtocolIRC) case NNTP: - return kSecAttrProtocolNNTP as String + return String(kSecAttrProtocolNNTP) case POP3: - return kSecAttrProtocolPOP3 as String + return String(kSecAttrProtocolPOP3) case SMTP: - return kSecAttrProtocolSMTP as String + return String(kSecAttrProtocolSMTP) case SOCKS: - return kSecAttrProtocolSOCKS as String + return String(kSecAttrProtocolSOCKS) case IMAP: - return kSecAttrProtocolIMAP as String + return String(kSecAttrProtocolIMAP) case LDAP: - return kSecAttrProtocolLDAP as String + return String(kSecAttrProtocolLDAP) case AppleTalk: - return kSecAttrProtocolAppleTalk as String + return String(kSecAttrProtocolAppleTalk) case AFP: - return kSecAttrProtocolAFP as String + return String(kSecAttrProtocolAFP) case Telnet: - return kSecAttrProtocolTelnet as String + return String(kSecAttrProtocolTelnet) case SSH: - return kSecAttrProtocolSSH as String + return String(kSecAttrProtocolSSH) case FTPS: - return kSecAttrProtocolFTPS as String + return String(kSecAttrProtocolFTPS) case HTTPS: - return kSecAttrProtocolHTTPS as String + return String(kSecAttrProtocolHTTPS) case HTTPProxy: - return kSecAttrProtocolHTTPProxy as String + return String(kSecAttrProtocolHTTPProxy) case HTTPSProxy: - return kSecAttrProtocolHTTPSProxy as String + return String(kSecAttrProtocolHTTPSProxy) case FTPProxy: - return kSecAttrProtocolFTPProxy as String + return String(kSecAttrProtocolFTPProxy) case SMB: - return kSecAttrProtocolSMB as String + return String(kSecAttrProtocolSMB) case RTSP: - return kSecAttrProtocolRTSP as String + return String(kSecAttrProtocolRTSP) case RTSPProxy: - return kSecAttrProtocolRTSPProxy as String + return String(kSecAttrProtocolRTSPProxy) case DAAP: - return kSecAttrProtocolDAAP as String + return String(kSecAttrProtocolDAAP) case EPPC: - return kSecAttrProtocolEPPC as String + return String(kSecAttrProtocolEPPC) case IPP: - return kSecAttrProtocolIPP as String + return String(kSecAttrProtocolIPP) case NNTPS: - return kSecAttrProtocolNNTPS as String + return String(kSecAttrProtocolNNTPS) case LDAPS: - return kSecAttrProtocolLDAPS as String + return String(kSecAttrProtocolLDAPS) case TelnetS: - return kSecAttrProtocolTelnetS as String + return String(kSecAttrProtocolTelnetS) case IMAPS: - return kSecAttrProtocolIMAPS as String + return String(kSecAttrProtocolIMAPS) case IRCS: - return kSecAttrProtocolIRCS as String + return String(kSecAttrProtocolIRCS) case POP3S: - return kSecAttrProtocolPOP3S as String + return String(kSecAttrProtocolPOP3S) } } @@ -1330,21 +1493,21 @@ extension AuthenticationType : RawRepresentable, CustomStringConvertible { public var rawValue: String { switch self { case NTLM: - return kSecAttrAuthenticationTypeNTLM as String + return String(kSecAttrAuthenticationTypeNTLM) case MSN: - return kSecAttrAuthenticationTypeMSN as String + return String(kSecAttrAuthenticationTypeMSN) case DPA: - return kSecAttrAuthenticationTypeDPA as String + return String(kSecAttrAuthenticationTypeDPA) case RPA: - return kSecAttrAuthenticationTypeRPA as String + return String(kSecAttrAuthenticationTypeRPA) case HTTPBasic: - return kSecAttrAuthenticationTypeHTTPBasic as String + return String(kSecAttrAuthenticationTypeHTTPBasic) case HTTPDigest: - return kSecAttrAuthenticationTypeHTTPDigest as String + return String(kSecAttrAuthenticationTypeHTTPDigest) case HTMLForm: - return kSecAttrAuthenticationTypeHTMLForm as String + return String(kSecAttrAuthenticationTypeHTMLForm) case Default: - return kSecAttrAuthenticationTypeDefault as String + return String(kSecAttrAuthenticationTypeDefault) } } @@ -1415,23 +1578,23 @@ extension Accessibility : RawRepresentable, CustomStringConvertible { public var rawValue: String { switch self { case WhenUnlocked: - return kSecAttrAccessibleWhenUnlocked as String + return String(kSecAttrAccessibleWhenUnlocked) case AfterFirstUnlock: - return kSecAttrAccessibleAfterFirstUnlock as String + return String(kSecAttrAccessibleAfterFirstUnlock) case Always: - return kSecAttrAccessibleAlways as String + return String(kSecAttrAccessibleAlways) case WhenPasscodeSetThisDeviceOnly: if #available(OSX 10.10, *) { - return kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly as String + return String(kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly) } else { fatalError("'Accessibility.WhenPasscodeSetThisDeviceOnly' is not available on this version of OS.") } case WhenUnlockedThisDeviceOnly: - return kSecAttrAccessibleWhenUnlockedThisDeviceOnly as String + return String(kSecAttrAccessibleWhenUnlockedThisDeviceOnly) case AfterFirstUnlockThisDeviceOnly: - return kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly as String + return String(kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly) case AlwaysThisDeviceOnly: - return kSecAttrAccessibleAlwaysThisDeviceOnly as String + return String(kSecAttrAccessibleAlwaysThisDeviceOnly) } } diff --git a/Lib/KeychainAccessTests/KeychainAccessTests.swift b/Lib/KeychainAccessTests/KeychainAccessTests.swift index 8c1165e02..49345bfa1 100644 --- a/Lib/KeychainAccessTests/KeychainAccessTests.swift +++ b/Lib/KeychainAccessTests/KeychainAccessTests.swift @@ -288,8 +288,103 @@ class KeychainAccessTests: XCTestCase { XCTAssertNil(try! keychain.get("password"), "not stored password") do { try keychain.set("password1234", key: "password") } catch {} - XCTAssertEqual(try! keychain.get("username")!, "kishikawakatsumi", "stored username") - XCTAssertEqual(try! keychain.get("password")!, "password1234", "stored password") + XCTAssertEqual(try! keychain.get("username"), "kishikawakatsumi", "stored username") + XCTAssertEqual(try! keychain.get("password"), "password1234", "stored password") + } + + func testSetStringWithLabel() { + let keychain = Keychain(service: "Twitter") + .label("Twitter Account") + + XCTAssertNil(keychain["kishikawakatsumi"], "not stored password") + + do { + let label = try keychain.get("kishikawakatsumi") { (attributes) -> String? in + return attributes?.label + } + XCTAssertNil(label) + } catch { + XCTFail("error occurred") + } + + keychain["kishikawakatsumi"] = "password1234" + XCTAssertEqual(keychain["kishikawakatsumi"], "password1234", "stored password") + + do { + let label = try keychain.get("kishikawakatsumi") { (attributes) -> String? in + return attributes?.label + } + XCTAssertEqual(label, "Twitter Account") + } catch { + XCTFail("error occurred") + } + } + + func testSetStringWithComment() { + let keychain = Keychain(service: "Twitter") + .comment("Kishikawa Katsumi") + + XCTAssertNil(keychain["kishikawakatsumi"], "not stored password") + + do { + let comment = try keychain.get("kishikawakatsumi") { (attributes) -> String? in + return attributes?.comment + } + XCTAssertNil(comment) + } catch { + XCTFail("error occurred") + } + + keychain["kishikawakatsumi"] = "password1234" + XCTAssertEqual(keychain["kishikawakatsumi"], "password1234", "stored password") + + do { + let comment = try keychain.get("kishikawakatsumi") { (attributes) -> String? in + return attributes?.comment + } + XCTAssertEqual(comment, "Kishikawa Katsumi") + } catch { + XCTFail("error occurred") + } + } + + func testSetStringWithLabelAndComment() { + let keychain = Keychain(service: "Twitter") + .label("Twitter Account") + .comment("Kishikawa Katsumi") + + XCTAssertNil(keychain["kishikawakatsumi"], "not stored password") + + do { + let label = try keychain.get("kishikawakatsumi") { (attributes) -> String? in + return attributes?.label + } + XCTAssertNil(label) + + let comment = try keychain.get("kishikawakatsumi") { (attributes) -> String? in + return attributes?.comment + } + XCTAssertNil(comment) + } catch { + XCTFail("error occurred") + } + + keychain["kishikawakatsumi"] = "password1234" + XCTAssertEqual(keychain["kishikawakatsumi"], "password1234", "stored password") + + do { + let label = try keychain.get("kishikawakatsumi") { (attributes) -> String? in + return attributes?.label + } + XCTAssertEqual(label, "Twitter Account") + + let comment = try keychain.get("kishikawakatsumi") { (attributes) -> String? in + return attributes?.comment + } + XCTAssertEqual(comment, "Kishikawa Katsumi") + } catch { + XCTFail("error occurred") + } } func testSetData() { @@ -319,6 +414,151 @@ class KeychainAccessTests: XCTestCase { XCTAssertEqual(error.code, Int(Status.ConversionError.rawValue)) } } + + func testGetPersistentRef() { + let keychain = Keychain(service: "Twitter") + + XCTAssertNil(keychain["kishikawakatsumi"], "not stored password") + + do { + let persistentRef = try keychain.get("kishikawakatsumi") { $0?.persistentRef } + XCTAssertNil(persistentRef) + } catch { + XCTFail("error occurred") + } + + keychain["kishikawakatsumi"] = "password1234" + XCTAssertEqual(keychain["kishikawakatsumi"], "password1234", "stored password") + + do { + let persistentRef = try keychain.get("kishikawakatsumi") { $0?.persistentRef } + XCTAssertNotNil(persistentRef) + } catch { + XCTFail("error occurred") + } + } + + #if os(iOS) || os(tvOS) + func testSetAttributes() { + do { + var attributes = [String: AnyObject]() + attributes[String(kSecAttrDescription)] = "Description Test" + attributes[String(kSecAttrComment)] = "Comment Test" + attributes[String(kSecAttrCreator)] = "Creator Test" + attributes[String(kSecAttrType)] = "Type Test" + attributes[String(kSecAttrLabel)] = "Label Test" + attributes[String(kSecAttrIsInvisible)] = true + attributes[String(kSecAttrIsNegative)] = true + + let keychain = Keychain(service: "Twitter") + .attributes(attributes) + .accessibility(.WhenPasscodeSetThisDeviceOnly, authenticationPolicy: .UserPresence) + + XCTAssertNil(keychain["kishikawakatsumi"], "not stored password") + + do { + let attributes = try keychain.get("kishikawakatsumi") { $0 } + XCTAssertNil(attributes) + } catch { + XCTFail("error occurred") + } + + keychain["kishikawakatsumi"] = "password1234" + XCTAssertEqual(keychain["kishikawakatsumi"], "password1234", "stored password") + + do { + let attributes = try keychain.get("kishikawakatsumi") { $0 } + XCTAssertEqual(attributes?.`class`, ItemClass.GenericPassword.rawValue) + XCTAssertEqual(attributes?.data, "password1234".dataUsingEncoding(NSUTF8StringEncoding)) + XCTAssertNil(attributes?.ref) + XCTAssertNotNil(attributes?.persistentRef) + XCTAssertEqual(attributes?.accessible, Accessibility.WhenPasscodeSetThisDeviceOnly.rawValue) + XCTAssertNotNil(attributes?.accessControl) + XCTAssertEqual(attributes?.accessGroup, "") + XCTAssertNotNil(attributes?.synchronizable) + XCTAssertNotNil(attributes?.creationDate) + XCTAssertNotNil(attributes?.modificationDate) + XCTAssertEqual(attributes?.attributeDescription, "Description Test") + XCTAssertEqual(attributes?.comment, "Comment Test") + XCTAssertEqual(attributes?.creator, "Creator Test") + XCTAssertEqual(attributes?.type, "Type Test") + XCTAssertEqual(attributes?.label, "Label Test") + XCTAssertEqual(attributes?.isInvisible, true) + XCTAssertEqual(attributes?.isNegative, true) + XCTAssertEqual(attributes?.account, "kishikawakatsumi") + XCTAssertEqual(attributes?.service, "Twitter") + XCTAssertNil(attributes?.generic) + XCTAssertNil(attributes?.securityDomain) + XCTAssertNil(attributes?.server) + XCTAssertNil(attributes?.`protocol`) + XCTAssertNil(attributes?.authenticationType) + XCTAssertNil(attributes?.port) + XCTAssertNil(attributes?.path) + } catch { + XCTFail("error occurred") + } + } + do { + var attributes = [String: AnyObject]() + attributes[String(kSecAttrDescription)] = "Description Test" + attributes[String(kSecAttrComment)] = "Comment Test" + attributes[String(kSecAttrCreator)] = "Creator Test" + attributes[String(kSecAttrType)] = "Type Test" + attributes[String(kSecAttrLabel)] = "Label Test" + attributes[String(kSecAttrIsInvisible)] = true + attributes[String(kSecAttrIsNegative)] = true + attributes[String(kSecAttrSecurityDomain)] = "securitydomain" + + let keychain = Keychain(server: NSURL(string: "https://example.com:443//api/login/")!, protocolType: .HTTPS) + .attributes(attributes) + .accessibility(.WhenPasscodeSetThisDeviceOnly, authenticationPolicy: .UserPresence) + + XCTAssertNil(keychain["kishikawakatsumi"], "not stored password") + + do { + let attributes = try keychain.get("kishikawakatsumi") { $0 } + XCTAssertNil(attributes) + } catch { + XCTFail("error occurred") + } + + keychain["kishikawakatsumi"] = "password1234" + XCTAssertEqual(keychain["kishikawakatsumi"], "password1234", "stored password") + + do { + let attributes = try keychain.get("kishikawakatsumi") { $0 } + XCTAssertEqual(attributes?.`class`, ItemClass.InternetPassword.rawValue) + XCTAssertEqual(attributes?.data, "password1234".dataUsingEncoding(NSUTF8StringEncoding)) + XCTAssertNil(attributes?.ref) + XCTAssertNotNil(attributes?.persistentRef) + XCTAssertEqual(attributes?.accessible, Accessibility.WhenPasscodeSetThisDeviceOnly.rawValue) + XCTAssertNotNil(attributes?.accessControl) + XCTAssertEqual(attributes?.accessGroup, "") + XCTAssertNotNil(attributes?.synchronizable) + XCTAssertNotNil(attributes?.creationDate) + XCTAssertNotNil(attributes?.modificationDate) + XCTAssertEqual(attributes?.attributeDescription, "Description Test") + XCTAssertEqual(attributes?.comment, "Comment Test") + XCTAssertEqual(attributes?.creator, "Creator Test") + XCTAssertEqual(attributes?.type, "Type Test") + XCTAssertEqual(attributes?.label, "Label Test") + XCTAssertEqual(attributes?.isInvisible, true) + XCTAssertEqual(attributes?.isNegative, true) + XCTAssertEqual(attributes?.account, "kishikawakatsumi") + XCTAssertNil(attributes?.service) + XCTAssertNil(attributes?.generic) + XCTAssertEqual(attributes?.securityDomain, "securitydomain") + XCTAssertEqual(attributes?.server, "example.com") + XCTAssertEqual(attributes?.`protocol`, ProtocolType.HTTPS.rawValue) + XCTAssertEqual(attributes?.authenticationType, AuthenticationType.Default.rawValue) + XCTAssertEqual(attributes?.port, 443) + XCTAssertEqual(attributes?.path, "") + } catch { + XCTFail("error occurred") + } + } + } + #endif func testRemoveString() { let keychain = Keychain(service: "Twitter") From 1bb3482e4c964beebecb8897eb974c6be51197ad Mon Sep 17 00:00:00 2001 From: kishikawa katsumi Date: Sun, 1 Nov 2015 05:47:38 +0900 Subject: [PATCH 2/2] Update README about attributes --- README.md | 43 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e07d06fae..eb83412d3 100644 --- a/README.md +++ b/README.md @@ -160,7 +160,7 @@ do { } ``` -### :key: Label and Comment +### :key: Set Label and Comment ```swift let keychain = Keychain(server: "https://github.com", protocolType: .HTTPS) @@ -174,6 +174,47 @@ do { } ``` +### :key: Obtaining Other Attributes + +#### PersistentRef + +```swift +let keychain = Keychain() +do { + let persistentRef = try keychain.get("kishikawakatsumi") { $0?.persistentRef } + ... +} catch let error { + print("error: \(error)") +} +``` + +#### Creation Date + +```swift +let keychain = Keychain() +do { + let creationDate = try keychain.get("kishikawakatsumi") { $0?.creationDate } + ... +} catch let error { + print("error: \(error)") +} +``` + +#### All Attributes + +```swift +let keychain = Keychain() +do { + let attributes = try keychain.get("kishikawakatsumi") { $0 } + print(attributes.comment) + print(attributes.label) + print(attributes.creator) + ... +} catch let error { + print("error: \(error)") +} +``` + ### :key: Configuration (Accessibility, Sharing, iCould Sync) **Provides fluent interfaces**