Refactoring
This commit is contained in:
parent
6f487524dd
commit
6dfda031a6
|
@ -10,19 +10,18 @@ private let devInstanceURL = URL(string: "https://mastodon.social")!
|
|||
private let devIdentityID = UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!
|
||||
private let devAccessToken = "DEVELOPMENT_ACCESS_TOKEN"
|
||||
|
||||
extension Secrets {
|
||||
static func fresh() -> Secrets { Secrets(keychainService: MockKeychainService()) }
|
||||
func freshKeychainService() -> KeychainServiceType { MockKeychainService() }
|
||||
|
||||
static let development: Secrets = {
|
||||
let secrets = Secrets.fresh()
|
||||
let developmentKeychainService: KeychainServiceType = {
|
||||
let keychainService = MockKeychainService()
|
||||
let secretsService = SecretsService(identityID: devIdentityID, keychainService: keychainService)
|
||||
|
||||
try! secrets.set("DEVELOPMENT_CLIENT_ID", forItem: .clientID, forIdentityID: devIdentityID)
|
||||
try! secrets.set("DEVELOPMENT_CLIENT_SECRET", forItem: .clientSecret, forIdentityID: devIdentityID)
|
||||
try! secrets.set(devAccessToken, forItem: .accessToken, forIdentityID: devIdentityID)
|
||||
try! secretsService.set("DEVELOPMENT_CLIENT_ID", forItem: .clientID)
|
||||
try! secretsService.set("DEVELOPMENT_CLIENT_SECRET", forItem: .clientSecret)
|
||||
try! secretsService.set(devAccessToken, forItem: .accessToken)
|
||||
|
||||
return secrets
|
||||
return keychainService
|
||||
}()
|
||||
}
|
||||
|
||||
extension Defaults {
|
||||
static func fresh() -> Defaults { Defaults(userDefaults: MockUserDefaults()) }
|
||||
|
@ -74,13 +73,13 @@ extension AppEnvironment {
|
|||
URLSessionConfiguration: URLSessionConfiguration = .stubbing,
|
||||
identityDatabase: IdentityDatabase = .fresh(),
|
||||
defaults: Defaults = .fresh(),
|
||||
secrets: Secrets = .fresh(),
|
||||
keychainService: KeychainServiceType = freshKeychainService(),
|
||||
webAuthSessionType: WebAuthSessionType.Type = SuccessfulMockWebAuthSession.self) -> AppEnvironment {
|
||||
AppEnvironment(
|
||||
URLSessionConfiguration: URLSessionConfiguration,
|
||||
identityDatabase: identityDatabase,
|
||||
defaults: defaults,
|
||||
secrets: secrets,
|
||||
keychainService: keychainService,
|
||||
webAuthSessionType: webAuthSessionType)
|
||||
}
|
||||
|
||||
|
@ -88,7 +87,7 @@ extension AppEnvironment {
|
|||
URLSessionConfiguration: .stubbing,
|
||||
identityDatabase: .development,
|
||||
defaults: .development,
|
||||
secrets: .development,
|
||||
keychainService: developmentKeychainService,
|
||||
webAuthSessionType: SuccessfulMockWebAuthSession.self)
|
||||
}
|
||||
|
||||
|
|
|
@ -77,8 +77,6 @@
|
|||
D0666A6424C6DC6C00F3F04B /* AppAuthorization.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0666A6224C6DC6C00F3F04B /* AppAuthorization.swift */; };
|
||||
D0666A6F24C6DFB300F3F04B /* AccessToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0666A6E24C6DFB300F3F04B /* AccessToken.swift */; };
|
||||
D0666A7024C6DFB300F3F04B /* AccessToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0666A6E24C6DFB300F3F04B /* AccessToken.swift */; };
|
||||
D0666A7224C6E0D300F3F04B /* Secrets.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0666A7124C6E0D300F3F04B /* Secrets.swift */; };
|
||||
D0666A7324C6E0D300F3F04B /* Secrets.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0666A7124C6E0D300F3F04B /* Secrets.swift */; };
|
||||
D0666A7D24C7745A00F3F04B /* GRDB in Frameworks */ = {isa = PBXBuildFile; productRef = D0666A7C24C7745A00F3F04B /* GRDB */; };
|
||||
D06B491F24D3F7FE00642749 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = D06B491E24D3F7FE00642749 /* Localizable.strings */; };
|
||||
D06B492024D3FB8000642749 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = D06B491E24D3F7FE00642749 /* Localizable.strings */; };
|
||||
|
@ -140,6 +138,8 @@
|
|||
D0EC8DC324DF7D9C00A08489 /* IdentityService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0EC8DC124DF7D9C00A08489 /* IdentityService.swift */; };
|
||||
D0EC8DC524DF842700A08489 /* KeychainService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0EC8DC424DF842700A08489 /* KeychainService.swift */; };
|
||||
D0EC8DC624DF842700A08489 /* KeychainService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0EC8DC424DF842700A08489 /* KeychainService.swift */; };
|
||||
D0EC8DC824DF8B3C00A08489 /* SecretsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0EC8DC724DF8B3C00A08489 /* SecretsService.swift */; };
|
||||
D0EC8DC924DF8B3C00A08489 /* SecretsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0EC8DC724DF8B3C00A08489 /* SecretsService.swift */; };
|
||||
D0ED1B6E24CE100C00B4899C /* AddIdentityViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0ED1B6D24CE100C00B4899C /* AddIdentityViewModelTests.swift */; };
|
||||
D0ED1BB724CE47F400B4899C /* WebAuthSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0ED1BB624CE47F400B4899C /* WebAuthSession.swift */; };
|
||||
D0ED1BB824CE47F400B4899C /* WebAuthSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0ED1BB624CE47F400B4899C /* WebAuthSession.swift */; };
|
||||
|
@ -216,7 +216,6 @@
|
|||
D0666A5324C6C3E500F3F04B /* Emoji.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Emoji.swift; sourceTree = "<group>"; };
|
||||
D0666A6224C6DC6C00F3F04B /* AppAuthorization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppAuthorization.swift; sourceTree = "<group>"; };
|
||||
D0666A6E24C6DFB300F3F04B /* AccessToken.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessToken.swift; sourceTree = "<group>"; };
|
||||
D0666A7124C6E0D300F3F04B /* Secrets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Secrets.swift; sourceTree = "<group>"; };
|
||||
D06B491E24D3F7FE00642749 /* Localizable.strings */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; path = Localizable.strings; sourceTree = "<group>"; };
|
||||
D074577624D29006004758DB /* MockWebAuthSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockWebAuthSession.swift; sourceTree = "<group>"; };
|
||||
D074577924D29366004758DB /* URLSessionConfiguration+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URLSessionConfiguration+Extensions.swift"; sourceTree = "<group>"; };
|
||||
|
@ -245,6 +244,7 @@
|
|||
D0DC177624D0CF2600A75C65 /* MockKeychainService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockKeychainService.swift; sourceTree = "<group>"; };
|
||||
D0EC8DC124DF7D9C00A08489 /* IdentityService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IdentityService.swift; sourceTree = "<group>"; };
|
||||
D0EC8DC424DF842700A08489 /* KeychainService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeychainService.swift; sourceTree = "<group>"; };
|
||||
D0EC8DC724DF8B3C00A08489 /* SecretsService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecretsService.swift; sourceTree = "<group>"; };
|
||||
D0ED1B6D24CE100C00B4899C /* AddIdentityViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddIdentityViewModelTests.swift; sourceTree = "<group>"; };
|
||||
D0ED1BB624CE47F400B4899C /* WebAuthSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebAuthSession.swift; sourceTree = "<group>"; };
|
||||
D0ED1BC024CED48800B4899C /* HTTPClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPClient.swift; sourceTree = "<group>"; };
|
||||
|
@ -351,6 +351,7 @@
|
|||
children = (
|
||||
D0EC8DC124DF7D9C00A08489 /* IdentityService.swift */,
|
||||
D0EC8DC424DF842700A08489 /* KeychainService.swift */,
|
||||
D0EC8DC724DF8B3C00A08489 /* SecretsService.swift */,
|
||||
);
|
||||
path = Services;
|
||||
sourceTree = "<group>";
|
||||
|
@ -440,7 +441,6 @@
|
|||
D0666A4D24C6C39600F3F04B /* Instance.swift */,
|
||||
D0ED1BE224CFA84400B4899C /* MastodonError.swift */,
|
||||
D0CD847224DBDEC700CF380C /* MastodonPreferences.swift */,
|
||||
D0666A7124C6E0D300F3F04B /* Secrets.swift */,
|
||||
D0CD847524DBDF3C00CF380C /* Status.swift */,
|
||||
D0CD847B24DBEA9F00CF380C /* Unknowable.swift */,
|
||||
);
|
||||
|
@ -808,10 +808,10 @@
|
|||
D0ED1BCE24CF768200B4899C /* MastodonEndpoint.swift in Sources */,
|
||||
D074577A24D29366004758DB /* URLSessionConfiguration+Extensions.swift in Sources */,
|
||||
D0ED1BB724CE47F400B4899C /* WebAuthSession.swift in Sources */,
|
||||
D0666A7224C6E0D300F3F04B /* Secrets.swift in Sources */,
|
||||
D0A1CA7424DAC2F1003063E9 /* KingfisherOptionsInfo+Extensions.swift in Sources */,
|
||||
D0159F9124DE743700E78478 /* TabNavigationView.swift in Sources */,
|
||||
D0ED1BC424CED54D00B4899C /* HTTPTarget.swift in Sources */,
|
||||
D0EC8DC824DF8B3C00A08489 /* SecretsService.swift in Sources */,
|
||||
D0159FA324DE955900E78478 /* CustomEmojiText.swift in Sources */,
|
||||
D0C963FE24CC3812003BD330 /* Publisher+Extensions.swift in Sources */,
|
||||
D04FD73C24D4A83A007D572D /* InstanceEndpoint+Stubbing.swift in Sources */,
|
||||
|
@ -872,6 +872,7 @@
|
|||
D0DC174E24CFF1F100A75C65 /* Stubbing.swift in Sources */,
|
||||
D0091B6C24DC10CE0040E8D2 /* PostingReadingPreferencesViewModel.swift in Sources */,
|
||||
D0091B6F24DD68090040E8D2 /* PreferencesView.swift in Sources */,
|
||||
D0EC8DC924DF8B3C00A08489 /* SecretsService.swift in Sources */,
|
||||
D0DB6EF524C5233E00D965FE /* AddIdentityView.swift in Sources */,
|
||||
D019E6EA24DF72E700697C7D /* InstanceEndpoint.swift in Sources */,
|
||||
D0159F9C24DE748C00E78478 /* SidebarNavigationView.swift in Sources */,
|
||||
|
@ -880,7 +881,6 @@
|
|||
D0ED1BCF24CF768200B4899C /* MastodonEndpoint.swift in Sources */,
|
||||
D074577B24D29366004758DB /* URLSessionConfiguration+Extensions.swift in Sources */,
|
||||
D0ED1BB824CE47F400B4899C /* WebAuthSession.swift in Sources */,
|
||||
D0666A7324C6E0D300F3F04B /* Secrets.swift in Sources */,
|
||||
D0A1CA7524DAC2F1003063E9 /* KingfisherOptionsInfo+Extensions.swift in Sources */,
|
||||
D0ED1BC524CED54D00B4899C /* HTTPTarget.swift in Sources */,
|
||||
D0C963FF24CC3812003BD330 /* Publisher+Extensions.swift in Sources */,
|
||||
|
|
|
@ -19,7 +19,7 @@ struct MetatextApp: App {
|
|||
URLSessionConfiguration: .default,
|
||||
identityDatabase: identityDatabase,
|
||||
defaults: Defaults(userDefaults: .standard),
|
||||
secrets: Secrets(keychainService: KeychainService(serviceName: "com.metabolist.metatext")),
|
||||
keychainService: KeychainService(serviceName: Self.keychainServiceName),
|
||||
webAuthSessionType: WebAuthSession.self)
|
||||
}
|
||||
|
||||
|
@ -29,3 +29,7 @@ struct MetatextApp: App {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension MetatextApp {
|
||||
static let keychainServiceName = "com.metabolist.metatext"
|
||||
}
|
||||
|
|
|
@ -6,6 +6,6 @@ struct AppEnvironment {
|
|||
let URLSessionConfiguration: URLSessionConfiguration
|
||||
let identityDatabase: IdentityDatabase
|
||||
let defaults: Defaults
|
||||
let secrets: Secrets
|
||||
let keychainService: KeychainServiceType
|
||||
let webAuthSessionType: WebAuthSessionType.Type
|
||||
}
|
||||
|
|
|
@ -16,7 +16,10 @@ class IdentityService {
|
|||
self.appEnvironment = appEnvironment
|
||||
observationErrors = observationErrorsInput.eraseToAnyPublisher()
|
||||
networkClient = MastodonClient(configuration: appEnvironment.URLSessionConfiguration)
|
||||
networkClient.accessToken = try appEnvironment.secrets.item(.accessToken, forIdentityID: identityID)
|
||||
networkClient.accessToken = try SecretsService(
|
||||
identityID: identityID,
|
||||
keychainService: appEnvironment.keychainService)
|
||||
.item(.accessToken)
|
||||
|
||||
let observation = appEnvironment.identityDatabase.identityObservation(id: identityID).share()
|
||||
|
||||
|
|
|
@ -11,42 +11,46 @@ enum SecretsStorableError: Error {
|
|||
case conversionFromDataStoredInSecrets(Data)
|
||||
}
|
||||
|
||||
class Secrets {
|
||||
struct SecretsService {
|
||||
let identityID: UUID
|
||||
private let keychainService: KeychainServiceType
|
||||
|
||||
init(keychainService: KeychainServiceType) {
|
||||
init(identityID: UUID, keychainService: KeychainServiceType) {
|
||||
self.identityID = identityID
|
||||
self.keychainService = keychainService
|
||||
}
|
||||
}
|
||||
|
||||
extension Secrets {
|
||||
enum Item: String {
|
||||
extension SecretsService {
|
||||
enum Item: String, CaseIterable {
|
||||
case clientID = "client-id"
|
||||
case clientSecret = "client-secret"
|
||||
case accessToken = "access-token"
|
||||
}
|
||||
}
|
||||
|
||||
extension Secrets {
|
||||
func set(_ data: SecretsStorable, forItem item: Item, forIdentityID identityID: UUID) throws {
|
||||
try keychainService.set(data: data.dataStoredInSecrets, forKey: Self.key(item: item, identityID: identityID))
|
||||
extension SecretsService {
|
||||
func set(_ data: SecretsStorable, forItem item: Item) throws {
|
||||
try keychainService.set(data: data.dataStoredInSecrets, forKey: key(item: item))
|
||||
}
|
||||
|
||||
func item<T: SecretsStorable>(_ item: Item, forIdentityID identityID: UUID) throws -> T? {
|
||||
guard let data = try keychainService.getData(key: Self.key(item: item, identityID: identityID)) else {
|
||||
func item<T: SecretsStorable>(_ item: Item) throws -> T? {
|
||||
guard let data = try keychainService.getData(key: key(item: item)) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return try T.fromDataStoredInSecrets(data)
|
||||
}
|
||||
|
||||
func delete(_ item: Item, forIdentityID identityID: UUID) throws {
|
||||
try keychainService.deleteData(key: Self.key(item: item, identityID: identityID))
|
||||
func deleteAllItems() throws {
|
||||
for item in SecretsService.Item.allCases {
|
||||
try keychainService.deleteData(key: key(item: item))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension Secrets {
|
||||
static func key(item: Item, identityID: UUID) -> String {
|
||||
private extension SecretsService {
|
||||
func key(item: Item) -> String {
|
||||
identityID.uuidString + "." + item.rawValue
|
||||
}
|
||||
}
|
|
@ -39,7 +39,7 @@ class AddIdentityViewModel: ObservableObject {
|
|||
identityID: identityID,
|
||||
instanceURL: instanceURL,
|
||||
redirectURL: redirectURL,
|
||||
secrets: environment.secrets)
|
||||
keychainService: environment.keychainService)
|
||||
.authenticationURL(instanceURL: instanceURL, redirectURL: redirectURL)
|
||||
.authenticate(
|
||||
webAuthSessionType: environment.webAuthSessionType,
|
||||
|
@ -67,7 +67,7 @@ private extension AddIdentityViewModel {
|
|||
identityID: UUID,
|
||||
instanceURL: URL,
|
||||
redirectURL: URL,
|
||||
secrets: Secrets) -> AnyPublisher<AppAuthorization, Error> {
|
||||
keychainService: KeychainServiceType) -> AnyPublisher<AppAuthorization, Error> {
|
||||
let endpoint = AppAuthorizationEndpoint.apps(
|
||||
clientName: MastodonAPI.OAuth.clientName,
|
||||
redirectURI: redirectURL.absoluteString,
|
||||
|
@ -77,8 +77,9 @@ private extension AddIdentityViewModel {
|
|||
|
||||
return networkClient.request(target)
|
||||
.tryMap {
|
||||
try secrets.set($0.clientId, forItem: .clientID, forIdentityID: identityID)
|
||||
try secrets.set($0.clientSecret, forItem: .clientSecret, forIdentityID: identityID)
|
||||
let secretsService = SecretsService(identityID: identityID, keychainService: keychainService)
|
||||
try secretsService.set($0.clientId, forItem: .clientID)
|
||||
try secretsService.set($0.clientSecret, forItem: .clientSecret)
|
||||
|
||||
return $0
|
||||
}
|
||||
|
@ -174,7 +175,9 @@ private extension Publisher where Output == (AppAuthorization, String), Failure
|
|||
private extension Publisher where Output == AccessToken {
|
||||
func createIdentity(id: UUID, instanceURL: URL, environment: AppEnvironment) -> AnyPublisher<UUID, Error> {
|
||||
tryMap { accessToken -> (UUID, URL) in
|
||||
try environment.secrets.set(accessToken.accessToken, forItem: .accessToken, forIdentityID: id)
|
||||
let secretsService = SecretsService(identityID: id, keychainService: environment.keychainService)
|
||||
|
||||
try secretsService.set(accessToken.accessToken, forItem: .accessToken)
|
||||
|
||||
return (id, instanceURL)
|
||||
}
|
||||
|
|
|
@ -26,6 +26,13 @@ extension RootViewModel {
|
|||
|
||||
func deleteIdentity(id: UUID) {
|
||||
environment.identityDatabase.deleteIdentity(id: id)
|
||||
.continuingIfWeakReferenceIsStillAlive(to: self)
|
||||
.tryMap {
|
||||
try SecretsService(
|
||||
identityID: id,
|
||||
keychainService: $1.environment.keychainService)
|
||||
.deleteAllItems()
|
||||
}
|
||||
.sink(receiveCompletion: { _ in }, receiveValue: {})
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
|
|
@ -20,15 +20,15 @@ class AddIdentityViewModelTests: XCTestCase {
|
|||
|
||||
XCTAssertEqual(addedIdentity.id, addedIdentityID)
|
||||
XCTAssertEqual(addedIdentity.url, URL(string: "https://mastodon.social")!)
|
||||
|
||||
let secretsService = SecretsService(identityID: addedIdentity.id, keychainService: environment.keychainService)
|
||||
|
||||
XCTAssertEqual(
|
||||
try environment.secrets.item(.clientID, forIdentityID: addedIdentityID) as String?,
|
||||
"AUTHORIZATION_CLIENT_ID_STUB_VALUE")
|
||||
try secretsService.item(.clientID) as String?, "AUTHORIZATION_CLIENT_ID_STUB_VALUE")
|
||||
XCTAssertEqual(
|
||||
try environment.secrets.item(.clientSecret, forIdentityID: addedIdentityID) as String?,
|
||||
"AUTHORIZATION_CLIENT_SECRET_STUB_VALUE")
|
||||
try secretsService.item(.clientSecret) as String?, "AUTHORIZATION_CLIENT_SECRET_STUB_VALUE")
|
||||
XCTAssertEqual(
|
||||
try environment.secrets.item(.accessToken, forIdentityID: addedIdentityID) as String?,
|
||||
"ACCESS_TOKEN_STUB_VALUE")
|
||||
try secretsService.item(.accessToken) as String?, "ACCESS_TOKEN_STUB_VALUE")
|
||||
}
|
||||
|
||||
func testAddIdentityWithoutScheme() throws {
|
||||
|
|
Loading…
Reference in New Issue