diff --git a/Development Assets/MockKeychainService.swift b/Development Assets/MockKeychainService.swift index 341a7ff..743aef5 100644 --- a/Development Assets/MockKeychainService.swift +++ b/Development Assets/MockKeychainService.swift @@ -30,6 +30,10 @@ extension MockKeychainService: KeychainService { static func getPrivateKey(applicationTag: String, attributes: [String: Any]) throws -> Data? { fatalError("not implemented") } + + static func deleteKey(applicationTag: String) throws { + fatalError("not implemented") + } } private extension MockKeychainService { diff --git a/Notification Service Extension/NotificationService.swift b/Notification Service Extension/NotificationService.swift index 5ee0e32..5632edc 100644 --- a/Notification Service Extension/NotificationService.swift +++ b/Notification Service Extension/NotificationService.swift @@ -89,7 +89,7 @@ private extension NotificationService { let serverPublicKeyData = Data(base64Encoded: serverPublicKeyBase64) else { throw NotificationServiceError.userInfoDataAbsent } - let secretsService = SecretsService(identityID: identityID, keychainServiceType: LiveKeychainService.self) + let secretsService = SecretsService(identityID: identityID, keychainService: LiveKeychainService.self) guard let auth = try secretsService.getPushAuth(), diff --git a/Shared/Services/IdentitiesService.swift b/Shared/Services/IdentitiesService.swift index 69266f0..5a8c961 100644 --- a/Shared/Services/IdentitiesService.swift +++ b/Shared/Services/IdentitiesService.swift @@ -31,7 +31,7 @@ extension IdentitiesService { } func authorizeIdentity(id: UUID, instanceURL: URL) -> AnyPublisher { - let secretsService = SecretsService(identityID: id, keychainServiceType: environment.keychainServiceType) + let secretsService = SecretsService(identityID: id, keychainService: environment.keychainServiceType) let authenticationService = AuthenticationService(environment: environment) return authenticationService.authorizeApp(instanceURL: instanceURL) @@ -57,7 +57,7 @@ extension IdentitiesService { .tryMap { _ -> Void in try SecretsService( identityID: id, - keychainServiceType: environment.keychainServiceType) + keychainService: environment.keychainServiceType) .deleteAllItems() return () diff --git a/Shared/Services/IdentityService.swift b/Shared/Services/IdentityService.swift index 5b7ca38..deca3f3 100644 --- a/Shared/Services/IdentityService.swift +++ b/Shared/Services/IdentityService.swift @@ -32,7 +32,7 @@ class IdentityService { self.identity = identity secretsService = SecretsService( identityID: identityID, - keychainServiceType: environment.keychainServiceType) + keychainService: environment.keychainServiceType) networkClient = MastodonClient(session: environment.session) networkClient.instanceURL = identity.url networkClient.accessToken = try secretsService.item(.accessToken) diff --git a/Shared/Services/KeychainService.swift b/Shared/Services/KeychainService.swift index 23310ce..e1e5431 100644 --- a/Shared/Services/KeychainService.swift +++ b/Shared/Services/KeychainService.swift @@ -8,6 +8,7 @@ protocol KeychainService { static func getGenericPassword(account: String, service: String) throws -> Data? static func generateKeyAndReturnPublicKey(applicationTag: String, attributes: [String: Any]) throws -> Data static func getPrivateKey(applicationTag: String, attributes: [String: Any]) throws -> Data? + static func deleteKey(applicationTag: String) throws } struct LiveKeychainService {} @@ -104,6 +105,14 @@ extension LiveKeychainService: KeychainService { throw NSError(status: status) } } + + static func deleteKey(applicationTag: String) throws { + let status = SecItemDelete(keyQueryDictionary(applicationTag: applicationTag) as CFDictionary) + + if status != errSecSuccess { + throw NSError(status: status) + } + } } private extension LiveKeychainService { diff --git a/Shared/Services/SecretsService.swift b/Shared/Services/SecretsService.swift index 29f5907..80e6a30 100644 --- a/Shared/Services/SecretsService.swift +++ b/Shared/Services/SecretsService.swift @@ -13,11 +13,11 @@ enum SecretsStorableError: Error { struct SecretsService { let identityID: UUID - private let keychainServiceType: KeychainService.Type + private let keychainService: KeychainService.Type - init(identityID: UUID, keychainServiceType: KeychainService.Type) { + init(identityID: UUID, keychainService: KeychainService.Type) { self.identityID = identityID - self.keychainServiceType = keychainServiceType + self.keychainService = keychainService } } @@ -31,16 +31,30 @@ extension SecretsService { } } +extension SecretsService.Item { + enum Kind { + case genericPassword + case key + } + + var kind: Kind { + switch self { + case .pushKey: return .key + default: return .genericPassword + } + } +} + extension SecretsService { func set(_ data: SecretsStorable, forItem item: Item) throws { - try keychainServiceType.setGenericPassword( + try keychainService.setGenericPassword( data: data.dataStoredInSecrets, forAccount: key(item: item), service: Self.keychainServiceName) } func item(_ item: Item) throws -> T? { - guard let data = try keychainServiceType.getGenericPassword( + guard let data = try keychainService.getGenericPassword( account: key(item: item), service: Self.keychainServiceName) else { return nil @@ -51,20 +65,25 @@ extension SecretsService { func deleteAllItems() throws { for item in SecretsService.Item.allCases { - try keychainServiceType.deleteGenericPassword( - account: key(item: item), - service: Self.keychainServiceName) + switch item.kind { + case .genericPassword: + try keychainService.deleteGenericPassword( + account: key(item: item), + service: Self.keychainServiceName) + case .key: + try keychainService.deleteKey(applicationTag: key(item: item)) + } } } func generatePushKeyAndReturnPublicKey() throws -> Data { - try keychainServiceType.generateKeyAndReturnPublicKey( + try keychainService.generateKeyAndReturnPublicKey( applicationTag: key(item: .pushKey), attributes: PushKey.attributes) } func getPushKey() throws -> Data? { - try keychainServiceType.getPrivateKey( + try keychainService.getPrivateKey( applicationTag: key(item: .pushKey), attributes: PushKey.attributes) } diff --git a/Shared/View Models/RootViewModel.swift b/Shared/View Models/RootViewModel.swift index 27a5117..19fc040 100644 --- a/Shared/View Models/RootViewModel.swift +++ b/Shared/View Models/RootViewModel.swift @@ -72,7 +72,7 @@ extension RootViewModel { func deleteIdentity(id: UUID) { identitiesService.deleteIdentity(id: id) - .sink(receiveCompletion: { _ in }, receiveValue: {}) + .sink { _ in } receiveValue: { _ in } .store(in: &cancellables) } diff --git a/Tests/View Models/AddIdentityViewModelTests.swift b/Tests/View Models/AddIdentityViewModelTests.swift index 72f5642..86e4752 100644 --- a/Tests/View Models/AddIdentityViewModelTests.swift +++ b/Tests/View Models/AddIdentityViewModelTests.swift @@ -9,29 +9,23 @@ class AddIdentityViewModelTests: XCTestCase { func testAddIdentity() throws { let identityDatabase = IdentityDatabase.fresh() let sut = AddIdentityViewModel(identitiesService: .fresh(identityDatabase: identityDatabase)) - let addedIDAndURLRecorder = sut.addedIdentityIDAndURL.record() + let addedIDRecorder = sut.addedIdentityID.record() sut.urlFieldText = "https://mastodon.social" sut.logInTapped() - let addedIdentityIDAndURL = try wait(for: addedIDAndURLRecorder.next(), timeout: 1) - -// XCTAssertEqual(addedIdentityIDAndURL.0, addedIdentityID) - XCTAssertEqual(addedIdentityIDAndURL.1, URL(string: "https://mastodon.social")!) + _ = try wait(for: addedIDRecorder.next(), timeout: 1) } func testAddIdentityWithoutScheme() throws { let identityDatabase = IdentityDatabase.fresh() let sut = AddIdentityViewModel(identitiesService: .fresh(identityDatabase: identityDatabase)) - let addedIDAndURLRecorder = sut.addedIdentityIDAndURL.record() + let addedIDRecorder = sut.addedIdentityID.record() sut.urlFieldText = "mastodon.social" sut.logInTapped() - let addedIdentityIDAndURL = try wait(for: addedIDAndURLRecorder.next(), timeout: 1) - -// XCTAssertEqual(addedIdentityIDAndURL.0, addedIdentityID) - XCTAssertEqual(addedIdentityIDAndURL.1, URL(string: "https://mastodon.social")!) + _ = try wait(for: addedIDRecorder.next(), timeout: 1) } func testInvalidURL() throws { diff --git a/Tests/View Models/RootViewModelTests.swift b/Tests/View Models/RootViewModelTests.swift index e19f97d..c25cde7 100644 --- a/Tests/View Models/RootViewModelTests.swift +++ b/Tests/View Models/RootViewModelTests.swift @@ -20,8 +20,8 @@ class RootViewModelTests: XCTestCase { let addIdentityViewModel = sut.addIdentityViewModel() - addIdentityViewModel.addedIdentityIDAndURL - .sink(receiveValue: sut.newIdentityCreated(id:instanceURL:)) + addIdentityViewModel.addedIdentityID + .sink(receiveValue: sut.newIdentitySelected(id:)) .store(in: &cancellables) addIdentityViewModel.urlFieldText = "https://mastodon.social"