mirror of https://github.com/oxen-io/session-ios
WIP adding PNs for updated groups and changes for enabling unit testing
Started adding logic to subscribe and unsubscribe for updated group push notifications Updated the keychain service to be injected via dependencies Reworked the Dependencies logic to avoid a concurrent access issue Fixed an issue where some keychain data might not be cleared in some cases Fixed an issue where being kicked and readded to a group would make it seem to disappear ("invited" message wasn't getting created)pull/941/head
parent
0c1ea23b08
commit
32f5a18e00
@ -1,112 +0,0 @@
|
||||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
// stringlint:disable
|
||||
|
||||
import Foundation
|
||||
import SAMKeychain
|
||||
|
||||
public enum KeychainStorageError: Error {
|
||||
case failure(code: Int32?, description: String)
|
||||
|
||||
public var code: Int32? {
|
||||
switch self {
|
||||
case .failure(let code, _): return code
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
@objc public protocol SSKKeychainStorage: AnyObject {
|
||||
|
||||
@objc func string(forService service: String, key: String) throws -> String
|
||||
|
||||
@objc(setString:service:key:error:) func set(string: String, service: String, key: String) throws
|
||||
|
||||
@objc func data(forService service: String, key: String) throws -> Data
|
||||
|
||||
@objc func set(data: Data, service: String, key: String) throws
|
||||
|
||||
@objc func remove(service: String, key: String) throws
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
@objc
|
||||
public class SSKDefaultKeychainStorage: NSObject, SSKKeychainStorage {
|
||||
|
||||
@objc public static let shared = SSKDefaultKeychainStorage()
|
||||
|
||||
// Force usage as a singleton
|
||||
override private init() {
|
||||
super.init()
|
||||
}
|
||||
|
||||
@objc public func string(forService service: String, key: String) throws -> String {
|
||||
var error: NSError?
|
||||
let result = SAMKeychain.password(forService: service, account: key, error: &error)
|
||||
if let error = error {
|
||||
throw KeychainStorageError.failure(code: Int32(error.code), description: "\(logTag) error retrieving string: \(error)")
|
||||
}
|
||||
guard let string = result else {
|
||||
throw KeychainStorageError.failure(code: nil, description: "\(logTag) could not retrieve string")
|
||||
}
|
||||
return string
|
||||
}
|
||||
|
||||
@objc public func set(string: String, service: String, key: String) throws {
|
||||
|
||||
SAMKeychain.setAccessibilityType(kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly)
|
||||
|
||||
var error: NSError?
|
||||
let result = SAMKeychain.setPassword(string, forService: service, account: key, error: &error)
|
||||
if let error = error {
|
||||
throw KeychainStorageError.failure(code: Int32(error.code), description: "\(logTag) error setting string: \(error)")
|
||||
}
|
||||
guard result else {
|
||||
throw KeychainStorageError.failure(code: nil, description: "\(logTag) could not set string")
|
||||
}
|
||||
}
|
||||
|
||||
@objc public func data(forService service: String, key: String) throws -> Data {
|
||||
var error: NSError?
|
||||
let result = SAMKeychain.passwordData(forService: service, account: key, error: &error)
|
||||
if let error = error {
|
||||
throw KeychainStorageError.failure(code: Int32(error.code), description: "\(logTag) error retrieving data: \(error)")
|
||||
}
|
||||
guard let data = result else {
|
||||
throw KeychainStorageError.failure(code: nil, description: "\(logTag) could not retrieve data")
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
@objc public func set(data: Data, service: String, key: String) throws {
|
||||
|
||||
SAMKeychain.setAccessibilityType(kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly)
|
||||
|
||||
var error: NSError?
|
||||
let result = SAMKeychain.setPasswordData(data, forService: service, account: key, error: &error)
|
||||
if let error = error {
|
||||
throw KeychainStorageError.failure(code: Int32(error.code), description: "\(logTag) error setting data: \(error)")
|
||||
}
|
||||
guard result else {
|
||||
throw KeychainStorageError.failure(code: nil, description: "\(logTag) could not set data")
|
||||
}
|
||||
}
|
||||
|
||||
@objc public func remove(service: String, key: String) throws {
|
||||
var error: NSError?
|
||||
let result = SAMKeychain.deletePassword(forService: service, account: key, error: &error)
|
||||
if let error = error {
|
||||
// If deletion failed because the specified item could not be found in the keychain, consider it success.
|
||||
if error.code == errSecItemNotFound {
|
||||
return
|
||||
}
|
||||
throw KeychainStorageError.failure(code: Int32(error.code), description: "\(logTag) error removing data: \(error)")
|
||||
}
|
||||
guard result else {
|
||||
throw KeychainStorageError.failure(code: nil, description: "\(logTag) could not remove data")
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,195 @@
|
||||
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
|
||||
//
|
||||
// stringlint:disable
|
||||
|
||||
import Foundation
|
||||
import SAMKeychain
|
||||
|
||||
// MARK: - Singleton
|
||||
|
||||
public extension Singleton {
|
||||
static let keychain: SingletonConfig<KeychainStorageType> = Dependencies.create(
|
||||
identifier: "keychain",
|
||||
createInstance: { _ in KeychainStorage() }
|
||||
)
|
||||
}
|
||||
|
||||
public enum KeychainStorageError: Error {
|
||||
case failure(code: Int32?, description: String)
|
||||
|
||||
public var code: Int32? {
|
||||
switch self {
|
||||
case .failure(let code, _): return code
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - KeychainStorageType
|
||||
|
||||
public protocol KeychainStorageType {
|
||||
func string(forService service: KeychainStorage.ServiceKey, key: KeychainStorage.StringKey) throws -> String
|
||||
func set(string: String, service: KeychainStorage.ServiceKey, key: KeychainStorage.StringKey) throws
|
||||
func remove(service: KeychainStorage.ServiceKey, key: KeychainStorage.StringKey) throws
|
||||
|
||||
func data(forService service: KeychainStorage.ServiceKey, key: KeychainStorage.DataKey) throws -> Data
|
||||
func set(data: Data, service: KeychainStorage.ServiceKey, key: KeychainStorage.DataKey) throws
|
||||
func remove(service: KeychainStorage.ServiceKey, key: KeychainStorage.DataKey) throws
|
||||
|
||||
func removeAll()
|
||||
}
|
||||
|
||||
// MARK: - KeychainStorage
|
||||
|
||||
public class KeychainStorage: KeychainStorageType {
|
||||
public func string(forService service: KeychainStorage.ServiceKey, key: KeychainStorage.StringKey) throws -> String {
|
||||
var error: NSError?
|
||||
let result: String? = SAMKeychain.password(forService: service.rawValue, account: key.rawValue, error: &error)
|
||||
|
||||
switch (error, result) {
|
||||
case (.some(let error), _):
|
||||
throw KeychainStorageError.failure(
|
||||
code: Int32(error.code),
|
||||
description: "[KeychainStorage] Error retrieving string: \(error)"
|
||||
)
|
||||
|
||||
case (_, .none):
|
||||
throw KeychainStorageError.failure(code: nil, description: "[KeychainStorage] Could not retrieve string")
|
||||
|
||||
case (_, .some(let string)): return string
|
||||
}
|
||||
}
|
||||
|
||||
public func set(string: String, service: KeychainStorage.ServiceKey, key: KeychainStorage.StringKey) throws {
|
||||
SAMKeychain.setAccessibilityType(kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly)
|
||||
|
||||
var error: NSError?
|
||||
let result: Bool = SAMKeychain.setPassword(string, forService: service.rawValue, account: key.rawValue, error: &error)
|
||||
|
||||
switch (error, result) {
|
||||
case (.some(let error), _):
|
||||
throw KeychainStorageError.failure(
|
||||
code: Int32(error.code),
|
||||
description: "[KeychainStorage] Error setting string: \(error)"
|
||||
)
|
||||
|
||||
case (_, false):
|
||||
throw KeychainStorageError.failure(code: nil, description: "[KeychainStorage] Could not set string")
|
||||
|
||||
case (_, true): break
|
||||
}
|
||||
}
|
||||
|
||||
public func remove(service: KeychainStorage.ServiceKey, key: KeychainStorage.StringKey) throws {
|
||||
try remove(service: service.rawValue, key: key.rawValue)
|
||||
}
|
||||
|
||||
public func data(forService service: KeychainStorage.ServiceKey, key: KeychainStorage.DataKey) throws -> Data {
|
||||
var error: NSError?
|
||||
let result: Data? = SAMKeychain.passwordData(forService: service.rawValue, account: key.rawValue, error: &error)
|
||||
|
||||
switch (error, result) {
|
||||
case (.some(let error), _):
|
||||
throw KeychainStorageError.failure(
|
||||
code: Int32(error.code),
|
||||
description: "[KeychainStorage] Error retrieving data: \(error)"
|
||||
)
|
||||
|
||||
case (_, .none):
|
||||
throw KeychainStorageError.failure(code: nil, description: "[KeychainStorage] Could not retrieve data")
|
||||
|
||||
case (_, .some(let data)): return data
|
||||
}
|
||||
}
|
||||
|
||||
public func set(data: Data, service: KeychainStorage.ServiceKey, key: KeychainStorage.DataKey) throws {
|
||||
SAMKeychain.setAccessibilityType(kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly)
|
||||
|
||||
var error: NSError?
|
||||
let result: Bool = SAMKeychain.setPasswordData(data, forService: service.rawValue, account: key.rawValue, error: &error)
|
||||
|
||||
switch (error, result) {
|
||||
case (.some(let error), _):
|
||||
throw KeychainStorageError.failure(
|
||||
code: Int32(error.code),
|
||||
description: "[KeychainStorage] Error setting data: \(error)"
|
||||
)
|
||||
|
||||
case (_, false):
|
||||
throw KeychainStorageError.failure(code: nil, description: "[KeychainStorage] Could not set data")
|
||||
|
||||
case (_, true): break
|
||||
}
|
||||
}
|
||||
|
||||
public func remove(service: KeychainStorage.ServiceKey, key: KeychainStorage.DataKey) throws {
|
||||
try remove(service: service.rawValue, key: key.rawValue)
|
||||
}
|
||||
|
||||
private func remove(service: String, key: String) throws {
|
||||
var error: NSError?
|
||||
let result: Bool = SAMKeychain.deletePassword(forService: service, account: key, error: &error)
|
||||
|
||||
switch (error, result) {
|
||||
case (.some(let error), _):
|
||||
/// If deletion failed because the specified item could not be found in the keychain, consider it success
|
||||
guard error.code != errSecItemNotFound else { return }
|
||||
|
||||
throw KeychainStorageError.failure(
|
||||
code: Int32(error.code),
|
||||
description: "[KeychainStorage] Error removing data: \(error)"
|
||||
)
|
||||
|
||||
case (_, false):
|
||||
throw KeychainStorageError.failure(code: nil, description: "[KeychainStorage] Could not remove data")
|
||||
|
||||
case (_, true): break
|
||||
}
|
||||
}
|
||||
|
||||
public func removeAll() {
|
||||
let allData: [[String: Any]] = SAMKeychain.allAccounts().defaulting(to: [])
|
||||
|
||||
allData.forEach { keychainEntry in
|
||||
guard
|
||||
let service: String = keychainEntry[kSAMKeychainWhereKey] as? String,
|
||||
let key: String = keychainEntry[kSAMKeychainAccountKey] as? String
|
||||
else { return }
|
||||
|
||||
try? remove(service: service, key: key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Keys
|
||||
|
||||
public extension KeychainStorage {
|
||||
struct ServiceKey: RawRepresentable, ExpressibleByStringLiteral, Hashable {
|
||||
public let rawValue: String
|
||||
|
||||
public init(_ rawValue: String) { self.rawValue = rawValue }
|
||||
public init?(rawValue: String) { self.rawValue = rawValue }
|
||||
public init(stringLiteral value: String) { self.init(value) }
|
||||
public init(unicodeScalarLiteral value: String) { self.init(value) }
|
||||
public init(extendedGraphemeClusterLiteral value: String) { self.init(value) }
|
||||
}
|
||||
|
||||
struct DataKey: RawRepresentable, ExpressibleByStringLiteral, Hashable {
|
||||
public let rawValue: String
|
||||
|
||||
public init(_ rawValue: String) { self.rawValue = rawValue }
|
||||
public init?(rawValue: String) { self.rawValue = rawValue }
|
||||
public init(stringLiteral value: String) { self.init(value) }
|
||||
public init(unicodeScalarLiteral value: String) { self.init(value) }
|
||||
public init(extendedGraphemeClusterLiteral value: String) { self.init(value) }
|
||||
}
|
||||
|
||||
struct StringKey: RawRepresentable, ExpressibleByStringLiteral, Hashable {
|
||||
public let rawValue: String
|
||||
|
||||
public init(_ rawValue: String) { self.rawValue = rawValue }
|
||||
public init?(rawValue: String) { self.rawValue = rawValue }
|
||||
public init(stringLiteral value: String) { self.init(value) }
|
||||
public init(unicodeScalarLiteral value: String) { self.init(value) }
|
||||
public init(extendedGraphemeClusterLiteral value: String) { self.init(value) }
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue