Skip to content

Support macOS 10.15 and added a new Obj-C wrapper #241

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -44,3 +44,4 @@ Package.resolved

# DocC
docs.archive/
.DerivedData.noindex
7 changes: 5 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -3,8 +3,11 @@ import PackageDescription

let package = Package(
name: "SimpleKeychain",
platforms: [.iOS(.v14), .macOS(.v11), .tvOS(.v14), .watchOS(.v7), .visionOS(.v1)],
products: [.library(name: "SimpleKeychain", targets: ["SimpleKeychain"])],
platforms: [.iOS(.v14), .macOS(.v10_15), .tvOS(.v14), .watchOS(.v7), .visionOS(.v1)],
products: [
.library(name: "SimpleKeychain", targets: ["SimpleKeychain"]),
.library(name: "SimpleKeychain-dynamic", type: .dynamic, targets: ["SimpleKeychain"])
],
targets: [
.target(
name: "SimpleKeychain",
30 changes: 24 additions & 6 deletions SimpleKeychain.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
@@ -7,6 +7,15 @@
objects = {

/* Begin PBXBuildFile section */
298132FD2D7300490089E3B8 /* SimpleKeychainObjC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 298132FC2D7300490089E3B8 /* SimpleKeychainObjC.swift */; };
298132FE2D7300490089E3B8 /* SimpleKeychainObjC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 298132FC2D7300490089E3B8 /* SimpleKeychainObjC.swift */; };
298132FF2D7300490089E3B8 /* SimpleKeychainObjC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 298132FC2D7300490089E3B8 /* SimpleKeychainObjC.swift */; };
298133002D7300490089E3B8 /* SimpleKeychainObjC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 298132FC2D7300490089E3B8 /* SimpleKeychainObjC.swift */; };
298133012D7300490089E3B8 /* SimpleKeychainObjC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 298132FC2D7300490089E3B8 /* SimpleKeychainObjC.swift */; };
298133032D7301740089E3B8 /* SimpleKeychainObjCTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 298133022D7301740089E3B8 /* SimpleKeychainObjCTests.swift */; };
298133042D7301740089E3B8 /* SimpleKeychainObjCTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 298133022D7301740089E3B8 /* SimpleKeychainObjCTests.swift */; };
298133052D7301740089E3B8 /* SimpleKeychainObjCTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 298133022D7301740089E3B8 /* SimpleKeychainObjCTests.swift */; };
298133062D7301740089E3B8 /* SimpleKeychainObjCTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 298133022D7301740089E3B8 /* SimpleKeychainObjCTests.swift */; };
5B0D47641EA63CD1009FF1BF /* SimpleKeychainSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F4D27651BCE995C003C27B3 /* SimpleKeychainSpec.swift */; };
5C29744623FF457A00BC18FA /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C29744523FF457A00BC18FA /* AppDelegate.swift */; };
5C29744823FF457A00BC18FA /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C29744723FF457A00BC18FA /* ViewController.swift */; };
@@ -117,6 +126,8 @@
/* End PBXCopyFilesBuildPhase section */

