From f5cc39f2565e5d85edacdaabbbb1b22032710533 Mon Sep 17 00:00:00 2001 From: Justin Mazzocchi <2831158+jzzocc@users.noreply.github.com> Date: Thu, 3 Sep 2020 23:44:04 -0700 Subject: [PATCH] Refactoring --- .../DB/Extensions/Secrets+Extensions.swift | 40 ------- Secrets/Sources/Secrets/Secrets.swift | 107 +++++++++++++----- .../Services/AllIdentitiesService.swift | 12 +- .../Services/IdentityService.swift | 2 +- 4 files changed, 88 insertions(+), 73 deletions(-) delete mode 100644 DB/Sources/DB/Extensions/Secrets+Extensions.swift diff --git a/DB/Sources/DB/Extensions/Secrets+Extensions.swift b/DB/Sources/DB/Extensions/Secrets+Extensions.swift deleted file mode 100644 index 7ab4295..0000000 --- a/DB/Sources/DB/Extensions/Secrets+Extensions.swift +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright © 2020 Metabolist. All rights reserved. - -import Foundation -import Keychain -import Secrets - -extension Secrets { - private static let passphraseByteCount = 64 - - static func databasePassphrase(identityID: UUID?, keychain: Keychain.Type) throws -> String { - let scopedSecrets: Secrets? - - if let identityID = identityID { - scopedSecrets = Secrets(identityID: identityID, keychain: keychain) - } else { - scopedSecrets = nil - } - - do { - return try scopedSecrets?.item(.databasePassphrase) ?? unscopedItem(.databasePassphrase, keychain: keychain) - } catch SecretsError.itemAbsent { - var bytes = [Int8](repeating: 0, count: passphraseByteCount) - let status = SecRandomCopyBytes(kSecRandomDefault, passphraseByteCount, &bytes) - - if status == errSecSuccess { - let passphrase = Data(bytes: bytes, count: passphraseByteCount).base64EncodedString() - - if let scopedSecrets = scopedSecrets { - try scopedSecrets.set(passphrase, forItem: .databasePassphrase) - } else { - try setUnscoped(passphrase, forItem: .databasePassphrase, keychain: keychain) - } - - return passphrase - } else { - throw NSError(status: status) - } - } - } -} diff --git a/Secrets/Sources/Secrets/Secrets.swift b/Secrets/Sources/Secrets/Secrets.swift index 87e38e9..90d96b7 100644 --- a/Secrets/Sources/Secrets/Secrets.swift +++ b/Secrets/Sources/Secrets/Secrets.swift @@ -52,38 +52,35 @@ extension Secrets.Item { } public extension Secrets { - static func setUnscoped(_ data: SecretsStorable, forItem item: Item, keychain: Keychain.Type) throws { - try keychain.setGenericPassword( - data: data.dataStoredInSecrets, - forAccount: item.rawValue, - service: keychainServiceName) - } + static func databasePassphrase(identityID: UUID?, keychain: Keychain.Type) throws -> String { + let scopedSecrets: Secrets? - static func unscopedItem(_ item: Item, keychain: Keychain.Type) throws -> T { - guard let data = try keychain.getGenericPassword( - account: item.rawValue, - service: Self.keychainServiceName) else { - throw SecretsError.itemAbsent + if let identityID = identityID { + scopedSecrets = Secrets(identityID: identityID, keychain: keychain) + } else { + scopedSecrets = nil } - return try T.fromDataStoredInSecrets(data) - } + do { + return try scopedSecrets?.item(.databasePassphrase) ?? unscopedItem(.databasePassphrase, keychain: keychain) + } catch SecretsError.itemAbsent { + var bytes = [Int8](repeating: 0, count: databasePassphraseByteCount) + let status = SecRandomCopyBytes(kSecRandomDefault, databasePassphraseByteCount, &bytes) - func set(_ data: SecretsStorable, forItem item: Item) throws { - try keychain.setGenericPassword( - data: data.dataStoredInSecrets, - forAccount: scopedKey(item: item), - service: Self.keychainServiceName) - } + if status == errSecSuccess { + let passphrase = Data(bytes: bytes, count: databasePassphraseByteCount).base64EncodedString() - func item(_ item: Item) throws -> T { - guard let data = try keychain.getGenericPassword( - account: scopedKey(item: item), - service: Self.keychainServiceName) else { - throw SecretsError.itemAbsent + if let scopedSecrets = scopedSecrets { + try scopedSecrets.set(passphrase, forItem: .databasePassphrase) + } else { + try setUnscoped(passphrase, forItem: .databasePassphrase, keychain: keychain) + } + + return passphrase + } else { + throw NSError(status: status) + } } - - return try T.fromDataStoredInSecrets(data) } func deleteAllItems() throws { @@ -99,6 +96,30 @@ public extension Secrets { } } + func getClientID() throws -> String { + try item(.clientID) + } + + func setClientID(_ clientID: String) throws { + try set(clientID, forItem: .clientID) + } + + func getClientSecret() throws -> String { + try item(.clientSecret) + } + + func setClientSecret(_ clientSecret: String) throws { + try set(clientSecret, forItem: .clientSecret) + } + + func getAccessToken() throws -> String { + try item(.accessToken) + } + + func setAccessToken(_ accessToken: String) throws { + try set(accessToken, forItem: .accessToken) + } + func generatePushKeyAndReturnPublicKey() throws -> Data { try keychain.generateKeyAndReturnPublicKey( applicationTag: scopedKey(item: .pushKey), @@ -130,10 +151,44 @@ public extension Secrets { private extension Secrets { static let keychainServiceName = "com.metabolist.metatext" + static let databasePassphraseByteCount = 64 + + private static func set(_ data: SecretsStorable, forAccount account: String, keychain: Keychain.Type) throws { + try keychain.setGenericPassword( + data: data.dataStoredInSecrets, + forAccount: account, + service: keychainServiceName) + } + + private static func get(account: String, keychain: Keychain.Type) throws -> T { + guard let data = try keychain.getGenericPassword( + account: account, + service: keychainServiceName) else { + throw SecretsError.itemAbsent + } + + return try T.fromDataStoredInSecrets(data) + } + + static func setUnscoped(_ data: SecretsStorable, forItem item: Item, keychain: Keychain.Type) throws { + try set(data, forAccount: item.rawValue, keychain: keychain) + } + + static func unscopedItem(_ item: Item, keychain: Keychain.Type) throws -> T { + try get(account: item.rawValue, keychain: keychain) + } func scopedKey(item: Item) -> String { identityID.uuidString + "." + item.rawValue } + + func set(_ data: SecretsStorable, forItem item: Item) throws { + try Self.set(data, forAccount: scopedKey(item: item), keychain: keychain) + } + + func item(_ item: Item) throws -> T { + try Self.get(account: scopedKey(item: item), keychain: keychain) + } } extension Data: SecretsStorable { diff --git a/ServiceLayer/Sources/ServiceLayer/Services/AllIdentitiesService.swift b/ServiceLayer/Sources/ServiceLayer/Services/AllIdentitiesService.swift index 0b6fb1e..976dc87 100644 --- a/ServiceLayer/Sources/ServiceLayer/Services/AllIdentitiesService.swift +++ b/ServiceLayer/Sources/ServiceLayer/Services/AllIdentitiesService.swift @@ -42,13 +42,13 @@ public extension AllIdentitiesService { return authenticationService.authorizeApp(instanceURL: instanceURL) .tryMap { appAuthorization -> (URL, AppAuthorization) in - try secrets.set(appAuthorization.clientId, forItem: .clientID) - try secrets.set(appAuthorization.clientSecret, forItem: .clientSecret) + try secrets.setClientID(appAuthorization.clientId) + try secrets.setClientSecret(appAuthorization.clientSecret) return (instanceURL, appAuthorization) } .flatMap(authenticationService.authenticate(instanceURL:appAuthorization:)) - .tryMap { try secrets.set($0.accessToken, forItem: .accessToken) } + .tryMap { try secrets.setAccessToken($0.accessToken) } .ignoreOutput() .eraseToAnyPublisher() } @@ -63,9 +63,9 @@ public extension AllIdentitiesService { .collect() .tryMap { _ in DeletionEndpoint.oauthRevoke( - token: try secrets.item(.accessToken), - clientID: try secrets.item(.clientID), - clientSecret: try secrets.item(.clientSecret)) + token: try secrets.getAccessToken(), + clientID: try secrets.getClientID(), + clientSecret: try secrets.getClientSecret()) } .flatMap(mastodonAPIClient.request) .collect() diff --git a/ServiceLayer/Sources/ServiceLayer/Services/IdentityService.swift b/ServiceLayer/Sources/ServiceLayer/Services/IdentityService.swift index 6662414..36e5939 100644 --- a/ServiceLayer/Sources/ServiceLayer/Services/IdentityService.swift +++ b/ServiceLayer/Sources/ServiceLayer/Services/IdentityService.swift @@ -40,7 +40,7 @@ public class IdentityService { keychain: environment.keychain) mastodonAPIClient = MastodonAPIClient(session: environment.session) mastodonAPIClient.instanceURL = identity.url - mastodonAPIClient.accessToken = try? secrets.item(.accessToken) + mastodonAPIClient.accessToken = try? secrets.getAccessToken() contentDatabase = try ContentDatabase(identityID: identityID, inMemory: environment.inMemoryContent,