Refactoring

This commit is contained in:
Justin Mazzocchi 2020-08-13 18:59:17 -07:00
parent 43ccc12468
commit 4bcb862065
No known key found for this signature in database
GPG Key ID: E223E6937AAFB01C
9 changed files with 53 additions and 27 deletions

View File

@ -30,6 +30,10 @@ extension MockKeychainService: KeychainService {
static func getPrivateKey(applicationTag: String, attributes: [String: Any]) throws -> Data? { static func getPrivateKey(applicationTag: String, attributes: [String: Any]) throws -> Data? {
fatalError("not implemented") fatalError("not implemented")
} }
static func deleteKey(applicationTag: String) throws {
fatalError("not implemented")
}
} }
private extension MockKeychainService { private extension MockKeychainService {

View File

@ -89,7 +89,7 @@ private extension NotificationService {
let serverPublicKeyData = Data(base64Encoded: serverPublicKeyBase64) let serverPublicKeyData = Data(base64Encoded: serverPublicKeyBase64)
else { throw NotificationServiceError.userInfoDataAbsent } else { throw NotificationServiceError.userInfoDataAbsent }
let secretsService = SecretsService(identityID: identityID, keychainServiceType: LiveKeychainService.self) let secretsService = SecretsService(identityID: identityID, keychainService: LiveKeychainService.self)
guard guard
let auth = try secretsService.getPushAuth(), let auth = try secretsService.getPushAuth(),

View File

@ -31,7 +31,7 @@ extension IdentitiesService {
} }
func authorizeIdentity(id: UUID, instanceURL: URL) -> AnyPublisher<Void, Error> { func authorizeIdentity(id: UUID, instanceURL: URL) -> AnyPublisher<Void, Error> {
let secretsService = SecretsService(identityID: id, keychainServiceType: environment.keychainServiceType) let secretsService = SecretsService(identityID: id, keychainService: environment.keychainServiceType)
let authenticationService = AuthenticationService(environment: environment) let authenticationService = AuthenticationService(environment: environment)
return authenticationService.authorizeApp(instanceURL: instanceURL) return authenticationService.authorizeApp(instanceURL: instanceURL)
@ -57,7 +57,7 @@ extension IdentitiesService {
.tryMap { _ -> Void in .tryMap { _ -> Void in
try SecretsService( try SecretsService(
identityID: id, identityID: id,
keychainServiceType: environment.keychainServiceType) keychainService: environment.keychainServiceType)
.deleteAllItems() .deleteAllItems()
return () return ()

View File

@ -32,7 +32,7 @@ class IdentityService {
self.identity = identity self.identity = identity
secretsService = SecretsService( secretsService = SecretsService(
identityID: identityID, identityID: identityID,
keychainServiceType: environment.keychainServiceType) keychainService: environment.keychainServiceType)
networkClient = MastodonClient(session: environment.session) networkClient = MastodonClient(session: environment.session)
networkClient.instanceURL = identity.url networkClient.instanceURL = identity.url
networkClient.accessToken = try secretsService.item(.accessToken) networkClient.accessToken = try secretsService.item(.accessToken)

View File

@ -8,6 +8,7 @@ protocol KeychainService {
static func getGenericPassword(account: String, service: String) throws -> Data? static func getGenericPassword(account: String, service: String) throws -> Data?
static func generateKeyAndReturnPublicKey(applicationTag: String, attributes: [String: Any]) throws -> Data static func generateKeyAndReturnPublicKey(applicationTag: String, attributes: [String: Any]) throws -> Data
static func getPrivateKey(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 {} struct LiveKeychainService {}
@ -104,6 +105,14 @@ extension LiveKeychainService: KeychainService {
throw NSError(status: status) 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 { private extension LiveKeychainService {

View File

@ -13,11 +13,11 @@ enum SecretsStorableError: Error {
struct SecretsService { struct SecretsService {
let identityID: UUID 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.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 { extension SecretsService {
func set(_ data: SecretsStorable, forItem item: Item) throws { func set(_ data: SecretsStorable, forItem item: Item) throws {
try keychainServiceType.setGenericPassword( try keychainService.setGenericPassword(
data: data.dataStoredInSecrets, data: data.dataStoredInSecrets,
forAccount: key(item: item), forAccount: key(item: item),
service: Self.keychainServiceName) service: Self.keychainServiceName)
} }
func item<T: SecretsStorable>(_ item: Item) throws -> T? { func item<T: SecretsStorable>(_ item: Item) throws -> T? {
guard let data = try keychainServiceType.getGenericPassword( guard let data = try keychainService.getGenericPassword(
account: key(item: item), account: key(item: item),
service: Self.keychainServiceName) else { service: Self.keychainServiceName) else {
return nil return nil
@ -51,20 +65,25 @@ extension SecretsService {
func deleteAllItems() throws { func deleteAllItems() throws {
for item in SecretsService.Item.allCases { for item in SecretsService.Item.allCases {
try keychainServiceType.deleteGenericPassword( switch item.kind {
account: key(item: item), case .genericPassword:
service: Self.keychainServiceName) try keychainService.deleteGenericPassword(
account: key(item: item),
service: Self.keychainServiceName)
case .key:
try keychainService.deleteKey(applicationTag: key(item: item))
}
} }
} }
func generatePushKeyAndReturnPublicKey() throws -> Data { func generatePushKeyAndReturnPublicKey() throws -> Data {
try keychainServiceType.generateKeyAndReturnPublicKey( try keychainService.generateKeyAndReturnPublicKey(
applicationTag: key(item: .pushKey), applicationTag: key(item: .pushKey),
attributes: PushKey.attributes) attributes: PushKey.attributes)
} }
func getPushKey() throws -> Data? { func getPushKey() throws -> Data? {
try keychainServiceType.getPrivateKey( try keychainService.getPrivateKey(
applicationTag: key(item: .pushKey), applicationTag: key(item: .pushKey),
attributes: PushKey.attributes) attributes: PushKey.attributes)
} }

View File

@ -72,7 +72,7 @@ extension RootViewModel {
func deleteIdentity(id: UUID) { func deleteIdentity(id: UUID) {
identitiesService.deleteIdentity(id: id) identitiesService.deleteIdentity(id: id)
.sink(receiveCompletion: { _ in }, receiveValue: {}) .sink { _ in } receiveValue: { _ in }
.store(in: &cancellables) .store(in: &cancellables)
} }

View File

@ -9,29 +9,23 @@ class AddIdentityViewModelTests: XCTestCase {
func testAddIdentity() throws { func testAddIdentity() throws {
let identityDatabase = IdentityDatabase.fresh() let identityDatabase = IdentityDatabase.fresh()
let sut = AddIdentityViewModel(identitiesService: .fresh(identityDatabase: identityDatabase)) let sut = AddIdentityViewModel(identitiesService: .fresh(identityDatabase: identityDatabase))
let addedIDAndURLRecorder = sut.addedIdentityIDAndURL.record() let addedIDRecorder = sut.addedIdentityID.record()
sut.urlFieldText = "https://mastodon.social" sut.urlFieldText = "https://mastodon.social"
sut.logInTapped() sut.logInTapped()
let addedIdentityIDAndURL = try wait(for: addedIDAndURLRecorder.next(), timeout: 1) _ = try wait(for: addedIDRecorder.next(), timeout: 1)
// XCTAssertEqual(addedIdentityIDAndURL.0, addedIdentityID)
XCTAssertEqual(addedIdentityIDAndURL.1, URL(string: "https://mastodon.social")!)
} }
func testAddIdentityWithoutScheme() throws { func testAddIdentityWithoutScheme() throws {
let identityDatabase = IdentityDatabase.fresh() let identityDatabase = IdentityDatabase.fresh()
let sut = AddIdentityViewModel(identitiesService: .fresh(identityDatabase: identityDatabase)) let sut = AddIdentityViewModel(identitiesService: .fresh(identityDatabase: identityDatabase))
let addedIDAndURLRecorder = sut.addedIdentityIDAndURL.record() let addedIDRecorder = sut.addedIdentityID.record()
sut.urlFieldText = "mastodon.social" sut.urlFieldText = "mastodon.social"
sut.logInTapped() sut.logInTapped()
let addedIdentityIDAndURL = try wait(for: addedIDAndURLRecorder.next(), timeout: 1) _ = try wait(for: addedIDRecorder.next(), timeout: 1)
// XCTAssertEqual(addedIdentityIDAndURL.0, addedIdentityID)
XCTAssertEqual(addedIdentityIDAndURL.1, URL(string: "https://mastodon.social")!)
} }
func testInvalidURL() throws { func testInvalidURL() throws {

View File

@ -20,8 +20,8 @@ class RootViewModelTests: XCTestCase {
let addIdentityViewModel = sut.addIdentityViewModel() let addIdentityViewModel = sut.addIdentityViewModel()
addIdentityViewModel.addedIdentityIDAndURL addIdentityViewModel.addedIdentityID
.sink(receiveValue: sut.newIdentityCreated(id:instanceURL:)) .sink(receiveValue: sut.newIdentitySelected(id:))
.store(in: &cancellables) .store(in: &cancellables)
addIdentityViewModel.urlFieldText = "https://mastodon.social" addIdentityViewModel.urlFieldText = "https://mastodon.social"