1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-11-25 12:15:18 +01:00

[PM-6199] VaultFilterService keeps decrypted collections in memory after logout (#8120)

* updated vault filter service to make use of collection service

* removed fix me

* reverted to use reload collections on org vault as collection admin service does not support state management yet

* fixed statement
This commit is contained in:
SmithThe4th 2024-03-21 16:42:34 -04:00 committed by GitHub
parent d6fa7a4e46
commit 6dc6aec88e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 30 additions and 39 deletions

View File

@ -5,6 +5,7 @@ import { CollectionView } from "@bitwarden/common/src/vault/models/view/collecti
import { FolderView } from "@bitwarden/common/src/vault/models/view/folder.view"; import { FolderView } from "@bitwarden/common/src/vault/models/view/folder.view";
import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node"; import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node";
import { CollectionAdminView } from "../../../../core/views/collection-admin.view";
import { import {
CipherTypeFilter, CipherTypeFilter,
CollectionFilter, CollectionFilter,
@ -20,7 +21,6 @@ export abstract class VaultFilterService {
folderTree$: Observable<TreeNode<FolderFilter>>; folderTree$: Observable<TreeNode<FolderFilter>>;
collectionTree$: Observable<TreeNode<CollectionFilter>>; collectionTree$: Observable<TreeNode<CollectionFilter>>;
cipherTypeTree$: Observable<TreeNode<CipherTypeFilter>>; cipherTypeTree$: Observable<TreeNode<CipherTypeFilter>>;
reloadCollections: (collections: CollectionView[]) => void;
getCollectionNodeFromTree: (id: string) => Promise<TreeNode<CollectionFilter>>; getCollectionNodeFromTree: (id: string) => Promise<TreeNode<CollectionFilter>>;
setCollapsedFilterNodes: (collapsedFilterNodes: Set<string>) => Promise<void>; setCollapsedFilterNodes: (collapsedFilterNodes: Set<string>) => Promise<void>;
expandOrgFilter: () => Promise<void>; expandOrgFilter: () => Promise<void>;
@ -30,4 +30,6 @@ export abstract class VaultFilterService {
head: CipherTypeFilter, head: CipherTypeFilter,
array: CipherTypeFilter[], array: CipherTypeFilter[],
) => Observable<TreeNode<CipherTypeFilter>>; ) => Observable<TreeNode<CipherTypeFilter>>;
// TODO: Remove this from org vault when collection admin service adopts state management
reloadCollections?: (collections: CollectionAdminView[]) => void;
} }

View File

@ -15,6 +15,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
import { Utils } from "@bitwarden/common/platform/misc/utils"; import { Utils } from "@bitwarden/common/platform/misc/utils";
import { UserId } from "@bitwarden/common/types/guid"; import { UserId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service";
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view"; import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view";
@ -31,8 +32,10 @@ describe("vault filter service", () => {
let cipherService: MockProxy<CipherService>; let cipherService: MockProxy<CipherService>;
let policyService: MockProxy<PolicyService>; let policyService: MockProxy<PolicyService>;
let i18nService: MockProxy<I18nService>; let i18nService: MockProxy<I18nService>;
let collectionService: MockProxy<CollectionService>;
let organizations: ReplaySubject<Organization[]>; let organizations: ReplaySubject<Organization[]>;
let folderViews: ReplaySubject<FolderView[]>; let folderViews: ReplaySubject<FolderView[]>;
let collectionViews: ReplaySubject<CollectionView[]>;
let stateProvider: FakeStateProvider; let stateProvider: FakeStateProvider;
const mockUserId = Utils.newGuid() as UserId; const mockUserId = Utils.newGuid() as UserId;
@ -48,12 +51,15 @@ describe("vault filter service", () => {
accountService = mockAccountServiceWith(mockUserId); accountService = mockAccountServiceWith(mockUserId);
stateProvider = new FakeStateProvider(accountService); stateProvider = new FakeStateProvider(accountService);
i18nService.collator = new Intl.Collator("en-US"); i18nService.collator = new Intl.Collator("en-US");
collectionService = mock<CollectionService>();
organizations = new ReplaySubject<Organization[]>(1); organizations = new ReplaySubject<Organization[]>(1);
folderViews = new ReplaySubject<FolderView[]>(1); folderViews = new ReplaySubject<FolderView[]>(1);
collectionViews = new ReplaySubject<CollectionView[]>(1);
organizationService.memberOrganizations$ = organizations; organizationService.memberOrganizations$ = organizations;
folderService.folderViews$ = folderViews; folderService.folderViews$ = folderViews;
collectionService.decryptedCollections$ = collectionViews;
vaultFilterService = new VaultFilterService( vaultFilterService = new VaultFilterService(
organizationService, organizationService,
@ -62,6 +68,7 @@ describe("vault filter service", () => {
policyService, policyService,
i18nService, i18nService,
stateProvider, stateProvider,
collectionService,
); );
collapsedGroupingsState = stateProvider.activeUser.getFake(COLLAPSED_GROUPINGS); collapsedGroupingsState = stateProvider.activeUser.getFake(COLLAPSED_GROUPINGS);
}); });
@ -196,9 +203,7 @@ describe("vault filter service", () => {
createCollectionView("1", "collection 1", "org test id"), createCollectionView("1", "collection 1", "org test id"),
createCollectionView("2", "collection 2", "non matching org id"), createCollectionView("2", "collection 2", "non matching org id"),
]; ];
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. collectionViews.next(storedCollections);
// eslint-disable-next-line @typescript-eslint/no-floating-promises
vaultFilterService.reloadCollections(storedCollections);
await expect(firstValueFrom(vaultFilterService.filteredCollections$)).resolves.toEqual([ await expect(firstValueFrom(vaultFilterService.filteredCollections$)).resolves.toEqual([
createCollectionView("1", "collection 1", "org test id"), createCollectionView("1", "collection 1", "org test id"),
@ -213,9 +218,7 @@ describe("vault filter service", () => {
createCollectionView("id-2", "Collection 1/Collection 2", "org test id"), createCollectionView("id-2", "Collection 1/Collection 2", "org test id"),
createCollectionView("id-3", "Collection 1/Collection 3", "org test id"), createCollectionView("id-3", "Collection 1/Collection 3", "org test id"),
]; ];
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. collectionViews.next(storedCollections);
// eslint-disable-next-line @typescript-eslint/no-floating-promises
vaultFilterService.reloadCollections(storedCollections);
const result = await firstValueFrom(vaultFilterService.collectionTree$); const result = await firstValueFrom(vaultFilterService.collectionTree$);
@ -228,9 +231,7 @@ describe("vault filter service", () => {
createCollectionView("id-1", "Collection 1", "org test id"), createCollectionView("id-1", "Collection 1", "org test id"),
createCollectionView("id-3", "Collection 1/Collection 2/Collection 3", "org test id"), createCollectionView("id-3", "Collection 1/Collection 2/Collection 3", "org test id"),
]; ];
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. collectionViews.next(storedCollections);
// eslint-disable-next-line @typescript-eslint/no-floating-promises
vaultFilterService.reloadCollections(storedCollections);
const result = await firstValueFrom(vaultFilterService.collectionTree$); const result = await firstValueFrom(vaultFilterService.collectionTree$);
@ -246,9 +247,7 @@ describe("vault filter service", () => {
createCollectionView("id-3", "Collection 1/Collection 2/Collection 3", "org test id"), createCollectionView("id-3", "Collection 1/Collection 2/Collection 3", "org test id"),
createCollectionView("id-4", "Collection 1/Collection 4", "org test id"), createCollectionView("id-4", "Collection 1/Collection 4", "org test id"),
]; ];
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. collectionViews.next(storedCollections);
// eslint-disable-next-line @typescript-eslint/no-floating-promises
vaultFilterService.reloadCollections(storedCollections);
const result = await firstValueFrom(vaultFilterService.collectionTree$); const result = await firstValueFrom(vaultFilterService.collectionTree$);
@ -266,9 +265,7 @@ describe("vault filter service", () => {
createCollectionView("id-1", "Collection 1", "org test id"), createCollectionView("id-1", "Collection 1", "org test id"),
createCollectionView("id-3", "Collection 1/Collection 2/Collection 3", "org test id"), createCollectionView("id-3", "Collection 1/Collection 2/Collection 3", "org test id"),
]; ];
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. collectionViews.next(storedCollections);
// eslint-disable-next-line @typescript-eslint/no-floating-promises
vaultFilterService.reloadCollections(storedCollections);
const result = await firstValueFrom(vaultFilterService.collectionTree$); const result = await firstValueFrom(vaultFilterService.collectionTree$);

View File

@ -1,13 +1,11 @@
import { Injectable } from "@angular/core"; import { Injectable } from "@angular/core";
import { import {
BehaviorSubject, BehaviorSubject,
combineLatest,
combineLatestWith, combineLatestWith,
firstValueFrom, firstValueFrom,
map, map,
Observable, Observable,
of, of,
ReplaySubject,
switchMap, switchMap,
} from "rxjs"; } from "rxjs";
@ -18,6 +16,7 @@ import { Organization } from "@bitwarden/common/admin-console/models/domain/orga
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { ActiveUserState, StateProvider } from "@bitwarden/common/platform/state"; import { ActiveUserState, StateProvider } from "@bitwarden/common/platform/state";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service";
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherType } from "@bitwarden/common/vault/enums";
import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node"; import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node";
@ -57,17 +56,14 @@ export class VaultFilterService implements VaultFilterServiceAbstraction {
map((folders) => this.buildFolderTree(folders)), map((folders) => this.buildFolderTree(folders)),
); );
// TODO: Remove once collections is refactored with observables filteredCollections$: Observable<CollectionView[]> =
// replace with collection service observable this.collectionService.decryptedCollections$.pipe(
private collectionViews$ = new ReplaySubject<CollectionView[]>(1); combineLatestWith(this._organizationFilter),
filteredCollections$: Observable<CollectionView[]> = combineLatest([
this.collectionViews$,
this._organizationFilter,
]).pipe(
switchMap(([collections, org]) => { switchMap(([collections, org]) => {
return this.filterCollections(collections, org); return this.filterCollections(collections, org);
}), }),
); );
collectionTree$: Observable<TreeNode<CollectionFilter>> = this.filteredCollections$.pipe( collectionTree$: Observable<TreeNode<CollectionFilter>> = this.filteredCollections$.pipe(
map((collections) => this.buildCollectionTree(collections)), map((collections) => this.buildCollectionTree(collections)),
); );
@ -87,12 +83,9 @@ export class VaultFilterService implements VaultFilterServiceAbstraction {
protected policyService: PolicyService, protected policyService: PolicyService,
protected i18nService: I18nService, protected i18nService: I18nService,
protected stateProvider: StateProvider, protected stateProvider: StateProvider,
protected collectionService: CollectionService,
) {} ) {}
async reloadCollections(collections: CollectionView[]) {
this.collectionViews$.next(collections);
}
async getCollectionNodeFromTree(id: string) { async getCollectionNodeFromTree(id: string) {
const collections = await firstValueFrom(this.collectionTree$); const collections = await firstValueFrom(this.collectionTree$);
return ServiceUtils.getTreeNodeObject(collections, id) as TreeNode<CollectionFilter>; return ServiceUtils.getTreeNodeObject(collections, id) as TreeNode<CollectionFilter>;

View File

@ -406,10 +406,6 @@ export class VaultComponent implements OnInit, OnDestroy {
(filter.organizationId === undefined || filter.organizationId === Unassigned); (filter.organizationId === undefined || filter.organizationId === Unassigned);
this.isEmpty = collections?.length === 0 && ciphers?.length === 0; this.isEmpty = collections?.length === 0 && ciphers?.length === 0;
// This is a temporary fix to avoid double fetching collections.
// TODO: Remove when implementing new VVR menu
this.vaultFilterService.reloadCollections(allCollections);
this.performingInitialLoad = false; this.performingInitialLoad = false;
this.refreshing = false; this.refreshing = false;
}, },

View File

@ -6,11 +6,11 @@ import { PolicyService } from "@bitwarden/common/admin-console/abstractions/poli
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { StateProvider } from "@bitwarden/common/platform/state"; import { StateProvider } from "@bitwarden/common/platform/state";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service";
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node"; import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node";
import { CollectionAdminView } from "../../../vault/core/views/collection-admin.view"; import { CollectionAdminView } from "../../../vault/core/views/collection-admin.view";
import { CollectionAdminService } from "../../core/collection-admin.service";
import { VaultFilterService as BaseVaultFilterService } from "../../individual-vault/vault-filter/services/vault-filter.service"; import { VaultFilterService as BaseVaultFilterService } from "../../individual-vault/vault-filter/services/vault-filter.service";
import { CollectionFilter } from "../../individual-vault/vault-filter/shared/models/vault-filter.type"; import { CollectionFilter } from "../../individual-vault/vault-filter/shared/models/vault-filter.type";
@ -32,7 +32,7 @@ export class VaultFilterService extends BaseVaultFilterService implements OnDest
policyService: PolicyService, policyService: PolicyService,
i18nService: I18nService, i18nService: I18nService,
stateProvider: StateProvider, stateProvider: StateProvider,
protected collectionAdminService: CollectionAdminService, collectionService: CollectionService,
) { ) {
super( super(
organizationService, organizationService,
@ -41,6 +41,7 @@ export class VaultFilterService extends BaseVaultFilterService implements OnDest
policyService, policyService,
i18nService, i18nService,
stateProvider, stateProvider,
collectionService,
); );
} }

View File

@ -7,6 +7,8 @@ import { TreeNode } from "../models/domain/tree-node";
import { CollectionView } from "../models/view/collection.view"; import { CollectionView } from "../models/view/collection.view";
export abstract class CollectionService { export abstract class CollectionService {
decryptedCollections$: Observable<CollectionView[]>;
clearActiveUserCache: () => Promise<void>; clearActiveUserCache: () => Promise<void>;
encrypt: (model: CollectionView) => Promise<Collection>; encrypt: (model: CollectionView) => Promise<Collection>;
decryptedCollectionViews$: (ids: CollectionId[]) => Observable<CollectionView[]>; decryptedCollectionViews$: (ids: CollectionId[]) => Observable<CollectionView[]>;