From 4c9c34b6ce8db28e3eb0c48fb3d93684df28e17e Mon Sep 17 00:00:00 2001 From: Marcus Kida Date: Mon, 5 Jun 2023 14:02:44 +0200 Subject: [PATCH 1/7] Migrate legacy authentications --- Mastodon/Supporting Files/AppDelegate.swift | 1 + .../AuthenticationServiceProvider.swift | 43 +++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/Mastodon/Supporting Files/AppDelegate.swift b/Mastodon/Supporting Files/AppDelegate.swift index 79eaeb5cd..2778baad6 100644 --- a/Mastodon/Supporting Files/AppDelegate.swift +++ b/Mastodon/Supporting Files/AppDelegate.swift @@ -18,6 +18,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { let appContext = AppContext() func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + AuthenticationServiceProvider.shared.migrateLegacyAuthenticationsIfRequired(in: appContext.managedObjectContext) AuthenticationServiceProvider.shared.restore() AppSecret.default.register() diff --git a/MastodonSDK/Sources/MastodonCore/AuthenticationServiceProvider.swift b/MastodonSDK/Sources/MastodonCore/AuthenticationServiceProvider.swift index 5a9c77228..f7ec62718 100644 --- a/MastodonSDK/Sources/MastodonCore/AuthenticationServiceProvider.swift +++ b/MastodonSDK/Sources/MastodonCore/AuthenticationServiceProvider.swift @@ -6,10 +6,14 @@ import CoreDataStack import MastodonSDK import KeychainAccess import MastodonCommon +import os.log public class AuthenticationServiceProvider: ObservableObject { + private let logger = Logger(subsystem: "AuthenticationServiceProvider", category: "Authentication") + public static let shared = AuthenticationServiceProvider() private static let keychain = Keychain(service: "org.joinmastodon.app.authentications", accessGroup: AppName.groupID) + private let userDefaults: UserDefaults = .shared private init() {} @@ -63,6 +67,35 @@ public extension AuthenticationServiceProvider { return try? JSONDecoder().decode(MastodonAuthentication.self, from: data) } } + + func migrateLegacyAuthenticationsIfRequired(in context: NSManagedObjectContext) { + guard !userDefaults.didMigrateAuthentications else { return } + + defer { userDefaults.didMigrateAuthentications = true } + + do { + let request = NSFetchRequest(entityName: "MastodonAuthentication") + let legacyAuthentications = try context.fetch(request) + + self.authentications = legacyAuthentications.map { auth in + MastodonAuthentication( + identifier: auth.identifier, + domain: auth.domain, + username: auth.username, + appAccessToken: auth.appAccessToken, + userAccessToken: auth.userAccessToken, + clientID: auth.clientID, + clientSecret: auth.clientSecret, + createdAt: auth.createdAt, + updatedAt: auth.updatedAt, + activedAt: auth.activedAt, + userID: auth.userID + ) + } + } catch { + logger.log(level: .error, "Could not migrate legacy authentications") + } + } } // MARK: - Private @@ -73,3 +106,13 @@ private extension AuthenticationServiceProvider { } } } + +private extension UserDefaults { + @objc dynamic var didMigrateAuthentications: Bool { + get { + register(defaults: [#function: false]) + return bool(forKey: #function) + } + set { self[#function] = newValue } + } +} From 55afa02b529cb7716dae9238519fab41d102f2dd Mon Sep 17 00:00:00 2001 From: Marcus Kida Date: Mon, 5 Jun 2023 15:53:27 +0200 Subject: [PATCH 2/7] Try migrating old auth to keychain --- Mastodon/Supporting Files/AppDelegate.swift | 1 - .../Handler/SendPostIntentHandler.swift | 2 +- .../Sources/CoreDataStack/CoreDataStack.swift | 21 ++++++--- .../Sources/MastodonCore/AppContext.swift | 9 +++- .../AuthenticationServiceProvider.swift | 46 +++++++++++++------ 5 files changed, 54 insertions(+), 25 deletions(-) diff --git a/Mastodon/Supporting Files/AppDelegate.swift b/Mastodon/Supporting Files/AppDelegate.swift index 2778baad6..79eaeb5cd 100644 --- a/Mastodon/Supporting Files/AppDelegate.swift +++ b/Mastodon/Supporting Files/AppDelegate.swift @@ -18,7 +18,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { let appContext = AppContext() func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - AuthenticationServiceProvider.shared.migrateLegacyAuthenticationsIfRequired(in: appContext.managedObjectContext) AuthenticationServiceProvider.shared.restore() AppSecret.default.register() diff --git a/MastodonIntent/Handler/SendPostIntentHandler.swift b/MastodonIntent/Handler/SendPostIntentHandler.swift index b457b1c91..af56ad523 100644 --- a/MastodonIntent/Handler/SendPostIntentHandler.swift +++ b/MastodonIntent/Handler/SendPostIntentHandler.swift @@ -17,7 +17,7 @@ final class SendPostIntentHandler: NSObject { var disposeBag = Set() - let coreDataStack = CoreDataStack() + let coreDataStack = CoreDataStack(isInMemory: true) lazy var managedObjectContext = coreDataStack.persistentContainer.viewContext lazy var api: APIService = { let backgroundManagedObjectContext = coreDataStack.newTaskContext() diff --git a/MastodonSDK/Sources/CoreDataStack/CoreDataStack.swift b/MastodonSDK/Sources/CoreDataStack/CoreDataStack.swift index 48e69e375..ce512f303 100644 --- a/MastodonSDK/Sources/CoreDataStack/CoreDataStack.swift +++ b/MastodonSDK/Sources/CoreDataStack/CoreDataStack.swift @@ -22,10 +22,15 @@ public final class CoreDataStack { self.storeDescriptions = storeDescriptions } - public convenience init(databaseName: String = "shared") { + public convenience init(databaseName: String = "shared", isInMemory: Bool) { let storeURL = URL.storeURL(for: AppName.groupID, databaseName: databaseName) - let storeDescription = NSPersistentStoreDescription(url: storeURL) - storeDescription.url = URL(fileURLWithPath: "/dev/null") /// in-memory store with all features in favor of NSInMemoryStoreType + let storeDescription: NSPersistentStoreDescription + if isInMemory { + storeDescription = NSPersistentStoreDescription(url: URL(string: "file:///dev/null")!) /// in-memory store with all features in favor of NSInMemoryStoreType + } else { + storeDescription = NSPersistentStoreDescription(url: storeURL) + storeDescription.isReadOnly = true + } self.init(persistentStoreDescriptions: [storeDescription]) } @@ -123,16 +128,18 @@ extension CoreDataStack { } } -extension CoreDataStack { - - public func rebuild() { +public extension CoreDataStack { + func tearDown() { let oldStoreURL = persistentContainer.persistentStoreCoordinator.url(for: persistentContainer.persistentStoreCoordinator.persistentStores.first!) try! persistentContainer.persistentStoreCoordinator.destroyPersistentStore(at: oldStoreURL, ofType: NSSQLiteStoreType, options: nil) + } + + func rebuild() { + tearDown() CoreDataStack.load(persistentContainer: persistentContainer) { [weak self] in guard let self = self else { return } self.didFinishLoad.value = true } } - } diff --git a/MastodonSDK/Sources/MastodonCore/AppContext.swift b/MastodonSDK/Sources/MastodonCore/AppContext.swift index d8aa06fae..12eea2519 100644 --- a/MastodonSDK/Sources/MastodonCore/AppContext.swift +++ b/MastodonSDK/Sources/MastodonCore/AppContext.swift @@ -47,7 +47,14 @@ public class AppContext: ObservableObject { .eraseToAnyPublisher() public init() { - let _coreDataStack = CoreDataStack() + + /// Migrate existing Authentications to new Keychain-Based format +// var _legacyCoreDataStack: CoreDataStack? = CoreDataStack(isInMemory: false) +// AuthenticationServiceProvider.shared.migrateLegacyAuthenticationsIfRequired(in: _legacyCoreDataStack!.persistentContainer.viewContext) +// _legacyCoreDataStack!.tearDown() +// _legacyCoreDataStack = nil + + let _coreDataStack = CoreDataStack(isInMemory: true) let _managedObjectContext = _coreDataStack.persistentContainer.viewContext let _backgroundManagedObjectContext = _coreDataStack.persistentContainer.newBackgroundContext() coreDataStack = _coreDataStack diff --git a/MastodonSDK/Sources/MastodonCore/AuthenticationServiceProvider.swift b/MastodonSDK/Sources/MastodonCore/AuthenticationServiceProvider.swift index f7ec62718..81d0c2da7 100644 --- a/MastodonSDK/Sources/MastodonCore/AuthenticationServiceProvider.swift +++ b/MastodonSDK/Sources/MastodonCore/AuthenticationServiceProvider.swift @@ -69,27 +69,43 @@ public extension AuthenticationServiceProvider { } func migrateLegacyAuthenticationsIfRequired(in context: NSManagedObjectContext) { - guard !userDefaults.didMigrateAuthentications else { return } +// guard !userDefaults.didMigrateAuthentications else { return } defer { userDefaults.didMigrateAuthentications = true } do { - let request = NSFetchRequest(entityName: "MastodonAuthentication") + let request = NSFetchRequest(entityName: "MastodonAuthentication") let legacyAuthentications = try context.fetch(request) - self.authentications = legacyAuthentications.map { auth in - MastodonAuthentication( - identifier: auth.identifier, - domain: auth.domain, - username: auth.username, - appAccessToken: auth.appAccessToken, - userAccessToken: auth.userAccessToken, - clientID: auth.clientID, - clientSecret: auth.clientSecret, - createdAt: auth.createdAt, - updatedAt: auth.updatedAt, - activedAt: auth.activedAt, - userID: auth.userID + self.authentications = legacyAuthentications.compactMap { auth -> MastodonAuthentication? in + guard + let identifier = auth.value(forKey: "identifier") as? UUID, + let domain = auth.value(forKey: "domain") as? String, + let username = auth.value(forKey: "username") as? String, + let appAccessToken = auth.value(forKey: "appAccessToken") as? String, + let userAccessToken = auth.value(forKey: "userAccessToken") as? String, + let clientID = auth.value(forKey: "clientID") as? String, + let clientSecret = auth.value(forKey: "clientSecret") as? String, + let createdAt = auth.value(forKey: "createdAt") as? Date, + let updatedAt = auth.value(forKey: "updatedAt") as? Date, + let activedAt = auth.value(forKey: "activedAt") as? Date, + let userID = auth.value(forKey: "userID") as? String + + else { + return nil + } + return MastodonAuthentication( + identifier: identifier, + domain: domain, + username: username, + appAccessToken: appAccessToken, + userAccessToken: userAccessToken, + clientID: clientID, + clientSecret: clientSecret, + createdAt: createdAt, + updatedAt: updatedAt, + activedAt: activedAt, + userID: userID ) } } catch { From 73909005de777ab22e0e26ad40baabc375f11b2c Mon Sep 17 00:00:00 2001 From: Marcus Kida Date: Mon, 5 Jun 2023 17:21:32 +0200 Subject: [PATCH 3/7] Fix migration issues with duplicate persistent store --- .../Sources/CoreDataStack/CoreDataStack.swift | 1 - .../Sources/MastodonCore/AppContext.swift | 21 ++++++++++++------- .../AuthenticationServiceProvider.swift | 8 ++++--- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/MastodonSDK/Sources/CoreDataStack/CoreDataStack.swift b/MastodonSDK/Sources/CoreDataStack/CoreDataStack.swift index ce512f303..f7d8f1de7 100644 --- a/MastodonSDK/Sources/CoreDataStack/CoreDataStack.swift +++ b/MastodonSDK/Sources/CoreDataStack/CoreDataStack.swift @@ -29,7 +29,6 @@ public final class CoreDataStack { storeDescription = NSPersistentStoreDescription(url: URL(string: "file:///dev/null")!) /// in-memory store with all features in favor of NSInMemoryStoreType } else { storeDescription = NSPersistentStoreDescription(url: storeURL) - storeDescription.isReadOnly = true } self.init(persistentStoreDescriptions: [storeDescription]) } diff --git a/MastodonSDK/Sources/MastodonCore/AppContext.swift b/MastodonSDK/Sources/MastodonCore/AppContext.swift index 12eea2519..02966e2f8 100644 --- a/MastodonSDK/Sources/MastodonCore/AppContext.swift +++ b/MastodonSDK/Sources/MastodonCore/AppContext.swift @@ -47,15 +47,22 @@ public class AppContext: ObservableObject { .eraseToAnyPublisher() public init() { + + let authProvider = AuthenticationServiceProvider.shared + let _coreDataStack: CoreDataStack + if authProvider.authenticationMigrationRequired { + _coreDataStack = CoreDataStack(isInMemory: false) + authProvider.migrateLegacyAuthentications( + in: _coreDataStack.persistentContainer.viewContext + ) + } else { + _coreDataStack = CoreDataStack(isInMemory: true) + } - /// Migrate existing Authentications to new Keychain-Based format -// var _legacyCoreDataStack: CoreDataStack? = CoreDataStack(isInMemory: false) -// AuthenticationServiceProvider.shared.migrateLegacyAuthenticationsIfRequired(in: _legacyCoreDataStack!.persistentContainer.viewContext) -// _legacyCoreDataStack!.tearDown() -// _legacyCoreDataStack = nil - - let _coreDataStack = CoreDataStack(isInMemory: true) let _managedObjectContext = _coreDataStack.persistentContainer.viewContext + _coreDataStack.persistentContainer.persistentStoreDescriptions.forEach { + $0.url = URL(fileURLWithPath: "/dev/null") + } let _backgroundManagedObjectContext = _coreDataStack.persistentContainer.newBackgroundContext() coreDataStack = _coreDataStack managedObjectContext = _managedObjectContext diff --git a/MastodonSDK/Sources/MastodonCore/AuthenticationServiceProvider.swift b/MastodonSDK/Sources/MastodonCore/AuthenticationServiceProvider.swift index 81d0c2da7..01b7bc6f3 100644 --- a/MastodonSDK/Sources/MastodonCore/AuthenticationServiceProvider.swift +++ b/MastodonSDK/Sources/MastodonCore/AuthenticationServiceProvider.swift @@ -68,9 +68,7 @@ public extension AuthenticationServiceProvider { } } - func migrateLegacyAuthenticationsIfRequired(in context: NSManagedObjectContext) { -// guard !userDefaults.didMigrateAuthentications else { return } - + func migrateLegacyAuthentications(in context: NSManagedObjectContext) { defer { userDefaults.didMigrateAuthentications = true } do { @@ -112,6 +110,10 @@ public extension AuthenticationServiceProvider { logger.log(level: .error, "Could not migrate legacy authentications") } } + + var authenticationMigrationRequired: Bool { + !userDefaults.didMigrateAuthentications + } } // MARK: - Private From 183f30306580a3fbc2c742a98bcc36a5043a4131 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Tue, 13 Jun 2023 12:36:48 +0200 Subject: [PATCH 4/7] Move extension to its own file --- .../AuthenticationServiceProvider.swift | 10 ---------- .../UserDefaults+Authentication.swift | 20 +++++++++++++++++++ 2 files changed, 20 insertions(+), 10 deletions(-) create mode 100644 MastodonSDK/Sources/MastodonCore/UserDefaults+Authentication.swift diff --git a/MastodonSDK/Sources/MastodonCore/AuthenticationServiceProvider.swift b/MastodonSDK/Sources/MastodonCore/AuthenticationServiceProvider.swift index 01b7bc6f3..b8f743072 100644 --- a/MastodonSDK/Sources/MastodonCore/AuthenticationServiceProvider.swift +++ b/MastodonSDK/Sources/MastodonCore/AuthenticationServiceProvider.swift @@ -124,13 +124,3 @@ private extension AuthenticationServiceProvider { } } } - -private extension UserDefaults { - @objc dynamic var didMigrateAuthentications: Bool { - get { - register(defaults: [#function: false]) - return bool(forKey: #function) - } - set { self[#function] = newValue } - } -} diff --git a/MastodonSDK/Sources/MastodonCore/UserDefaults+Authentication.swift b/MastodonSDK/Sources/MastodonCore/UserDefaults+Authentication.swift new file mode 100644 index 000000000..d5262027c --- /dev/null +++ b/MastodonSDK/Sources/MastodonCore/UserDefaults+Authentication.swift @@ -0,0 +1,20 @@ +// Copyright © 2023 Mastodon gGmbH. All rights reserved. + +import Foundation + +public extension UserDefaults { + + enum Keys { + static let didMigrateAuthenticationsKey = "didMigrateAuthentications" + } + + @objc dynamic var didMigrateAuthentications: Bool { + get { + return bool(forKey: Keys.didMigrateAuthenticationsKey) + } + + set { + set(newValue, forKey: Keys.didMigrateAuthenticationsKey) + } + } +} From aede20f2c84adb812ef5289539004e182a119734 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Tue, 13 Jun 2023 13:05:05 +0200 Subject: [PATCH 5/7] Mark migration as succesful only in case of success --- .../AuthenticationServiceProvider.swift | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/MastodonSDK/Sources/MastodonCore/AuthenticationServiceProvider.swift b/MastodonSDK/Sources/MastodonCore/AuthenticationServiceProvider.swift index b8f743072..6e8e80971 100644 --- a/MastodonSDK/Sources/MastodonCore/AuthenticationServiceProvider.swift +++ b/MastodonSDK/Sources/MastodonCore/AuthenticationServiceProvider.swift @@ -68,14 +68,12 @@ public extension AuthenticationServiceProvider { } } - func migrateLegacyAuthentications(in context: NSManagedObjectContext) { - defer { userDefaults.didMigrateAuthentications = true } - + func migrateLegacyAuthentications(in context: NSManagedObjectContext) { do { let request = NSFetchRequest(entityName: "MastodonAuthentication") let legacyAuthentications = try context.fetch(request) - self.authentications = legacyAuthentications.compactMap { auth -> MastodonAuthentication? in + let migratedAuthentications = legacyAuthentications.compactMap { auth -> MastodonAuthentication? in guard let identifier = auth.value(forKey: "identifier") as? UUID, let domain = auth.value(forKey: "domain") as? String, @@ -106,13 +104,21 @@ public extension AuthenticationServiceProvider { userID: userID ) } + + if migratedAuthentications.count != legacyAuthentications.count { + logger.log(level: .default, "Not all mitgrations could be migrated.") + } + + self.authentications = migratedAuthentications + userDefaults.didMigrateAuthentications = true } catch { + userDefaults.didMigrateAuthentications = false logger.log(level: .error, "Could not migrate legacy authentications") } } var authenticationMigrationRequired: Bool { - !userDefaults.didMigrateAuthentications + userDefaults.didMigrateAuthentications == false } } From 001404b1ae7f269ee3aa0728f4722cda56943564 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Tue, 13 Jun 2023 14:01:32 +0200 Subject: [PATCH 6/7] Slightly refactor authentication migration --- .../CoreData 8.xcdatamodel/contents | 2 +- .../AuthenticationServiceProvider.swift | 44 ++++++------------- 2 files changed, 14 insertions(+), 32 deletions(-) diff --git a/MastodonSDK/Sources/CoreDataStack/CoreData.xcdatamodeld/CoreData 8.xcdatamodel/contents b/MastodonSDK/Sources/CoreDataStack/CoreData.xcdatamodeld/CoreData 8.xcdatamodel/contents index fe1fcd98a..b875fe28d 100644 --- a/MastodonSDK/Sources/CoreDataStack/CoreData.xcdatamodeld/CoreData 8.xcdatamodel/contents +++ b/MastodonSDK/Sources/CoreDataStack/CoreData.xcdatamodeld/CoreData 8.xcdatamodel/contents @@ -65,7 +65,7 @@ - + diff --git a/MastodonSDK/Sources/MastodonCore/AuthenticationServiceProvider.swift b/MastodonSDK/Sources/MastodonCore/AuthenticationServiceProvider.swift index 6e8e80971..20ea9b906 100644 --- a/MastodonSDK/Sources/MastodonCore/AuthenticationServiceProvider.swift +++ b/MastodonSDK/Sources/MastodonCore/AuthenticationServiceProvider.swift @@ -70,38 +70,20 @@ public extension AuthenticationServiceProvider { func migrateLegacyAuthentications(in context: NSManagedObjectContext) { do { - let request = NSFetchRequest(entityName: "MastodonAuthentication") - let legacyAuthentications = try context.fetch(request) - - let migratedAuthentications = legacyAuthentications.compactMap { auth -> MastodonAuthentication? in - guard - let identifier = auth.value(forKey: "identifier") as? UUID, - let domain = auth.value(forKey: "domain") as? String, - let username = auth.value(forKey: "username") as? String, - let appAccessToken = auth.value(forKey: "appAccessToken") as? String, - let userAccessToken = auth.value(forKey: "userAccessToken") as? String, - let clientID = auth.value(forKey: "clientID") as? String, - let clientSecret = auth.value(forKey: "clientSecret") as? String, - let createdAt = auth.value(forKey: "createdAt") as? Date, - let updatedAt = auth.value(forKey: "updatedAt") as? Date, - let activedAt = auth.value(forKey: "activedAt") as? Date, - let userID = auth.value(forKey: "userID") as? String - - else { - return nil - } + let legacyAuthentications = try context.fetch(MastodonAuthenticationLegacy.sortedFetchRequest) + let migratedAuthentications = legacyAuthentications.compactMap { auth -> MastodonAuthentication? in return MastodonAuthentication( - identifier: identifier, - domain: domain, - username: username, - appAccessToken: appAccessToken, - userAccessToken: userAccessToken, - clientID: clientID, - clientSecret: clientSecret, - createdAt: createdAt, - updatedAt: updatedAt, - activedAt: activedAt, - userID: userID + identifier: auth.identifier, + domain: auth.domain, + username: auth.username, + appAccessToken: auth.appAccessToken, + userAccessToken: auth.userAccessToken, + clientID: auth.clientID, + clientSecret: auth.clientSecret, + createdAt: auth.createdAt, + updatedAt: auth.updatedAt, + activedAt: auth.activedAt, + userID: auth.userID ) } From c5bba298ac3fa871a34fe82335277b03418ac679 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Tue, 13 Jun 2023 14:33:45 +0200 Subject: [PATCH 7/7] Improve log statement :facepalm: Co-authored-by: Marcus Kida --- .../Sources/MastodonCore/AuthenticationServiceProvider.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MastodonSDK/Sources/MastodonCore/AuthenticationServiceProvider.swift b/MastodonSDK/Sources/MastodonCore/AuthenticationServiceProvider.swift index 20ea9b906..f3cd504a0 100644 --- a/MastodonSDK/Sources/MastodonCore/AuthenticationServiceProvider.swift +++ b/MastodonSDK/Sources/MastodonCore/AuthenticationServiceProvider.swift @@ -88,7 +88,7 @@ public extension AuthenticationServiceProvider { } if migratedAuthentications.count != legacyAuthentications.count { - logger.log(level: .default, "Not all mitgrations could be migrated.") + logger.log(level: .default, "Not all account authentications could be migrated.") } self.authentications = migratedAuthentications