Skip to content

Commit

Permalink
Merge pull request #422 from kishikawakatsumi/alexmx-master
Browse files Browse the repository at this point in the history
Add get/set/remove ignoring AttributeSynchronizable APIs (`get(_:, ignoringAttributeSynchronizable:)`).
  • Loading branch information
kishikawakatsumi authored Oct 28, 2019
2 parents b1825ee + 046b2c7 commit 2c65b1d
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 16 deletions.
36 changes: 20 additions & 16 deletions Lib/KeychainAccess/Keychain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -549,12 +549,12 @@ public final class Keychain {

// 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
}
guard let string = String(data: data, encoding: .utf8) else {
Expand All @@ -564,8 +564,8 @@ public final class Keychain {
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[ReturnData] = kCFBooleanTrue
Expand All @@ -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

Expand Down Expand Up @@ -618,16 +618,16 @@ public final class Keychain {

// 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 {
print("failed to convert string to data")
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
#if os(iOS)
if #available(iOS 9.0, *) {
Expand Down Expand Up @@ -756,8 +756,8 @@ public final class Keychain {

// 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

let status = SecItemDelete(query as CFDictionary)
Expand Down Expand Up @@ -1256,14 +1256,18 @@ extension Keychain: CustomStringConvertible, CustomDebugStringConvertible {

extension Options {

func query() -> [String: Any] {
func query(ignoringAttributeSynchronizable: Bool = true) -> [String: Any] {
var query = [String: Any]()

query[Class] = itemClass.rawValue
query[AttributeSynchronizable] = SynchronizableAny
if let accessGroup = self.accessGroup {
query[AttributeAccessGroup] = accessGroup
}
if ignoringAttributeSynchronizable {
query[AttributeSynchronizable] = SynchronizableAny
} else {
query[AttributeSynchronizable] = synchronizable ? kCFBooleanTrue : kCFBooleanFalse
}

switch itemClass {
case .genericPassword:
Expand Down
83 changes: 83 additions & 0 deletions Lib/KeychainAccessTests/KeychainAccessTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1737,4 +1737,87 @@ class KeychainAccessTests: XCTestCase {
}
#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")
}
}

0 comments on commit 2c65b1d

Please sign in to comment.