1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-17 00:33:44 +00:00

Pm-10953/add-user-context-to-sync-replaces (#10627)

* Require userId for setting masterKeyEncryptedUserKey

* Replace folders for specified user

* Require userId for collection replace

* Cipher Replace requires userId

* Require UserId to update equivalent domains

* Require userId for policy replace

* sync state updates between fake state for better testing

* Revert to public observable tests

Since they now sync, we can test single-user updates impacting active user observables

* Do not init fake states through sync

Do not sync initial null values, that might wipe out already existing data.

* Require userId for Send replace

* Include userId for organization replace

* Require userId for billing sync data

* Require user Id for key connector sync data

* Allow decode of token by userId

* Require userId for synced key connector updates

* Add userId to policy setting during organization invite accept

* Fix cli

* Handle null userId

---------

Co-authored-by: bnagawiecki <107435978+bnagawiecki@users.noreply.github.com>
This commit is contained in:
Matt Gibson
2024-08-26 17:44:08 -07:00
committed by GitHub
parent 866a624e44
commit 9459cda304
46 changed files with 666 additions and 484 deletions

View File

@@ -133,7 +133,7 @@ export abstract class CipherService implements UserKeyRotationDataProvider<Ciphe
* @returns A promise that resolves to a record of updated cipher store, keyed by their cipher ID. Returns all ciphers, not just those updated
*/
upsert: (cipher: CipherData | CipherData[]) => Promise<Record<CipherId, CipherData>>;
replace: (ciphers: { [id: string]: CipherData }) => Promise<any>;
replace: (ciphers: { [id: string]: CipherData }, userId: UserId) => Promise<any>;
clear: (userId?: string) => Promise<void>;
moveManyWithServer: (ids: string[], folderId: string) => Promise<any>;
delete: (id: string | string[]) => Promise<any>;

View File

@@ -1,6 +1,6 @@
import { Observable } from "rxjs";
import { CollectionId } from "../../types/guid";
import { CollectionId, UserId } from "../../types/guid";
import { CollectionData } from "../models/data/collection.data";
import { Collection } from "../models/domain/collection";
import { TreeNode } from "../models/domain/tree-node";
@@ -22,7 +22,7 @@ export abstract class CollectionService {
getAllNested: (collections?: CollectionView[]) => Promise<TreeNode<CollectionView>[]>;
getNested: (id: string) => Promise<TreeNode<CollectionView>>;
upsert: (collection: CollectionData | CollectionData[]) => Promise<any>;
replace: (collections: { [id: string]: CollectionData }) => Promise<any>;
replace: (collections: { [id: string]: CollectionData }, userId: UserId) => Promise<any>;
clear: (userId?: string) => Promise<void>;
delete: (id: string | string[]) => Promise<any>;
}

View File

@@ -45,7 +45,7 @@ export abstract class FolderService implements UserKeyRotationDataProvider<Folde
export abstract class InternalFolderService extends FolderService {
upsert: (folder: FolderData | FolderData[]) => Promise<void>;
replace: (folders: { [id: string]: FolderData }) => Promise<void>;
replace: (folders: { [id: string]: FolderData }, userId: UserId) => Promise<void>;
clear: (userId?: string) => Promise<void>;
delete: (id: string | string[]) => Promise<any>;
}

View File

@@ -913,8 +913,8 @@ export class CipherService implements CipherServiceAbstraction {
});
}
async replace(ciphers: { [id: string]: CipherData }): Promise<any> {
await this.updateEncryptedCipherState(() => ciphers);
async replace(ciphers: { [id: string]: CipherData }, userId: UserId): Promise<any> {
await this.updateEncryptedCipherState(() => ciphers, userId);
}
/**
@@ -924,15 +924,18 @@ export class CipherService implements CipherServiceAbstraction {
*/
private async updateEncryptedCipherState(
update: (current: Record<CipherId, CipherData>) => Record<CipherId, CipherData>,
userId: UserId = null,
): Promise<Record<CipherId, CipherData>> {
const userId = await firstValueFrom(this.stateProvider.activeUserId$);
userId ||= await firstValueFrom(this.stateProvider.activeUserId$);
// Store that we should wait for an update to return any ciphers
await this.ciphersExpectingUpdate.forceValue(true);
await this.clearDecryptedCiphersState(userId);
const [, updatedCiphers] = await this.encryptedCiphersState.update((current) => {
const result = update(current ?? {});
return result;
});
const updatedCiphers = await this.stateProvider
.getUser(userId, ENCRYPTED_CIPHERS)
.update((current) => {
const result = update(current ?? {});
return result;
});
return updatedCiphers;
}

View File

@@ -184,8 +184,10 @@ export class CollectionService implements CollectionServiceAbstraction {
});
}
async replace(collections: Record<CollectionId, CollectionData>): Promise<void> {
await this.encryptedCollectionDataState.update(() => collections);
async replace(collections: Record<CollectionId, CollectionData>, userId: UserId): Promise<void> {
await this.stateProvider
.getUser(userId, ENCRYPTED_COLLECTION_DATA_KEY)
.update(() => collections);
}
async clear(userId?: UserId): Promise<void> {

View File

@@ -120,7 +120,7 @@ describe("Folder Service", () => {
});
it("replace", async () => {
await folderService.replace({ "2": folderData("2", "test 2") });
await folderService.replace({ "2": folderData("2", "test 2") }, mockUserId);
expect(await firstValueFrom(folderService.folders$)).toEqual([
{

View File

@@ -111,12 +111,12 @@ export class FolderService implements InternalFolderServiceAbstraction {
});
}
async replace(folders: { [id: string]: FolderData }): Promise<void> {
async replace(folders: { [id: string]: FolderData }, userId: UserId): Promise<void> {
if (!folders) {
return;
}
await this.encryptedFoldersState.update(() => {
await this.stateProvider.getUser(userId, FOLDER_ENCRYPTED_FOLDERS).update(() => {
const newFolders: Record<string, FolderData> = { ...folders };
return newFolders;
});