mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-14 03:20:49 +00:00
Merged via /review-pr -> /prepare-pr -> /merge-pr.
Prepared head SHA: da2f8f6141
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Reviewed-by: @mbelinky
78 lines
2.8 KiB
Swift
78 lines
2.8 KiB
Swift
import Foundation
|
|
import Security
|
|
|
|
public enum GenericPasswordKeychainStore {
|
|
public static func loadString(service: String, account: String) -> String? {
|
|
guard let data = self.loadData(service: service, account: account) else { return nil }
|
|
return String(data: data, encoding: .utf8)
|
|
}
|
|
|
|
@discardableResult
|
|
public static func saveString(
|
|
_ value: String,
|
|
service: String,
|
|
account: String,
|
|
accessible: CFString = kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
|
|
) -> Bool {
|
|
self.saveData(Data(value.utf8), service: service, account: account, accessible: accessible)
|
|
}
|
|
|
|
@discardableResult
|
|
public static func delete(service: String, account: String) -> Bool {
|
|
let query = self.baseQuery(service: service, account: account)
|
|
let status = SecItemDelete(query as CFDictionary)
|
|
return status == errSecSuccess || status == errSecItemNotFound
|
|
}
|
|
|
|
private static func loadData(service: String, account: String) -> Data? {
|
|
var query = self.baseQuery(service: service, account: account)
|
|
query[kSecReturnData as String] = true
|
|
query[kSecMatchLimit as String] = kSecMatchLimitOne
|
|
|
|
var item: CFTypeRef?
|
|
let status = SecItemCopyMatching(query as CFDictionary, &item)
|
|
guard status == errSecSuccess, let data = item as? Data else { return nil }
|
|
return data
|
|
}
|
|
|
|
@discardableResult
|
|
private static func saveData(
|
|
_ data: Data,
|
|
service: String,
|
|
account: String,
|
|
accessible: CFString
|
|
) -> Bool {
|
|
let query = self.baseQuery(service: service, account: account)
|
|
let previousData = self.loadData(service: service, account: account)
|
|
|
|
let deleteStatus = SecItemDelete(query as CFDictionary)
|
|
guard deleteStatus == errSecSuccess || deleteStatus == errSecItemNotFound else {
|
|
return false
|
|
}
|
|
|
|
var insert = query
|
|
insert[kSecValueData as String] = data
|
|
insert[kSecAttrAccessible as String] = accessible
|
|
if SecItemAdd(insert as CFDictionary, nil) == errSecSuccess {
|
|
return true
|
|
}
|
|
|
|
// Best-effort rollback: preserve prior value if replacement fails.
|
|
guard let previousData else { return false }
|
|
var rollback = query
|
|
rollback[kSecValueData as String] = previousData
|
|
rollback[kSecAttrAccessible as String] = accessible
|
|
_ = SecItemDelete(query as CFDictionary)
|
|
_ = SecItemAdd(rollback as CFDictionary, nil)
|
|
return false
|
|
}
|
|
|
|
private static func baseQuery(service: String, account: String) -> [String: Any] {
|
|
[
|
|
kSecClass as String: kSecClassGenericPassword,
|
|
kSecAttrService as String: service,
|
|
kSecAttrAccount as String: account,
|
|
]
|
|
}
|
|
}
|