Merge pull request #1062 from mastodon/migrate-auth

Authentication Migrations
This commit is contained in:
Nathan Mattes 2023-06-13 15:46:06 +02:00 committed by GitHub
commit dea59ac820
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 89 additions and 10 deletions

View File

@ -17,7 +17,7 @@ final class SendPostIntentHandler: NSObject {
var disposeBag = Set<AnyCancellable>()
let coreDataStack = CoreDataStack()
let coreDataStack = CoreDataStack(isInMemory: true)
lazy var managedObjectContext = coreDataStack.persistentContainer.viewContext
lazy var api: APIService = {
let backgroundManagedObjectContext = coreDataStack.newTaskContext()

View File

@ -65,7 +65,7 @@
<attribute name="version" optional="YES" attributeType="String"/>
<relationship name="authentications" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonAuthentication" inverseName="instance" inverseEntity="MastodonAuthentication"/>
</entity>
<entity name="MastodonAuthentication" representedClassName="CoreDataStack.MastodonAuthentication" syncable="YES">
<entity name="MastodonAuthentication" representedClassName="CoreDataStack.MastodonAuthenticationLegacy" syncable="YES">
<attribute name="activedAt" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="appAccessToken" attributeType="String"/>
<attribute name="clientID" attributeType="String"/>

View File

@ -22,10 +22,14 @@ 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)
}
self.init(persistentStoreDescriptions: [storeDescription])
}
@ -123,16 +127,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
}
}
}

View File

@ -47,8 +47,22 @@ public class AppContext: ObservableObject {
.eraseToAnyPublisher()
public init() {
let _coreDataStack = CoreDataStack()
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)
}
let _managedObjectContext = _coreDataStack.persistentContainer.viewContext
_coreDataStack.persistentContainer.persistentStoreDescriptions.forEach {
$0.url = URL(fileURLWithPath: "/dev/null")
}
let _backgroundManagedObjectContext = _coreDataStack.persistentContainer.newBackgroundContext()
coreDataStack = _coreDataStack
managedObjectContext = _managedObjectContext

View File

@ -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,41 @@ public extension AuthenticationServiceProvider {
return try? JSONDecoder().decode(MastodonAuthentication.self, from: data)
}
}
func migrateLegacyAuthentications(in context: NSManagedObjectContext) {
do {
let legacyAuthentications = try context.fetch(MastodonAuthenticationLegacy.sortedFetchRequest)
let migratedAuthentications = legacyAuthentications.compactMap { auth -> MastodonAuthentication? in
return 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
)
}
if migratedAuthentications.count != legacyAuthentications.count {
logger.log(level: .default, "Not all account authentications 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 == false
}
}
// MARK: - Private

View File

@ -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)
}
}
}