1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-26 01:23:24 +00:00

Merge branch 'main' into km/replace-encstring-with-unsigned-shared-key

This commit is contained in:
Bernd Schoolmann
2026-01-13 13:56:46 +01:00
845 changed files with 67370 additions and 14768 deletions

View File

@@ -9,6 +9,14 @@ import { CollectionData, Collection, CollectionView } from "../models";
export abstract class CollectionService {
abstract encryptedCollections$(userId: UserId): Observable<Collection[] | null>;
abstract decryptedCollections$(userId: UserId): Observable<CollectionView[]>;
/**
* Gets the default collection for a user in a given organization, if it exists.
*/
abstract defaultUserCollection$(
userId: UserId,
orgId: OrganizationId,
): Observable<CollectionView | undefined>;
abstract upsert(collection: CollectionData, userId: UserId): Promise<any>;
abstract replace(collections: { [id: string]: CollectionData }, userId: UserId): Promise<any>;
/**

View File

@@ -15,9 +15,10 @@ import {
} from "@bitwarden/common/spec";
import { CollectionId, OrganizationId, UserId } from "@bitwarden/common/types/guid";
import { OrgKey } from "@bitwarden/common/types/key";
import { newGuid } from "@bitwarden/guid";
import { KeyService } from "@bitwarden/key-management";
import { CollectionData, CollectionView } from "../models";
import { CollectionData, CollectionTypes, CollectionView } from "../models";
import { DECRYPTED_COLLECTION_DATA_KEY, ENCRYPTED_COLLECTION_DATA_KEY } from "./collection.state";
import { DefaultCollectionService } from "./default-collection.service";
@@ -389,6 +390,83 @@ describe("DefaultCollectionService", () => {
});
});
describe("defaultUserCollection$", () => {
it("returns the default collection when one exists matching the org", async () => {
const orgId = newGuid() as OrganizationId;
const defaultCollection = collectionViewDataFactory(orgId);
defaultCollection.type = CollectionTypes.DefaultUserCollection;
const regularCollection = collectionViewDataFactory(orgId);
regularCollection.type = CollectionTypes.SharedCollection;
await setDecryptedState([defaultCollection, regularCollection]);
const result = await firstValueFrom(collectionService.defaultUserCollection$(userId, orgId));
expect(result).toBeDefined();
expect(result?.id).toBe(defaultCollection.id);
expect(result?.isDefaultCollection).toBe(true);
});
it("returns undefined when no default collection exists", async () => {
const orgId = newGuid() as OrganizationId;
const collection1 = collectionViewDataFactory(orgId);
collection1.type = CollectionTypes.SharedCollection;
const collection2 = collectionViewDataFactory(orgId);
collection2.type = CollectionTypes.SharedCollection;
await setDecryptedState([collection1, collection2]);
const result = await firstValueFrom(collectionService.defaultUserCollection$(userId, orgId));
expect(result).toBeUndefined();
});
it("returns undefined when default collection exists but for different org", async () => {
const orgA = newGuid() as OrganizationId;
const orgB = newGuid() as OrganizationId;
const defaultCollectionForOrgA = collectionViewDataFactory(orgA);
defaultCollectionForOrgA.type = CollectionTypes.DefaultUserCollection;
await setDecryptedState([defaultCollectionForOrgA]);
const result = await firstValueFrom(collectionService.defaultUserCollection$(userId, orgB));
expect(result).toBeUndefined();
});
it("returns undefined when collections array is empty", async () => {
const orgId = newGuid() as OrganizationId;
await setDecryptedState([]);
const result = await firstValueFrom(collectionService.defaultUserCollection$(userId, orgId));
expect(result).toBeUndefined();
});
it("returns correct collection when multiple orgs have default collections", async () => {
const orgA = newGuid() as OrganizationId;
const orgB = newGuid() as OrganizationId;
const defaultCollectionForOrgA = collectionViewDataFactory(orgA);
defaultCollectionForOrgA.type = CollectionTypes.DefaultUserCollection;
const defaultCollectionForOrgB = collectionViewDataFactory(orgB);
defaultCollectionForOrgB.type = CollectionTypes.DefaultUserCollection;
await setDecryptedState([defaultCollectionForOrgA, defaultCollectionForOrgB]);
const result = await firstValueFrom(collectionService.defaultUserCollection$(userId, orgB));
expect(result).toBeDefined();
expect(result?.id).toBe(defaultCollectionForOrgB.id);
expect(result?.organizationId).toBe(orgB);
});
});
const setEncryptedState = (collectionData: CollectionData[] | null) =>
stateProvider.setUserState(
ENCRYPTED_COLLECTION_DATA_KEY,

View File

@@ -87,6 +87,17 @@ export class DefaultCollectionService implements CollectionService {
return result$;
}
defaultUserCollection$(
userId: UserId,
orgId: OrganizationId,
): Observable<CollectionView | undefined> {
return this.decryptedCollections$(userId).pipe(
map((collections) => {
return collections.find((c) => c.isDefaultCollection && c.organizationId === orgId);
}),
);
}
private initializeDecryptedState(userId: UserId): Observable<CollectionView[]> {
return combineLatest([
this.encryptedCollections$(userId),