/* Begin PBXFileReference section */
298132FC2D7300490089E3B8 /* SimpleKeychainObjC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleKeychainObjC.swift; sourceTree = "<group>"; };
298133022D7301740089E3B8 /* SimpleKeychainObjCTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleKeychainObjCTests.swift; sourceTree = "<group>"; };
5B0AB18F2088E2DB002D7109 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = SOURCE_ROOT; };
5B0D47591EA63C74009FF1BF /* SimpleKeychainTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SimpleKeychainTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
5B108AA91EA62F6100ED4DD2 /* SimpleKeychain.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SimpleKeychain.framework; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -323,6 +334,7 @@
children = (
5FEEB99F1B7BD70A00501415 /* Supporting Files */,
5CDF40592852D88C003840E6 /* SimpleKeychain.swift */,
298132FC2D7300490089E3B8 /* SimpleKeychainObjC.swift */,
5C737B2A285A7C0200B4BB25 /* SimpleKeychainError.swift */,
5C840111285AFF7B00689C01 /* Accessibility.swift */,
);
@@ -343,6 +355,7 @@
children = (
5FEEB9AC1B7BD70B00501415 /* Supporting Files */,
5F4D27651BCE995C003C27B3 /* SimpleKeychainSpec.swift */,
298133022D7301740089E3B8 /* SimpleKeychainObjCTests.swift */,
5C737B2F285AB57A00B4BB25 /* SimpleKeychainErrorSpec.swift */,
5CEB577A285BCE7E00A32A80 /* AccessibilitySpec.swift */,
);
@@ -930,6 +943,7 @@
5B0D47641EA63CD1009FF1BF /* SimpleKeychainSpec.swift in Sources */,
5CEB577D285BCE7E00A32A80 /* AccessibilitySpec.swift in Sources */,
5C737B36285AB9B100B4BB25 /* SimpleKeychainErrorSpec.swift in Sources */,
298133052D7301740089E3B8 /* SimpleKeychainObjCTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -939,6 +953,7 @@
files = (
5C737B2D285A7C0200B4BB25 /* SimpleKeychainError.swift in Sources */,
5C840114285AFF7B00689C01 /* Accessibility.swift in Sources */,
298133002D7300490089E3B8 /* SimpleKeychainObjC.swift in Sources */,
5CDF405C2852D88C003840E6 /* SimpleKeychain.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -949,6 +964,7 @@
files = (
5C737B2E285A7C0200B4BB25 /* SimpleKeychainError.swift in Sources */,
5C840115285AFF7B00689C01 /* Accessibility.swift in Sources */,
298132FF2D7300490089E3B8 /* SimpleKeychainObjC.swift in Sources */,
5CDF405D2852D88C003840E6 /* SimpleKeychain.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -969,6 +985,7 @@
5F4D27851BCE99E7003C27B3 /* SimpleKeychainSpec.swift in Sources */,
5CEB577B285BCE7E00A32A80 /* AccessibilitySpec.swift in Sources */,
5C737B34285AB9B100B4BB25 /* SimpleKeychainErrorSpec.swift in Sources */,
298133032D7301740089E3B8 /* SimpleKeychainObjCTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -979,6 +996,7 @@
5F4D279F1BCEA6CE003C27B3 /* SimpleKeychainSpec.swift in Sources */,
5CEB577C285BCE7E00A32A80 /* AccessibilitySpec.swift in Sources */,
5C737B35285AB9B100B4BB25 /* SimpleKeychainErrorSpec.swift in Sources */,
298133042D7301740089E3B8 /* SimpleKeychainObjCTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -996,6 +1014,7 @@
files = (
5C737B2B285A7C0200B4BB25 /* SimpleKeychainError.swift in Sources */,
5C840112285AFF7B00689C01 /* Accessibility.swift in Sources */,
298133012D7300490089E3B8 /* SimpleKeychainObjC.swift in Sources */,
5CDF405A2852D88C003840E6 /* SimpleKeychain.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -1006,6 +1025,7 @@
files = (
5C737B2C285A7C0200B4BB25 /* SimpleKeychainError.swift in Sources */,
5C840113285AFF7B00689C01 /* Accessibility.swift in Sources */,
298132FE2D7300490089E3B8 /* SimpleKeychainObjC.swift in Sources */,
5CDF405B2852D88C003840E6 /* SimpleKeychain.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -1025,6 +1045,7 @@
files = (
C1D1FBAD2C2192FA008E9E3F /* SimpleKeychain.swift in Sources */,
C1D1FBAE2C2192FA008E9E3F /* SimpleKeychainError.swift in Sources */,
298132FD2D7300490089E3B8 /* SimpleKeychainObjC.swift in Sources */,
C1D1FBAF2C2192FA008E9E3F /* Accessibility.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -1036,6 +1057,7 @@
C1D1FBB02C219322008E9E3F /* SimpleKeychainSpec.swift in Sources */,
C1D1FBB12C219322008E9E3F /* SimpleKeychainErrorSpec.swift in Sources */,
C1D1FBB22C219322008E9E3F /* AccessibilitySpec.swift in Sources */,
298133062D7301740089E3B8 /* SimpleKeychainObjCTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1389,7 +1411,6 @@
"@executable_path/../Frameworks",
"@loader_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 11.0;
PRODUCT_BUNDLE_IDENTIFIER = com.auth0.SimpleKeychainTests;
PRODUCT_NAME = SimpleKeychainTests;
SDKROOT = macosx;
@@ -1409,7 +1430,6 @@
"@executable_path/../Frameworks",
"@loader_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 11.0;
PRODUCT_BUNDLE_IDENTIFIER = com.auth0.SimpleKeychainTests;
PRODUCT_NAME = SimpleKeychainTests;
SDKROOT = macosx;
@@ -1513,7 +1533,7 @@
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
MACOSX_DEPLOYMENT_TARGET = 11.0;
MACOSX_DEPLOYMENT_TARGET = 10.15;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@@ -1568,7 +1588,7 @@
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
MACOSX_DEPLOYMENT_TARGET = 11.0;
MACOSX_DEPLOYMENT_TARGET = 10.15;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
@@ -1654,7 +1674,6 @@
"@executable_path/../Frameworks",
"@loader_path/Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 11.0;
PRODUCT_BUNDLE_IDENTIFIER = "com.auth0.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = SimpleKeychain;
SDKROOT = macosx;
@@ -1683,7 +1702,6 @@
"@executable_path/../Frameworks",
"@loader_path/Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 11.0;
PRODUCT_BUNDLE_IDENTIFIER = "com.auth0.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = SimpleKeychain;
SDKROOT = macosx;
36 changes: 7 additions & 29 deletions SimpleKeychain/SimpleKeychain.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import Foundation
import Security
#if canImport(LocalAuthentication)
#if canImport(LocalAuthentication) && !os(tvOS)
@preconcurrency import LocalAuthentication
public typealias SimpleKeychainContext = LAContext
#else
// Dummy context type for platforms without LAContext
public typealias SimpleKeychainContext = NSObject
#endif

typealias RetrieveFunction = (_ query: CFDictionary, _ result: UnsafeMutablePointer<CFTypeRef?>?) -> OSStatus
@@ -21,8 +25,7 @@ public struct SimpleKeychain: @unchecked Sendable {
var retrieve: RetrieveFunction = SecItemCopyMatching
var remove: RemoveFunction = SecItemDelete

#if canImport(LocalAuthentication) && !os(tvOS)
let context: LAContext?
let context: SimpleKeychainContext?

/// Initializes a ``SimpleKeychain`` instance.
///
@@ -39,7 +42,7 @@ public struct SimpleKeychain: @unchecked Sendable {
accessGroup: String? = nil,
accessibility: Accessibility = .afterFirstUnlock,
accessControlFlags: SecAccessControlCreateFlags? = nil,
context: LAContext? = nil,
context: SimpleKeychainContext? = nil,
synchronizable: Bool = false,
attributes: [String: Any] = [:]) {
self.service = service
@@ -50,31 +53,6 @@ public struct SimpleKeychain: @unchecked Sendable {
self.isSynchronizable = synchronizable
self.attributes = attributes
}
#else
/// Initializes a ``SimpleKeychain`` instance.
///
/// - Parameter service: Name of the service under which to save items. Defaults to the bundle identifier.
/// - Parameter accessGroup: access group for sharing Keychain items. Defaults to `nil`.
/// - Parameter accessibility: ``Accessibility`` type the stored items will have. Defaults to
/// ``Accessibility/afterFirstUnlock``.
/// - Parameter accessControlFlags: Access control conditions for `kSecAttrAccessControl`. Defaults to `nil`.
/// - Parameter synchronizable: Whether the items should be synchronized through iCloud. Defaults to `false`.
/// - Parameter attributes: Additional attributes to include in every query. Defaults to an empty dictionary.
/// - Returns: A ``SimpleKeychain`` instance.
public init(service: String = Bundle.main.bundleIdentifier!,
accessGroup: String? = nil,
accessibility: Accessibility = .afterFirstUnlock,
accessControlFlags: SecAccessControlCreateFlags? = nil,
synchronizable: Bool = false,
attributes: [String: Any] = [:]) {
self.service = service
self.accessGroup = accessGroup
self.accessibility = accessibility
self.accessControlFlags = accessControlFlags
self.isSynchronizable = synchronizable
self.attributes = attributes
}
#endif

private func assertSuccess(forStatus status: OSStatus) throws {
if status != errSecSuccess {
281 changes: 281 additions & 0 deletions SimpleKeychain/SimpleKeychainObjC.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,281 @@
import Foundation

@objc public enum SimpleKeychainAccessibility: Int {
case whenUnlocked
case whenUnlockedThisDeviceOnly
case afterFirstUnlock
case afterFirstUnlockThisDeviceOnly
case whenPasscodeSetThisDeviceOnly
}

/// A 'Simple' Obj-C wrapper around `SimpleKeychain`
@objcMembers
public class SimpleKeychainObjC: NSObject {
private let simpleKeychain: SimpleKeychain

/// Initializes a ``SimpleKeychain`` instance.
///
/// - Parameters:
/// - service: Name of the service under which to save items. Defaults to the bundle identifier.
/// - accessGroup: access group for sharing Keychain items. Defaults to `nil`.
/// - accessibility: ``Accessibility`` type the stored items will have. Defaults to ``Accessibility/afterFirstUnlock``.
/// - accessControlFlags: Access control conditions for `kSecAttrAccessControl`. Defaults to `nil`.
/// - context: `LAContext` used to access Keychain items. Defaults to `nil`.
/// - synchronizable: Whether the items should be synchronized through iCloud. Defaults to `false`.
/// - attributes: Additional attributes to include in every query. Defaults to an empty dictionary.
@objc public init(service: String,
accessGroup: String?,
accessibility: SimpleKeychainAccessibility,
accessControlFlags: NSNumber?,
context: SimpleKeychainContext?,
synchronizable: Bool,
attributes: [String: Any]) {

// Convert ObjC enum to Swift enum
let swiftAccessibility: Accessibility
switch accessibility {
case .whenUnlocked:
swiftAccessibility = .whenUnlocked
case .whenUnlockedThisDeviceOnly:
swiftAccessibility = .whenUnlockedThisDeviceOnly
case .afterFirstUnlock:
swiftAccessibility = .afterFirstUnlock
case .afterFirstUnlockThisDeviceOnly:
swiftAccessibility = .afterFirstUnlockThisDeviceOnly
case .whenPasscodeSetThisDeviceOnly:
swiftAccessibility = .whenPasscodeSetThisDeviceOnly
}

// Convert NSNumber to SecAccessControlCreateFlags if provided
let flags: SecAccessControlCreateFlags?
if let accessControlFlags = accessControlFlags {
flags = SecAccessControlCreateFlags(rawValue: accessControlFlags.uintValue)
} else {
flags = nil
}

// Add kSecUseDataProtectionKeychain on macOS by default
// as we want the accessibility to be used
var mutableAttributes = attributes
#if os(macOS)
if mutableAttributes[kSecUseDataProtectionKeychain as String] == nil {
mutableAttributes[kSecUseDataProtectionKeychain as String] = true
}
#endif

// Initialize the underlying Swift SimpleKeychain
self.simpleKeychain = SimpleKeychain(
service: service,
accessGroup: accessGroup,
accessibility: swiftAccessibility,
accessControlFlags: flags,
context: context,
synchronizable: synchronizable,
attributes: attributes
)

super.init()
}

/// Convenience initializer that uses the defaults
@objc public convenience init(service: String = Bundle.main.bundleIdentifier!) {
self.init(
service: service,
accessGroup: nil,
accessibility: .afterFirstUnlock,
accessControlFlags: nil,
context: nil,
synchronizable: false,
attributes: [:]
)
}
}

// MARK: - Retrieve items

public extension SimpleKeychainObjC {
/// Retrieves a `String` value from the Keychain.
///
/// ```swift
/// var error: NSError?
/// let value = try simpleKeychain.string(forKey: "your_key", error: &error)
/// ```
///
/// - Parameter key: Key of the Keychain item to retrieve.
/// - Parameter error: On failure, will be set to the error that occurred.
/// - Returns: The `String` value.
@objc func string(forKey key: String, error: NSErrorPointer) -> String? {
do {
return try simpleKeychain.string(forKey: key)
} catch let err {
if let error = error {
error.pointee = err as NSError
}
return nil
}
}

/// Retrieves a `Data` value from the Keychain.
///
/// ```swift
/// var error: NSError?
/// let value = try simpleKeychain.data(forKey: "your_key", error: &error)
/// ```
///
/// - Parameter key: Key of the Keychain item to retrieve.
/// - Parameter error: On failure, will be set to the error that occurred.
/// - Returns: The `Data` value.
@objc func data(forKey key: String, error: NSErrorPointer) -> Data? {
do {
return try simpleKeychain.data(forKey: key)
} catch let err {
if let error = error {
error.pointee = err as NSError
}
return nil
}
}
}

// MARK: - Store items

public extension SimpleKeychainObjC {
/// Saves a `String` value with the type `kSecClassGenericPassword` in the Keychain.
///
/// ```swift
/// var error: NSError?
/// try simpleKeychain.setString("some string", forKey: "your_key", error: &error)
/// ```
///
/// - Parameter string: Value to save in the Keychain.
/// - Parameter key: Key for the Keychain item.
/// - Parameter error: On failure, will be set to the error that occurred.
/// - Returns: `True` on a successful save. When `False`, check error.
@objc func setString(_ string: String, forKey key: String, error: NSErrorPointer) -> Bool {
do {
try simpleKeychain.set(string, forKey: key)
return true
} catch let err {
if let error = error {
error.pointee = err as NSError
}
return false
}
}

/// Saves a `Data` value with the type `kSecClassGenericPassword` in the Keychain.
///
/// ```swift
/// var error: NSError?
/// try simpleKeychain.set(data, forKey: "your_key", error: &error)
/// ```
///
/// - Parameter data: Value to save in the Keychain.
/// - Parameter key: Key for the Keychain item.
/// - Parameter error: On failure, will be set to the error that occurred.
/// - Returns: `True` on a successful save. When `False`, check error.
@objc func setData(_ data: Data, forKey key: String, error: NSErrorPointer) -> Bool {
do {
try simpleKeychain.set(data, forKey: key)
return true
} catch let err {
if let error = error {
error.pointee = err as NSError
}
return false
}
}
}

// MARK: - Delete items

public extension SimpleKeychainObjC {

/// Deletes an item from the Keychain.
///
/// ```swift
/// var error: NSError?
/// try simpleKeychain.deleteItem(forKey: "your_key", error: &error)
/// ```
///
/// - Parameter key: Key of the Keychain item to delete..
/// - Parameter error: On failure, will be set to the error that occurred.
/// - Returns: `True` on a successful save. When `False`, check error.
@objc func deleteItem(forKey key: String, error: NSErrorPointer) -> Bool {
do {
try simpleKeychain.deleteItem(forKey: key)
return true
} catch let err {
if let error = error {
error.pointee = err as NSError
}
return false
}
}

/// Deletes all items from the Keychain for the service and access group values.
///
/// ```swift
/// var error: NSError?
/// try simpleKeychain.deleteAll(error: &error)
/// ```
///
/// - Parameter error: On failure, will be set to the error that occurred.
/// - Returns: `True` on a successful save. When `False`, check error.
@objc func deleteAll(error: NSErrorPointer) -> Bool {
do {
try simpleKeychain.deleteAll()
return true
} catch let err {
if let error = error {
error.pointee = err as NSError
}
return false
}
}
}

// MARK: - Convenience methods

public extension SimpleKeychainObjC {
/// Checks if an item is stored in the Keychain.
///
/// ```swift
/// var error: NSError?
/// let isStored = try simpleKeychain.hasItem(forKey: "your_key", error: &error)
/// ```
///
/// - Parameter key: Key of the Keychain item to check.
/// - Parameter error: On failure, will be set to the error that occurred.
/// - Returns: Whether the item is stored in the Keychain or not.. When `False`, check error.
@objc func hasItem(forKey key: String, error: NSErrorPointer) -> Bool {
do {
return try simpleKeychain.hasItem(forKey: key)
} catch let err {
if let error = error {
error.pointee = err as NSError
}
return false
}
}

/// Retrieves the keys of all the items stored in the Keychain for the service and access group values.
///
/// ```swift
/// var error: NSError?
/// let keys = try simpleKeychain.keys(error: &error)
/// ```
///
/// - Returns: A `String` array containing the keys.
/// - Parameter error: On failure, will be set to the error that occurred.
@objc func keys(error: NSErrorPointer) -> [String]? {
do {
return try simpleKeychain.keys()
} catch let err {
if let error = error {
error.pointee = err as NSError
}
return nil
}
}
}
202 changes: 202 additions & 0 deletions SimpleKeychainTests/SimpleKeychainObjCTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
import XCTest
import Security
@testable import SimpleKeychain

class SimpleKeychainObjCTests: XCTestCase {
var sut: SimpleKeychainObjC!
let service = "com.auth0.simplekeychain.objc.tests"

override func setUp() {
super.setUp()
sut = SimpleKeychainObjC(service: service)

// Clear any existing items
var error: NSError?
_ = sut.deleteAll(error: &error)
}

override func tearDown() {
var error: NSError?
_ = sut.deleteAll(error: &error)
sut = nil
super.tearDown()
}

func testInitializationWithDefaultValues() {
// Default initialization should succeed
let keychain = SimpleKeychainObjC(service: service)
XCTAssertNotNil(keychain)
}

func testInitializationWithCustomValues() {
// Custom initialization with all parameters should succeed
let accessFlags = NSNumber(value: SecAccessControlCreateFlags.userPresence.rawValue)
let keychain = SimpleKeychainObjC(
service: service,
accessGroup: "Group",
accessibility: .afterFirstUnlock,
accessControlFlags: accessFlags,
context: nil,
synchronizable: true,
attributes: ["testKey": "testValue"]
)
XCTAssertNotNil(keychain)
}

func testStoringAndRetrievingString() {
let key = UUID().uuidString
let value = "TestValue-\(UUID().uuidString)"
var error: NSError?

// Store the string
let success = sut.setString(value, forKey: key, error: &error)
XCTAssertTrue(success, "String should be stored successfully")
XCTAssertNil(error, "Error should be nil when storing succeeds")

// Retrieve the string
let retrievedValue = sut.string(forKey: key, error: &error)
XCTAssertEqual(retrievedValue, value, "Retrieved value should match stored value")
XCTAssertNil(error, "Error should be nil when retrieving succeeds")
}

func testStoringAndRetrievingData() {
let key = UUID().uuidString
let originalString = "TestData-\(UUID().uuidString)"
let value = originalString.data(using: .utf8)!
var error: NSError?

// Store the data
let success = sut.setData(value, forKey: key, error: &error)
XCTAssertTrue(success, "Data should be stored successfully")
XCTAssertNil(error, "Error should be nil when storing succeeds")

// Retrieve the data
let retrievedData = sut.data(forKey: key, error: &error)
XCTAssertNotNil(retrievedData, "Retrieved data should not be nil")
XCTAssertNil(error, "Error should be nil when retrieving succeeds")

// Convert back to string and compare
let retrievedString = String(data: retrievedData!, encoding: .utf8)
XCTAssertEqual(retrievedString, originalString, "Retrieved data should convert back to original string")
}

func testRetrievingNonExistentItem() {
let nonExistentKey = "nonExistentKey-\(UUID().uuidString)"
var error: NSError?

// Try to retrieve a non-existent string
let retrievedString = sut.string(forKey: nonExistentKey, error: &error)
XCTAssertNil(retrievedString, "Retrieved string should be nil for non-existent key")
XCTAssertNotNil(error, "Error should not be nil when item doesn't exist")
XCTAssertEqual(error?.domain, "SimpleKeychain.SimpleKeychainError", "Error domain should match")

// Reset error for next test
error = nil

// Try to retrieve non-existent data
let retrievedData = sut.data(forKey: nonExistentKey, error: &error)
XCTAssertNil(retrievedData, "Retrieved data should be nil for non-existent key")
XCTAssertNotNil(error, "Error should not be nil when item doesn't exist")
}

func testDeletingItem() {
let key = UUID().uuidString
var error: NSError?

// Store an item
_ = sut.setString("testValue", forKey: key, error: &error)
XCTAssertNil(error, "Error should be nil when storing succeeds")

// Verify it exists
var exists = sut.hasItem(forKey: key, error: &error)
XCTAssertTrue(exists, "Item should exist after storing")
XCTAssertNil(error, "Error should be nil when checking succeeds")

// Delete the item
let success = sut.deleteItem(forKey: key, error: &error)
XCTAssertTrue(success, "Deletion should succeed")
XCTAssertNil(error, "Error should be nil when deletion succeeds")

// Verify it's gone
exists = sut.hasItem(forKey: key, error: &error)
XCTAssertFalse(exists, "Item should not exist after deletion")
}

func testDeletingAllItems() {
let keys = [UUID().uuidString, UUID().uuidString, UUID().uuidString]
var error: NSError?

// Store multiple items
for key in keys {
_ = sut.setString("value for \(key)", forKey: key, error: &error)
XCTAssertNil(error, "Error should be nil when storing succeeds")
}

// Delete all items
let success = sut.deleteAll(error: &error)
XCTAssertTrue(success, "Deleting all items should succeed")
XCTAssertNil(error, "Error should be nil when deletion succeeds")

// Verify all items are gone
for key in keys {
let exists = sut.hasItem(forKey: key, error: &error)
XCTAssertFalse(exists, "Item \(key) should not exist after deleteAll")
}
}

func testRetrievingAllKeys() {
let keys = [UUID().uuidString, UUID().uuidString, UUID().uuidString]
var error: NSError?

// Store multiple items
for key in keys {
_ = sut.setString("value for \(key)", forKey: key, error: &error)
XCTAssertNil(error, "Error should be nil when storing succeeds")
}

// Retrieve all keys
let retrievedKeys = sut.keys(error: &error)
XCTAssertNotNil(retrievedKeys, "Retrieved keys should not be nil")
XCTAssertNil(error, "Error should be nil when retrieving keys succeeds")

// Verify all our keys are included
for key in keys {
XCTAssertTrue(retrievedKeys!.contains(key), "Retrieved keys should contain \(key)")
}

// Verify count (note: there may be other items from other tests)
XCTAssertGreaterThanOrEqual(retrievedKeys!.count, keys.count,
"Should retrieve at least as many keys as we stored")
}

func testErrorHandlingForDeleteNonExistentItem() {
let nonExistentKey = "nonExistentKey-\(UUID().uuidString)"
var error: NSError?

// Try to delete a non-existent item
let success = sut.deleteItem(forKey: nonExistentKey, error: &error)
XCTAssertFalse(success, "Deleting non-existent item should fail")
XCTAssertNotNil(error, "Error should not be nil when deleting non-existent item")
XCTAssertEqual(error?.domain, "SimpleKeychain.SimpleKeychainError", "Error domain should match")
}

func testUpdatingExistingItem() {
let key = UUID().uuidString
let originalValue = "original value"
let updatedValue = "updated value"
var error: NSError?

// Store original value
_ = sut.setString(originalValue, forKey: key, error: &error)
XCTAssertNil(error, "Error should be nil when storing succeeds")

// Update with new value
let success = sut.setString(updatedValue, forKey: key, error: &error)
XCTAssertTrue(success, "Updating existing item should succeed")
XCTAssertNil(error, "Error should be nil when updating succeeds")

// Verify updated value
let retrievedValue = sut.string(forKey: key, error: &error)
XCTAssertEqual(retrievedValue, updatedValue, "Retrieved value should match updated value")
}
}