generated from bitwarden/template
-
Notifications
You must be signed in to change notification settings - Fork 26
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[BITAU-134] [BITAU-121] Create Shared CoreData Store (#937)
- Loading branch information
1 parent
235f340
commit fe37cb6
Showing
16 changed files
with
1,003 additions
and
0 deletions.
There are no files selected for viewing
135 changes: 135 additions & 0 deletions
135
AuthenticatorBridgeKit/AuthenticatorBridgeDataStore.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
import CoreData | ||
|
||
// MARK: - AuthenticatorStoreType | ||
|
||
/// A type of data store. | ||
/// | ||
public enum AuthenticatorBridgeStoreType { | ||
/// The data store is stored only in memory and isn't persisted to the device. This is used for | ||
/// unit testing. | ||
case memory | ||
|
||
/// The data store is persisted to the device. | ||
case persisted | ||
} | ||
|
||
// MARK: - AuthenticatorDataStore | ||
|
||
/// A data store that manages persisting data across app launches in Core Data. | ||
/// | ||
public class AuthenticatorBridgeDataStore { | ||
// MARK: Properties | ||
|
||
/// A managed object context which executes on a background queue. | ||
private(set) lazy var backgroundContext: NSManagedObjectContext = { | ||
let context = persistentContainer.newBackgroundContext() | ||
context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy | ||
return context | ||
}() | ||
|
||
/// The service used by the application to report non-fatal errors. | ||
let errorReporter: ErrorReporter | ||
|
||
/// The CoreData model name. | ||
private let modelName = "Bitwarden-Authenticator" | ||
|
||
/// The Core Data persistent container. | ||
public let persistentContainer: NSPersistentContainer | ||
|
||
// MARK: Initialization | ||
|
||
/// Initialize a `AuthenticatorBridgeDataStore`. | ||
/// | ||
/// - Parameters: | ||
/// - errorReporter: The service used by the application to report non-fatal errors. | ||
/// - groupIdentifier: The app group identifier for the shared resource. | ||
/// - storeType: The type of store to create. | ||
/// | ||
public init( | ||
errorReporter: ErrorReporter, | ||
groupIdentifier: String, | ||
storeType: AuthenticatorBridgeStoreType = .persisted | ||
) { | ||
self.errorReporter = errorReporter | ||
|
||
#if SWIFT_PACKAGE | ||
let bundle = Bundle.module | ||
#else | ||
let bundle = Bundle(for: type(of: self)) | ||
#endif | ||
|
||
let modelURL = bundle.url(forResource: modelName, withExtension: "momd")! | ||
let managedObjectModel = NSManagedObjectModel(contentsOf: modelURL)! | ||
persistentContainer = NSPersistentContainer( | ||
name: modelName, | ||
managedObjectModel: managedObjectModel | ||
) | ||
let storeDescription: NSPersistentStoreDescription | ||
switch storeType { | ||
case .memory: | ||
storeDescription = NSPersistentStoreDescription(url: URL(fileURLWithPath: "/dev/null")) | ||
case .persisted: | ||
let storeURL = FileManager.default | ||
.containerURL(forSecurityApplicationGroupIdentifier: groupIdentifier)! | ||
.appendingPathComponent("\(modelName).sqlite") | ||
storeDescription = NSPersistentStoreDescription(url: storeURL) | ||
} | ||
persistentContainer.persistentStoreDescriptions = [storeDescription] | ||
|
||
persistentContainer.loadPersistentStores { _, error in | ||
if let error { | ||
errorReporter.log(error: error) | ||
} | ||
} | ||
|
||
persistentContainer.viewContext.automaticallyMergesChangesFromParent = true | ||
} | ||
|
||
// MARK: Methods | ||
|
||
/// Executes a batch delete request and merges the changes into the background and view contexts. | ||
/// | ||
/// - Parameter request: The batch delete request to perform. | ||
/// | ||
public func executeBatchDelete(_ request: NSBatchDeleteRequest) async throws { | ||
try await backgroundContext.perform { | ||
try self.backgroundContext.executeAndMergeChanges( | ||
batchDeleteRequest: request, | ||
additionalContexts: [self.persistentContainer.viewContext] | ||
) | ||
} | ||
} | ||
|
||
/// Executes a batch insert request and merges the changes into the background and view contexts. | ||
/// | ||
/// - Parameter request: The batch insert request to perform. | ||
/// | ||
public func executeBatchInsert(_ request: NSBatchInsertRequest) async throws { | ||
try await backgroundContext.perform { | ||
try self.backgroundContext.executeAndMergeChanges( | ||
batchInsertRequest: request, | ||
additionalContexts: [self.persistentContainer.viewContext] | ||
) | ||
} | ||
} | ||
|
||
/// Executes a batch delete and batch insert request and merges the changes into the background | ||
/// and view contexts. | ||
/// | ||
/// - Parameters: | ||
/// - deleteRequest: The batch delete request to perform. | ||
/// - insertRequest: The batch insert request to perform. | ||
/// | ||
public func executeBatchReplace( | ||
deleteRequest: NSBatchDeleteRequest, | ||
insertRequest: NSBatchInsertRequest | ||
) async throws { | ||
try await backgroundContext.perform { | ||
try self.backgroundContext.executeAndMergeChanges( | ||
batchDeleteRequest: deleteRequest, | ||
batchInsertRequest: insertRequest, | ||
additionalContexts: [self.persistentContainer.viewContext] | ||
) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
import CoreData | ||
import Foundation | ||
|
||
/// A data model for persisting authenticator items into the shared CoreData store. | ||
/// | ||
public class AuthenticatorBridgeItemData: NSManagedObject, CodableModelData { | ||
public typealias Model = AuthenticatorBridgeItemDataModel | ||
|
||
// MARK: Properties | ||
|
||
/// The item's ID | ||
@NSManaged public var id: String | ||
|
||
/// The data model encoded as encrypted JSON data | ||
@NSManaged public var modelData: Data? | ||
|
||
/// The ID of the user who owns the item | ||
@NSManaged public var userId: String | ||
|
||
// MARK: Initialization | ||
|
||
/// Initialize an `AuthenticatorBridgeItemData` object for insertion into the managed object context | ||
/// | ||
/// - Parameters: | ||
/// - context: The managed object context to insert the initialized item | ||
/// - userId: The ID of the user who owns the item | ||
/// - authenticatorItem: the `AuthenticatorBridgeItemDataModel` used to create the item | ||
convenience init( | ||
context: NSManagedObjectContext, | ||
userId: String, | ||
authenticatorItem: AuthenticatorBridgeItemDataModel | ||
) throws { | ||
self.init(context: context) | ||
id = authenticatorItem.id | ||
model = authenticatorItem | ||
self.userId = userId | ||
} | ||
} | ||
|
||
// MARK: - ManagedUserObject | ||
|
||
extension AuthenticatorBridgeItemData: ManagedUserObject { | ||
/// Create an NSPredicate based on both the userId and id properties. | ||
/// | ||
/// - Parameters: | ||
/// - userId: The userId to match in the predicate | ||
/// - id: The id to match in the predicate | ||
/// - Returns: The NSPredicate for searching/filtering by userId and id | ||
/// | ||
static func userIdAndIdPredicate(userId: String, id: String) -> NSPredicate { | ||
NSPredicate( | ||
format: "%K == %@ AND %K == %@", | ||
#keyPath(AuthenticatorBridgeItemData.userId), | ||
userId, | ||
#keyPath(AuthenticatorBridgeItemData.id), | ||
id | ||
) | ||
} | ||
|
||
/// Create an NSPredicate based on the userId property. | ||
/// | ||
/// - Parameter userId: The userId to match in the predicate | ||
/// - Returns: The NSPredicate for searching/filtering by userId | ||
/// | ||
static func userIdPredicate(userId: String) -> NSPredicate { | ||
NSPredicate(format: "%K == %@", #keyPath(AuthenticatorBridgeItemData.userId), userId) | ||
} | ||
|
||
/// Updates the object with the properties from the `value` struct and the given `userId` | ||
/// | ||
/// - Parameters: | ||
/// - value: the `AuthenticatorBridgeItemDataModel` to use in updating the object | ||
/// - userId: userId to update this object with. | ||
/// | ||
func update(with value: AuthenticatorBridgeItemDataModel, userId: String) throws { | ||
id = value.id | ||
model = value | ||
self.userId = userId | ||
} | ||
} |
39 changes: 39 additions & 0 deletions
39
AuthenticatorBridgeKit/AuthenticatorBridgeItemDataModel.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import Foundation | ||
|
||
/// A struct for storing information about items that are shared between the Bitwarden and Authenticator apps. | ||
/// | ||
public struct AuthenticatorBridgeItemDataModel: Codable, Equatable { | ||
// MARK: Properties | ||
|
||
/// Bool indicating if this item is a favorite. | ||
public let favorite: Bool | ||
|
||
/// The unique id of the item. | ||
public let id: String | ||
|
||
/// The name of the item. | ||
public let name: String | ||
|
||
/// The TOTP key used to generate codes. | ||
public let totpKey: String? | ||
|
||
/// The username of the Bitwarden account that owns this iteam. | ||
public let username: String? | ||
|
||
/// Initialize an `AuthenticatorBridgeItemDataModel` with the values provided. | ||
/// | ||
/// - Parameters: | ||
/// - favorite: Bool indicating if this item is a favorite. | ||
/// - id: The unique id of the item. | ||
/// - name: The name of the item. | ||
/// - totpKey: The TOTP key used to generate codes. | ||
/// - username: The username of the Bitwarden account that owns this iteam. | ||
/// | ||
public init(favorite: Bool, id: String, name: String, totpKey: String?, username: String?) { | ||
self.favorite = favorite | ||
self.id = id | ||
self.name = name | ||
self.totpKey = totpKey | ||
self.username = username | ||
} | ||
} |
115 changes: 115 additions & 0 deletions
115
AuthenticatorBridgeKit/AuthenticatorBridgeItemService.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
import Foundation | ||
|
||
// MARK: - AuthenticatorBridgeItemService | ||
|
||
/// A service that provides a number of convenience methods for working with the shared | ||
/// `AuthenticatorBridgeItemData` objects. | ||
/// | ||
public protocol AuthenticatorBridgeItemService { | ||
/// Removes all items that are owned by the specific userId | ||
/// | ||
/// - Parameter userId: the id of the user for which to delete all items. | ||
/// | ||
func deleteAllForUserId(_ userId: String) async throws | ||
|
||
/// Fetches all items that are owned by the specific userId | ||
/// | ||
/// - Parameter userId: the id of the user for which to fetch items. | ||
/// | ||
func fetchAllForUserId(_ userId: String) async throws -> [AuthenticatorBridgeItemDataModel] | ||
|
||
/// Inserts the list of items into the store for the given userId. | ||
/// | ||
/// - Parameters: | ||
/// - items: The list of `AuthenticatorBridgeItemDataModel` to be inserted into the store. | ||
/// - userId: the id of the user for which to insert the items. | ||
/// | ||
func insertItems(_ items: [AuthenticatorBridgeItemDataModel], | ||
forUserId userId: String) async throws | ||
|
||
/// Deletes all existing items for a given user and inserts new items for the list of items provided. | ||
/// | ||
/// - Parameters: | ||
/// - items: The new items to be inserted into the store | ||
/// - userId: The userId of the items to be removed and then replaces with items. | ||
/// | ||
func replaceAllItems(with items: [AuthenticatorBridgeItemDataModel], | ||
forUserId userId: String) async throws | ||
} | ||
|
||
/// A concrete implementation of the `AuthenticatorBridgeItemService` protocol. | ||
/// | ||
public class DefaultAuthenticatorBridgeItemService: AuthenticatorBridgeItemService { | ||
// MARK: Properties | ||
|
||
/// The CoreData store for working with shared data. | ||
let dataStore: AuthenticatorBridgeDataStore | ||
|
||
/// The keychain repository for working with the shared key. | ||
let sharedKeychainRepository: SharedKeychainRepository | ||
|
||
// MARK: Initialization | ||
|
||
/// Initialize a `DefaultAuthenticatorBridgeItemService` | ||
/// | ||
/// - Parameters: | ||
/// - dataStore: The CoreData store for working with shared data | ||
/// - sharedKeychainRepository: The keychain repository for working with the shared key. | ||
/// | ||
init(dataStore: AuthenticatorBridgeDataStore, sharedKeychainRepository: SharedKeychainRepository) { | ||
self.dataStore = dataStore | ||
self.sharedKeychainRepository = sharedKeychainRepository | ||
} | ||
|
||
// MARK: Methods | ||
|
||
/// Removes all items that are owned by the specific userId | ||
/// | ||
/// - Parameter userId: the id of the user for which to delete all items. | ||
/// | ||
public func deleteAllForUserId(_ userId: String) async throws { | ||
try await dataStore.executeBatchDelete(AuthenticatorBridgeItemData.deleteByUserIdRequest(userId: userId)) | ||
} | ||
|
||
/// Fetches all items that are owned by the specific userId | ||
/// | ||
/// - Parameter userId: the id of the user for which to fetch items. | ||
/// | ||
public func fetchAllForUserId(_ userId: String) async throws -> [AuthenticatorBridgeItemDataModel] { | ||
let fetchRequest = AuthenticatorBridgeItemData.fetchByUserIdRequest(userId: userId) | ||
let result = try dataStore.backgroundContext.fetch(fetchRequest) | ||
|
||
return result.compactMap { data in | ||
data.model | ||
} | ||
} | ||
|
||
/// Inserts the list of items into the store for the given userId. | ||
/// | ||
/// - Parameters: | ||
/// - items: The list of `AuthenticatorBridgeItemDataModel` to be inserted into the store. | ||
/// - userId: the id of the user for which to insert the items. | ||
/// | ||
public func insertItems(_ items: [AuthenticatorBridgeItemDataModel], | ||
forUserId userId: String) async throws { | ||
try await dataStore.executeBatchInsert( | ||
AuthenticatorBridgeItemData.batchInsertRequest(objects: items, userId: userId) | ||
) | ||
} | ||
|
||
/// Deletes all existing items for a given user and inserts new items for the list of items provided. | ||
/// | ||
/// - Parameters: | ||
/// - items: The new items to be inserted into the store | ||
/// - userId: The userId of the items to be removed and then replaces with items. | ||
/// | ||
public func replaceAllItems(with items: [AuthenticatorBridgeItemDataModel], | ||
forUserId userId: String) async throws { | ||
let deleteRequest = AuthenticatorBridgeItemData.deleteByUserIdRequest(userId: userId) | ||
let insertRequest = try AuthenticatorBridgeItemData.batchInsertRequest(objects: items, userId: userId) | ||
try await dataStore.executeBatchReplace( | ||
deleteRequest: deleteRequest, | ||
insertRequest: insertRequest | ||
) | ||
} | ||
} |
14 changes: 14 additions & 0 deletions
14
AuthenticatorBridgeKit/Bitwarden-Authenticator.xcdatamodeld/Bitwarden.xcdatamodel/contents
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> | ||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="22758" systemVersion="23G93" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier=""> | ||
<entity name="AuthenticatorBridgeItemData" representedClassName="AuthenticatorBridgeKit.AuthenticatorBridgeItemData" syncable="YES"> | ||
<attribute name="id" attributeType="String"/> | ||
<attribute name="modelData" attributeType="Binary"/> | ||
<attribute name="userId" attributeType="String"/> | ||
<uniquenessConstraints> | ||
<uniquenessConstraint> | ||
<constraint value="userId"/> | ||
<constraint value="id"/> | ||
</uniquenessConstraint> | ||
</uniquenessConstraints> | ||
</entity> | ||
</model> |
Oops, something went wrong.