IOS updates
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
import Foundation
|
||||
import Security
|
||||
|
||||
enum KeychainService {
|
||||
static let service = "net.absdev.SonarDesk"
|
||||
|
||||
static func saveToken(_ token: String, serverID: UUID) throws {
|
||||
try saveLocal(token, account: serverID.uuidString)
|
||||
}
|
||||
|
||||
static func token(serverID: UUID) -> String? {
|
||||
readWithLegacyMigration(account: serverID.uuidString)
|
||||
}
|
||||
|
||||
static func saveProjectToken(_ token: String, serverID: UUID, projectKey: String) throws {
|
||||
try saveLocal(token, account: "project|\(serverID.uuidString)|\(projectKey)")
|
||||
}
|
||||
|
||||
static func projectToken(serverID: UUID, projectKey: String) -> String? {
|
||||
readWithLegacyMigration(account: "project|\(serverID.uuidString)|\(projectKey)")
|
||||
}
|
||||
|
||||
static func deleteToken(serverID: UUID) {
|
||||
deleteAll(account: serverID.uuidString)
|
||||
}
|
||||
|
||||
private static func query(account: String, synchronizable: CFTypeRef) -> [String: Any] {
|
||||
[kSecClass as String: kSecClassGenericPassword,
|
||||
kSecAttrService as String: service,
|
||||
kSecAttrAccount as String: account,
|
||||
kSecAttrSynchronizable as String: synchronizable]
|
||||
}
|
||||
|
||||
private static func saveLocal(_ value: String, account: String) throws {
|
||||
let clean = value.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
guard !clean.isEmpty else { throw KeychainError.emptyToken }
|
||||
let data = Data(clean.utf8)
|
||||
let localQuery = query(account: account, synchronizable: kCFBooleanFalse)
|
||||
let updateStatus = SecItemUpdate(localQuery as CFDictionary,
|
||||
[kSecValueData as String: data] as CFDictionary)
|
||||
if updateStatus == errSecSuccess { return }
|
||||
guard updateStatus == errSecItemNotFound else { throw KeychainError.status(updateStatus) }
|
||||
|
||||
var add = localQuery
|
||||
add[kSecValueData as String] = data
|
||||
add[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
|
||||
let status = SecItemAdd(add as CFDictionary, nil)
|
||||
guard status == errSecSuccess else { throw KeychainError.status(status) }
|
||||
}
|
||||
|
||||
private static func readWithLegacyMigration(account: String) -> String? {
|
||||
if let local = read(account: account, synchronizable: kCFBooleanFalse) { return local }
|
||||
guard let legacy = read(account: account, synchronizable: kSecAttrSynchronizableAny) else { return nil }
|
||||
// Best-effort migration from older iCloud/synchronizable Keychain entries.
|
||||
try? saveLocal(legacy, account: account)
|
||||
return legacy
|
||||
}
|
||||
|
||||
private static func read(account: String, synchronizable: CFTypeRef) -> String? {
|
||||
var lookup = query(account: account, synchronizable: synchronizable)
|
||||
lookup[kSecReturnData as String] = true
|
||||
lookup[kSecMatchLimit as String] = kSecMatchLimitOne
|
||||
var item: CFTypeRef?
|
||||
guard SecItemCopyMatching(lookup as CFDictionary, &item) == errSecSuccess,
|
||||
let data = item as? Data,
|
||||
let value = String(data: data, encoding: .utf8),
|
||||
!value.isEmpty else { return nil }
|
||||
return value
|
||||
}
|
||||
|
||||
private static func deleteAll(account: String) {
|
||||
SecItemDelete(query(account: account, synchronizable: kSecAttrSynchronizableAny) as CFDictionary)
|
||||
}
|
||||
}
|
||||
|
||||
enum KeychainError: LocalizedError {
|
||||
case emptyToken
|
||||
case status(OSStatus)
|
||||
|
||||
var errorDescription: String? {
|
||||
switch self {
|
||||
case .emptyToken: return "The SonarQube token cannot be empty."
|
||||
case .status(let status):
|
||||
return SecCopyErrorMessageString(status, nil) as String? ?? "Keychain error (\(status))."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension KeychainService {
|
||||
private static let licenseTokenAccount = "sonardesk.license-token"
|
||||
|
||||
static func saveLicenseToken(_ token: String) throws {
|
||||
try saveLocal(token, account: licenseTokenAccount)
|
||||
}
|
||||
|
||||
static func licenseToken() -> String? {
|
||||
readWithLegacyMigration(account: licenseTokenAccount)
|
||||
}
|
||||
|
||||
static func deleteLicenseToken() {
|
||||
deleteAll(account: licenseTokenAccount)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user