diff --git a/Domain/Sources/BBs/Pluto.swift b/Domain/Sources/BBs/Pluto.swift index 8ead464e..341e389c 100644 --- a/Domain/Sources/BBs/Pluto.swift +++ b/Domain/Sources/BBs/Pluto.swift @@ -1,15 +1,15 @@ import Foundation +import Combine -protocol Pluto { - func storeSeed(seed: Seed) async throws -> Session +public protocol Pluto { func storeDID( - session: Session, did: DID, keyPairIndex: Int, alias: String? - ) async throws + ) -> AnyPublisher - func getSession() async -> Session - func getDID(alias: String) async -> DID - func getDIDKeyPairIndex(did: DID) async -> Int + func getAllDIDs() -> AnyPublisher<[(did: DID, keyPairIndex: Int, alias: String?)], Error> + func getDIDInfo(did: DID) -> AnyPublisher<(did: DID, keyPairIndex: Int, alias: String?)?, Error> + func getDIDInfo(alias: String) -> AnyPublisher<[(did: DID, keyPairIndex: Int)], Error> + func getDIDKeyPairIndex(did: DID) -> AnyPublisher } diff --git a/Experiences/Tests/ExperiencesTest.swift b/Experiences/Tests/ExperiencesTest.swift deleted file mode 100644 index 1a0a45df..00000000 --- a/Experiences/Tests/ExperiencesTest.swift +++ /dev/null @@ -1 +0,0 @@ -// This file is just here as a foolproof/template since for Swift Packages a module source always have to have at least one Swift file diff --git a/Pluto/Sources/Helpers/Keychain/KeychainStorage.swift b/Pluto/Sources/Helpers/Keychain/KeychainStorage.swift deleted file mode 100644 index 3efeb469..00000000 --- a/Pluto/Sources/Helpers/Keychain/KeychainStorage.swift +++ /dev/null @@ -1,37 +0,0 @@ -import Foundation - -enum KeychainWrapperError: Error { - case serviceError(status: OSStatus) - case wrongType - case itemNotFound - case itemDuplicated - case pwAccessCreationError - case authFailed -} - -enum SecurityTypeDomain { - case none - case password(String) - case biometric -} - -protocol KeychainStorage { - func set(_ value: Data, forKey key: String) throws - func set( - _ value: Data, - forKey key: String, - security: SecurityTypeDomain - ) throws - - func get(key: String) throws -> Data - func get( - key: String, - security: SecurityTypeDomain - ) throws -> Data - - func delete(key: String) throws - func delete( - key: String, - security: SecurityTypeDomain - ) throws -} diff --git a/Pluto/Sources/Helpers/Keychain/KeychainStorageImpl.swift b/Pluto/Sources/Helpers/Keychain/KeychainStorageImpl.swift deleted file mode 100644 index 164629bf..00000000 --- a/Pluto/Sources/Helpers/Keychain/KeychainStorageImpl.swift +++ /dev/null @@ -1,252 +0,0 @@ -import Foundation -import LocalAuthentication - -struct KeychainStorageImpl: KeychainStorage { - let service: String - let accessGroup: String? - - func set(_ value: Data, forKey key: String) throws { - guard - let encodedIdentifier = key.data(using: String.Encoding.utf8) - else { return } - let query: [String: Any] = [ - kSecClass as String: kSecClassGenericPassword, - kSecAttrGeneric as String: encodedIdentifier, - kSecAttrAccount as String: encodedIdentifier, - kSecAttrService as String: service - ] - - let attributes: [String: Any] = [ - kSecValueData as String: value - ] - - do { - try createItem(query: query.merging(attributes, uniquingKeysWith: { _, new in - new - })) - } catch KeychainWrapperError.itemDuplicated { - try updateItem(query: query, attributes: attributes) - } catch { - throw error - } - } - - func set( - _ value: Data, - forKey key: String, - security: SecurityTypeDomain - ) throws { - guard - let encodedIdentifier = key.data(using: String.Encoding.utf8) - else { return } - - var query: [String: Any] = [ - kSecClass as String: kSecClassGenericPassword, - kSecAttrGeneric as String: encodedIdentifier, - kSecAttrAccount as String: encodedIdentifier, - kSecAttrService as String: service - ] - - switch security { - case .none: - break - case let .password(password): - let context = LAContext() - context.setCredential(password.data(using: .utf8), type: .applicationPassword) - query[kSecAttrAccessControl as String] = try getPwSecAccessControl() - query[kSecUseAuthenticationContext as String] = context - case .biometric: - query[kSecAttrAccessControl as String] = try getBiometricsSecAccessControl() - } - - let attributes: [String: Any] = [ - kSecValueData as String: value - ] - - do { - try createItem(query: query.merging(attributes, uniquingKeysWith: { _, new in - new - })) - } catch KeychainWrapperError.itemDuplicated { - try updateItem(query: query, attributes: attributes) - } catch { - print(error.localizedDescription) - throw error - } - } - - func get(key: String) throws -> Data { - let query: [String: Any] = [ - kSecClass as String: kSecClassGenericPassword, - kSecAttrAccount as String: key, - kSecAttrService as String: service, - kSecMatchLimit as String: kSecMatchLimitOne, - kSecReturnAttributes as String: true, - kSecReturnData as String: true - ] - - var item: CFTypeRef? - let status = SecItemCopyMatching(query as CFDictionary, &item) - - switch status { - case errSecSuccess: - guard - let existingItem = item as? [String: Any], - let value = existingItem[kSecValueData as String] as? Data - else { throw KeychainWrapperError.wrongType } - - return value - case errSecItemNotFound: - throw KeychainWrapperError.itemNotFound - case errSecAuthFailed: - throw KeychainWrapperError.authFailed - default: - throw KeychainWrapperError.serviceError(status: status) - } - } - - func get( - key: String, - security: SecurityTypeDomain - ) throws -> Data { - var query: [String: Any] = [ - kSecClass as String: kSecClassGenericPassword, - kSecAttrAccount as String: key, - kSecAttrService as String: service, - kSecMatchLimit as String: kSecMatchLimitOne, - kSecReturnAttributes as String: true, - kSecReturnData as String: true - ] - - switch security { - case .none: - break - case .biometric: - query[kSecAttrAccessControl as String] = try getBiometricsSecAccessControl() - case let .password(password): - let context = LAContext() - context.setCredential(password.data(using: .utf8), type: .applicationPassword) - query[kSecAttrAccessControl as String] = try getPwSecAccessControl() - query[kSecUseAuthenticationContext as String] = context - } - - var item: CFTypeRef? - let status = SecItemCopyMatching(query as CFDictionary, &item) - - switch status { - case errSecSuccess: - guard - let existingItem = item as? [String: Any], - let value = existingItem[kSecValueData as String] as? Data - else { throw KeychainWrapperError.wrongType } - - return value - case errSecItemNotFound: - throw KeychainWrapperError.itemNotFound - case errSecAuthFailed: - throw KeychainWrapperError.authFailed - default: - throw KeychainWrapperError.serviceError(status: status) - } - } - - func delete(key: String) throws { - let query: [String: Any] = [ - kSecClass as String: kSecClassGenericPassword, - kSecAttrAccount as String: key, - kSecAttrService as String: service - ] - - let status = SecItemDelete(query as CFDictionary) - guard status == errSecSuccess || status == errSecItemNotFound else { - throw KeychainWrapperError.serviceError(status: status) - } - } - - func delete(key: String, security: SecurityTypeDomain) throws { - var query: [String: Any] = [ - kSecClass as String: kSecClassGenericPassword, - kSecAttrAccount as String: key, - kSecAttrService as String: service - ] - - switch security { - case .none: - break - case let .password(password): - let context = LAContext() - context.setCredential(password.data(using: .utf8), type: .applicationPassword) - query[kSecAttrAccessControl as String] = try getPwSecAccessControl() - query[kSecUseAuthenticationContext as String] = context - case .biometric: - query[kSecAttrAccessControl as String] = try getBiometricsSecAccessControl() - } - - let status = SecItemDelete(query as CFDictionary) - guard status == errSecSuccess || status == errSecItemNotFound else { - throw KeychainWrapperError.serviceError(status: status) - } - } - - private func createItem(query: [String: Any]) throws { - let status = SecItemAdd(query as CFDictionary, nil) - switch status { - case errSecSuccess: - break - case errSecDuplicateItem: - throw KeychainWrapperError.itemDuplicated - case errSecAuthFailed: - throw KeychainWrapperError.authFailed - case errSecParam: - throw KeychainWrapperError.serviceError(status: status) - default: - throw KeychainWrapperError.serviceError(status: status) - } - } - - private func updateItem(query: [String: Any], attributes: [String: Any]) throws { - let status = SecItemUpdate(query as CFDictionary, attributes as CFDictionary) - switch status { - case errSecSuccess: - return - case errSecItemNotFound: - throw KeychainWrapperError.itemNotFound - case errSecAuthFailed: - throw KeychainWrapperError.authFailed - default: - throw KeychainWrapperError.serviceError(status: status) - } - } - - private func getPwSecAccessControl() throws -> SecAccessControl { - var error: Unmanaged? - guard - let access = SecAccessControlCreateWithFlags( - nil, - kSecAttrAccessibleWhenUnlockedThisDeviceOnly, - [.applicationPassword], - &error - ) - else { - throw KeychainWrapperError.pwAccessCreationError - } - - return access - } - - private func getBiometricsSecAccessControl() throws -> SecAccessControl { - var error: Unmanaged? - guard - let access = SecAccessControlCreateWithFlags( - nil, - kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, - [.userPresence], - &error - ) - else { - throw KeychainWrapperError.pwAccessCreationError - } - - return access - } -} diff --git a/Pluto/Sources/Pluto.swift b/Pluto/Sources/Pluto.swift deleted file mode 100644 index 0681fd0f..00000000 --- a/Pluto/Sources/Pluto.swift +++ /dev/null @@ -1,36 +0,0 @@ -public struct PlutoImpl { - public struct PlutoSetup { - public struct KeychainSetup { - public let service: String - public let accessGroup: String? - - public init( - service: String = "com.atala.prism.session", - accessGroup: String? = nil - ) { - self.service = service - self.accessGroup = accessGroup - } - } - - public let keychainSetup: KeychainSetup - public let coreDataSetup: CoreDataManager.CoreDataSetup - - public init( - keychainSetup: KeychainSetup, - coreDataSetup: CoreDataManager.CoreDataSetup = .init( - modelPath: .storeName("com.atala.prism.storage"), - storeType: .persistent - ) - ) { - self.keychainSetup = keychainSetup - self.coreDataSetup = coreDataSetup - } - } - - let setup: PlutoSetup - - public init(setup: PlutoSetup = .init(keychainSetup: .init())) { - self.setup = setup - } -} diff --git a/Pluto/Sources/PlutoImpl+Public.swift b/Pluto/Sources/PlutoImpl+Public.swift new file mode 100644 index 00000000..ae706d7b --- /dev/null +++ b/Pluto/Sources/PlutoImpl+Public.swift @@ -0,0 +1,28 @@ +import Combine +import Domain + +extension PlutoImpl: Pluto { + public func storeDID(did: DID, keyPairIndex: Int, alias: String?) -> AnyPublisher { + registeredDIDDao.addDID(did: did, keyPairIndex: keyPairIndex, alias: alias) + } + + public func getAllDIDs() -> AnyPublisher<[(did: DID, keyPairIndex: Int, alias: String?)], Error> { + registeredDIDDao.getAll() + } + + public func getDIDInfo(did: DID) -> AnyPublisher<(did: DID, keyPairIndex: Int, alias: String?)?, Error> { + registeredDIDDao.getDIDInfo(did: did) + } + + public func getDIDInfo(alias: String) -> AnyPublisher<[(did: DID, keyPairIndex: Int)], Error> { + registeredDIDDao.getDIDInfo(alias: alias) + .map { $0.map { ($0.did, $0.keyPairIndex) } } + .eraseToAnyPublisher() + } + + public func getDIDKeyPairIndex(did: DID) -> AnyPublisher { + getDIDInfo(did: did) + .map { $0?.keyPairIndex } + .eraseToAnyPublisher() + } + } diff --git a/Pluto/Sources/PlutoImpl.swift b/Pluto/Sources/PlutoImpl.swift new file mode 100644 index 00000000..ff19bd90 --- /dev/null +++ b/Pluto/Sources/PlutoImpl.swift @@ -0,0 +1,30 @@ +import Domain + +public struct PlutoImpl { + public struct PlutoSetup { + public let coreDataSetup: CoreDataManager.CoreDataSetup + + public init( + coreDataSetup: CoreDataManager.CoreDataSetup = .init( + modelPath: .storeName("com.atala.prism.storage"), + storeType: .persistent + ) + ) { + self.coreDataSetup = coreDataSetup + } + } + + let setup: PlutoSetup + let registeredDIDDao: CDRegisteredDIDDAO + private let coreDataManager: CoreDataManager + + public init(setup: PlutoSetup = .init()) { + let manager = CoreDataManager(setup: setup.coreDataSetup) + self.setup = setup + self.coreDataManager = manager + self.registeredDIDDao = CDRegisteredDIDDAO( + readContext: manager.mainContext, + writeContext: manager.editContext + ) + } +}