Browse Source

Merge pull request #422 from kishikawakatsumi/alexmx-master

Add get/set/remove ignoring AttributeSynchronizable APIs (`get(_:, ignoringAttributeSynchronizable:)`).
Kishikawa Katsumi 5 năm trước cách đây
mục cha
commit
2c65b1dd5e

+ 20 - 16
Lib/KeychainAccess/Keychain.swift

@@ -549,12 +549,12 @@ public final class Keychain {
 
 
     // MARK:
     // MARK:
 
 
-    public func get(_ key: String) throws -> String? {
-        return try getString(key)
+    public func get(_ key: String, ignoringAttributeSynchronizable: Bool = true) throws -> String? {
+        return try getString(key, ignoringAttributeSynchronizable: ignoringAttributeSynchronizable)
     }
     }
 
 
-    public func getString(_ key: String) throws -> String? {
-        guard let data = try getData(key) else  {
+    public func getString(_ key: String, ignoringAttributeSynchronizable: Bool = true) throws -> String? {
+        guard let data = try getData(key, ignoringAttributeSynchronizable: ignoringAttributeSynchronizable) else  {
             return nil
             return nil
         }
         }
         guard let string = String(data: data, encoding: .utf8) else {
         guard let string = String(data: data, encoding: .utf8) else {
@@ -564,8 +564,8 @@ public final class Keychain {
         return string
         return string
     }
     }
 
 
-    public func getData(_ key: String) throws -> Data? {
-        var query = options.query()
+    public func getData(_ key: String, ignoringAttributeSynchronizable: Bool = true) throws -> Data? {
+        var query = options.query(ignoringAttributeSynchronizable: ignoringAttributeSynchronizable)
 
 
         query[MatchLimit] = MatchLimitOne
         query[MatchLimit] = MatchLimitOne
         query[ReturnData] = kCFBooleanTrue
         query[ReturnData] = kCFBooleanTrue
@@ -588,8 +588,8 @@ public final class Keychain {
         }
         }
     }
     }
 
 
-    public func get<T>(_ key: String, handler: (Attributes?) -> T) throws -> T {
-        var query = options.query()
+    public func get<T>(_ key: String, ignoringAttributeSynchronizable: Bool = true, handler: (Attributes?) -> T) throws -> T {
+        var query = options.query(ignoringAttributeSynchronizable: ignoringAttributeSynchronizable)
 
 
         query[MatchLimit] = MatchLimitOne
         query[MatchLimit] = MatchLimitOne
 
 
@@ -618,16 +618,16 @@ public final class Keychain {
 
 
     // MARK:
     // MARK:
 
 
-    public func set(_ value: String, key: String) throws {
+    public func set(_ value: String, key: String, ignoringAttributeSynchronizable: Bool = true) throws {
         guard let data = value.data(using: .utf8, allowLossyConversion: false) else {
         guard let data = value.data(using: .utf8, allowLossyConversion: false) else {
             print("failed to convert string to data")
             print("failed to convert string to data")
             throw Status.conversionError
             throw Status.conversionError
         }
         }
-        try set(data, key: key)
+        try set(data, key: key, ignoringAttributeSynchronizable: ignoringAttributeSynchronizable)
     }
     }
 
 
-    public func set(_ value: Data, key: String) throws {
-        var query = options.query()
+    public func set(_ value: Data, key: String, ignoringAttributeSynchronizable: Bool = true) throws {
+        var query = options.query(ignoringAttributeSynchronizable: ignoringAttributeSynchronizable)
         query[AttributeAccount] = key
         query[AttributeAccount] = key
         #if os(iOS)
         #if os(iOS)
         if #available(iOS 9.0, *) {
         if #available(iOS 9.0, *) {
@@ -756,8 +756,8 @@ public final class Keychain {
 
 
     // MARK:
     // MARK:
 
 
-    public func remove(_ key: String) throws {
-        var query = options.query()
+    public func remove(_ key: String, ignoringAttributeSynchronizable: Bool = true) throws {
+        var query = options.query(ignoringAttributeSynchronizable: ignoringAttributeSynchronizable)
         query[AttributeAccount] = key
         query[AttributeAccount] = key
 
 
         let status = SecItemDelete(query as CFDictionary)
         let status = SecItemDelete(query as CFDictionary)
@@ -1256,14 +1256,18 @@ extension Keychain: CustomStringConvertible, CustomDebugStringConvertible {
 
 
 extension Options {
 extension Options {
 
 
-    func query() -> [String: Any] {
+    func query(ignoringAttributeSynchronizable: Bool = true) -> [String: Any] {
         var query = [String: Any]()
         var query = [String: Any]()
 
 
         query[Class] = itemClass.rawValue
         query[Class] = itemClass.rawValue
-        query[AttributeSynchronizable] = SynchronizableAny
         if let accessGroup = self.accessGroup {
         if let accessGroup = self.accessGroup {
             query[AttributeAccessGroup] = accessGroup
             query[AttributeAccessGroup] = accessGroup
         }
         }
+        if ignoringAttributeSynchronizable {
+            query[AttributeSynchronizable] = SynchronizableAny
+        } else {
+            query[AttributeSynchronizable] = synchronizable ? kCFBooleanTrue : kCFBooleanFalse
+        }
 
 
         switch itemClass {
         switch itemClass {
         case .genericPassword:
         case .genericPassword:

+ 83 - 0
Lib/KeychainAccessTests/KeychainAccessTests.swift

@@ -1737,4 +1737,87 @@ class KeychainAccessTests: XCTestCase {
         }
         }
         #endif
         #endif
     }
     }
+
+    func testIgnoringAttributeSynchronizable() {
+        let keychain = Keychain(service: "Twitter").synchronizable(false)
+        let keychainSynchronizable = Keychain(service: "Twitter").synchronizable(true)
+
+        XCTAssertNil(try! keychain.get("username", ignoringAttributeSynchronizable: false), "not stored username")
+        XCTAssertNil(try! keychain.get("password", ignoringAttributeSynchronizable: false), "not stored password")
+        XCTAssertNil(try! keychainSynchronizable.get("username", ignoringAttributeSynchronizable: false), "not stored username")
+        XCTAssertNil(try! keychainSynchronizable.get("password", ignoringAttributeSynchronizable: false), "not stored password")
+
+        do { try keychain.set("kishikawakatsumi", key: "username", ignoringAttributeSynchronizable: false) } catch {}
+        do { try keychainSynchronizable.set("kishikawakatsumi_synchronizable", key: "username", ignoringAttributeSynchronizable: false) } catch {}
+        XCTAssertEqual(try! keychain.get("username", ignoringAttributeSynchronizable: false), "kishikawakatsumi", "stored username")
+        XCTAssertEqual(try! keychainSynchronizable.get("username", ignoringAttributeSynchronizable: false), "kishikawakatsumi_synchronizable", "stored username")
+        XCTAssertNil(try! keychain.get("password", ignoringAttributeSynchronizable: false), "not stored password")
+        XCTAssertNil(try! keychainSynchronizable.get("password", ignoringAttributeSynchronizable: false), "not stored password")
+
+        do { try keychain.set("password1234", key: "password", ignoringAttributeSynchronizable: false) } catch {}
+        do { try keychainSynchronizable.set("password1234_synchronizable", key: "password", ignoringAttributeSynchronizable: false) } catch {}
+        XCTAssertEqual(try! keychain.get("username", ignoringAttributeSynchronizable: false), "kishikawakatsumi", "stored username")
+        XCTAssertEqual(try! keychainSynchronizable.get("username", ignoringAttributeSynchronizable: false), "kishikawakatsumi_synchronizable", "stored username")
+        XCTAssertEqual(try! keychain.get("password", ignoringAttributeSynchronizable: false), "password1234", "stored password")
+        XCTAssertEqual(try! keychainSynchronizable.get("password", ignoringAttributeSynchronizable: false), "password1234_synchronizable", "stored password")
+
+        do { try keychain.remove("username", ignoringAttributeSynchronizable: false) } catch {}
+        XCTAssertNil(try! keychain.get("username", ignoringAttributeSynchronizable: false), "not stored username")
+        XCTAssertEqual(try! keychainSynchronizable.get("username", ignoringAttributeSynchronizable: false), "kishikawakatsumi_synchronizable", "stored username")
+
+        do { try keychainSynchronizable.remove("username", ignoringAttributeSynchronizable: false) } catch {}
+        XCTAssertNil(try! keychain.get("username", ignoringAttributeSynchronizable: false), "not stored username")
+        XCTAssertNil(try! keychainSynchronizable.get("username", ignoringAttributeSynchronizable: false), "not stored username")
+        
+        XCTAssertEqual(try! keychain.get("password", ignoringAttributeSynchronizable: false), "password1234", "stored password")
+        XCTAssertEqual(try! keychainSynchronizable.get("password", ignoringAttributeSynchronizable: false), "password1234_synchronizable", "stored password")
+
+        do { try keychain.removeAll() } catch {}
+        XCTAssertNil(try! keychain.get("username", ignoringAttributeSynchronizable: false), "not stored username")
+        XCTAssertNil(try! keychainSynchronizable.get("username", ignoringAttributeSynchronizable: false), "not stored username")
+        XCTAssertNil(try! keychain.get("password", ignoringAttributeSynchronizable: false), "not stored password")
+        XCTAssertNil(try! keychainSynchronizable.get("password", ignoringAttributeSynchronizable: false), "not stored password")
+    }
+
+    func testIgnoringAttributeSynchronizableBackwardCompatibility() {
+        let keychain = Keychain(service: "Twitter").synchronizable(false)
+        let keychainSynchronizable = Keychain(service: "Twitter").synchronizable(true)
+
+        XCTAssertNil(try! keychain.get("username"), "not stored username")
+        XCTAssertNil(try! keychain.get("password"), "not stored password")
+        XCTAssertNil(try! keychainSynchronizable.get("username"), "not stored username")
+        XCTAssertNil(try! keychainSynchronizable.get("password"), "not stored password")
+
+        do { try keychain.set("kishikawakatsumi", key: "username") } catch {}
+        XCTAssertEqual(try! keychain.get("username"), "kishikawakatsumi", "stored username")
+        XCTAssertEqual(try! keychainSynchronizable.get("username"), "kishikawakatsumi", "stored username")
+
+        do { try keychainSynchronizable.set("kishikawakatsumi_synchronizable", key: "username") } catch {}
+        XCTAssertEqual(try! keychain.get("username"), "kishikawakatsumi_synchronizable", "stored username")
+        XCTAssertEqual(try! keychainSynchronizable.get("username"), "kishikawakatsumi_synchronizable", "stored username")
+        XCTAssertNil(try! keychain.get("password"), "not stored password")
+        XCTAssertNil(try! keychainSynchronizable.get("password"), "not stored password")
+
+        do { try keychain.set("password1234", key: "password") } catch {}
+        XCTAssertEqual(try! keychain.get("password"), "password1234", "stored password")
+        XCTAssertEqual(try! keychainSynchronizable.get("password"), "password1234", "stored password")
+
+        do { try keychainSynchronizable.set("password1234_synchronizable", key: "password") } catch {}
+        XCTAssertEqual(try! keychain.get("username"), "kishikawakatsumi_synchronizable", "stored username")
+        XCTAssertEqual(try! keychainSynchronizable.get("username"), "kishikawakatsumi_synchronizable", "stored username")
+        XCTAssertEqual(try! keychain.get("password"), "password1234_synchronizable", "stored password")
+        XCTAssertEqual(try! keychainSynchronizable.get("password"), "password1234_synchronizable", "stored password")
+
+        do { try keychain.remove("username") } catch {}
+        XCTAssertNil(try! keychain.get("username"), "not stored username")
+        XCTAssertNil(try! keychainSynchronizable.get("username"), "not stored username")
+        XCTAssertEqual(try! keychain.get("password"), "password1234_synchronizable", "stored password")
+        XCTAssertEqual(try! keychainSynchronizable.get("password"), "password1234_synchronizable", "stored password")
+
+        do { try keychain.removeAll() } catch {}
+        XCTAssertNil(try! keychain.get("username"), "not stored username")
+        XCTAssertNil(try! keychainSynchronizable.get("username"), "not stored username")
+        XCTAssertNil(try! keychain.get("password"), "not stored password")
+        XCTAssertNil(try! keychainSynchronizable.get("password"), "not stored password")
+    }
 }
 }