mirror of
https://github.com/bitwarden/browser.git
synced 2025-02-22 02:21:34 +01:00
[AC-1041] My Vault filter on first login fix (#10301)
* [AC-1041] Ensure organizationTree$ updates whenever the policy observables emit * [AC-1041] Ensure enforcePersonalOwnership updates whenever the policy observable emits * [AC-1041] Do not attempt to pre-select null filter values or read-only collections
This commit is contained in:
parent
fe9d44af6d
commit
1b22320dc5
@ -5,17 +5,28 @@ import {
|
||||
Component,
|
||||
ElementRef,
|
||||
EventEmitter,
|
||||
HostListener,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
Output,
|
||||
TemplateRef,
|
||||
ViewChild,
|
||||
ViewContainerRef,
|
||||
HostListener,
|
||||
OnDestroy,
|
||||
} from "@angular/core";
|
||||
import { BehaviorSubject, concatMap, map, merge, Observable, Subject, takeUntil } from "rxjs";
|
||||
import {
|
||||
BehaviorSubject,
|
||||
combineLatest,
|
||||
concatMap,
|
||||
map,
|
||||
merge,
|
||||
Observable,
|
||||
Subject,
|
||||
takeUntil,
|
||||
} from "rxjs";
|
||||
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
@ -88,6 +99,7 @@ export class VaultSelectComponent implements OnInit, OnDestroy {
|
||||
private viewContainerRef: ViewContainerRef,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private organizationService: OrganizationService,
|
||||
private policyService: PolicyService,
|
||||
) {}
|
||||
|
||||
@HostListener("document:keydown.escape", ["$event"])
|
||||
@ -103,11 +115,13 @@ export class VaultSelectComponent implements OnInit, OnDestroy {
|
||||
.pipe(takeUntil(this._destroy))
|
||||
.pipe(map((orgs) => orgs.sort(Utils.getSortFunction(this.i18nService, "name"))));
|
||||
|
||||
this.organizations$
|
||||
combineLatest([
|
||||
this.organizations$,
|
||||
this.policyService.policyAppliesToActiveUser$(PolicyType.PersonalOwnership),
|
||||
])
|
||||
.pipe(
|
||||
concatMap(async (organizations) => {
|
||||
this.enforcePersonalOwnership =
|
||||
await this.vaultFilterService.checkForPersonalOwnershipPolicy();
|
||||
concatMap(async ([organizations, enforcePersonalOwnership]) => {
|
||||
this.enforcePersonalOwnership = enforcePersonalOwnership;
|
||||
|
||||
if (this.shouldShow(organizations)) {
|
||||
if (this.enforcePersonalOwnership && !this.vaultFilterService.vaultFilter.myVaultOnly) {
|
||||
|
@ -36,6 +36,8 @@ describe("vault filter service", () => {
|
||||
let organizations: ReplaySubject<Organization[]>;
|
||||
let folderViews: ReplaySubject<FolderView[]>;
|
||||
let collectionViews: ReplaySubject<CollectionView[]>;
|
||||
let personalOwnershipPolicy: ReplaySubject<boolean>;
|
||||
let singleOrgPolicy: ReplaySubject<boolean>;
|
||||
let stateProvider: FakeStateProvider;
|
||||
|
||||
const mockUserId = Utils.newGuid() as UserId;
|
||||
@ -56,10 +58,18 @@ describe("vault filter service", () => {
|
||||
organizations = new ReplaySubject<Organization[]>(1);
|
||||
folderViews = new ReplaySubject<FolderView[]>(1);
|
||||
collectionViews = new ReplaySubject<CollectionView[]>(1);
|
||||
personalOwnershipPolicy = new ReplaySubject<boolean>(1);
|
||||
singleOrgPolicy = new ReplaySubject<boolean>(1);
|
||||
|
||||
organizationService.memberOrganizations$ = organizations;
|
||||
folderService.folderViews$ = folderViews;
|
||||
collectionService.decryptedCollections$ = collectionViews;
|
||||
policyService.policyAppliesToActiveUser$
|
||||
.calledWith(PolicyType.PersonalOwnership)
|
||||
.mockReturnValue(personalOwnershipPolicy);
|
||||
policyService.policyAppliesToActiveUser$
|
||||
.calledWith(PolicyType.SingleOrg)
|
||||
.mockReturnValue(singleOrgPolicy);
|
||||
|
||||
vaultFilterService = new VaultFilterService(
|
||||
organizationService,
|
||||
@ -100,6 +110,8 @@ describe("vault filter service", () => {
|
||||
beforeEach(() => {
|
||||
const storedOrgs = [createOrganization("1", "org1"), createOrganization("2", "org2")];
|
||||
organizations.next(storedOrgs);
|
||||
personalOwnershipPolicy.next(false);
|
||||
singleOrgPolicy.next(false);
|
||||
});
|
||||
|
||||
it("returns a nested tree", async () => {
|
||||
@ -111,9 +123,7 @@ describe("vault filter service", () => {
|
||||
});
|
||||
|
||||
it("hides My Vault if personal ownership policy is enabled", async () => {
|
||||
policyService.policyAppliesToUser
|
||||
.calledWith(PolicyType.PersonalOwnership)
|
||||
.mockResolvedValue(true);
|
||||
personalOwnershipPolicy.next(true);
|
||||
|
||||
const tree = await firstValueFrom(vaultFilterService.organizationTree$);
|
||||
|
||||
@ -122,7 +132,7 @@ describe("vault filter service", () => {
|
||||
});
|
||||
|
||||
it("returns 1 organization and My Vault if single organization policy is enabled", async () => {
|
||||
policyService.policyAppliesToUser.calledWith(PolicyType.SingleOrg).mockResolvedValue(true);
|
||||
singleOrgPolicy.next(true);
|
||||
|
||||
const tree = await firstValueFrom(vaultFilterService.organizationTree$);
|
||||
|
||||
@ -132,10 +142,8 @@ describe("vault filter service", () => {
|
||||
});
|
||||
|
||||
it("returns 1 organization if both single organization and personal ownership policies are enabled", async () => {
|
||||
policyService.policyAppliesToUser.calledWith(PolicyType.SingleOrg).mockResolvedValue(true);
|
||||
policyService.policyAppliesToUser
|
||||
.calledWith(PolicyType.PersonalOwnership)
|
||||
.mockResolvedValue(true);
|
||||
singleOrgPolicy.next(true);
|
||||
personalOwnershipPolicy.next(true);
|
||||
|
||||
const tree = await firstValueFrom(vaultFilterService.organizationTree$);
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import {
|
||||
BehaviorSubject,
|
||||
combineLatest,
|
||||
combineLatestWith,
|
||||
firstValueFrom,
|
||||
map,
|
||||
@ -39,10 +40,15 @@ const NestingDelimiter = "/";
|
||||
|
||||
@Injectable()
|
||||
export class VaultFilterService implements VaultFilterServiceAbstraction {
|
||||
organizationTree$: Observable<TreeNode<OrganizationFilter>> =
|
||||
this.organizationService.memberOrganizations$.pipe(
|
||||
switchMap((orgs) => this.buildOrganizationTree(orgs)),
|
||||
);
|
||||
organizationTree$: Observable<TreeNode<OrganizationFilter>> = combineLatest([
|
||||
this.organizationService.memberOrganizations$,
|
||||
this.policyService.policyAppliesToActiveUser$(PolicyType.SingleOrg),
|
||||
this.policyService.policyAppliesToActiveUser$(PolicyType.PersonalOwnership),
|
||||
]).pipe(
|
||||
switchMap(([orgs, singleOrgPolicy, personalOwnershipPolicy]) =>
|
||||
this.buildOrganizationTree(orgs, singleOrgPolicy, personalOwnershipPolicy),
|
||||
),
|
||||
);
|
||||
|
||||
protected _organizationFilter = new BehaviorSubject<Organization>(null);
|
||||
|
||||
@ -125,14 +131,16 @@ export class VaultFilterService implements VaultFilterServiceAbstraction {
|
||||
}
|
||||
|
||||
protected async buildOrganizationTree(
|
||||
orgs?: Organization[],
|
||||
orgs: Organization[],
|
||||
singleOrgPolicy: boolean,
|
||||
personalOwnershipPolicy: boolean,
|
||||
): Promise<TreeNode<OrganizationFilter>> {
|
||||
const headNode = this.getOrganizationFilterHead();
|
||||
if (!(await this.policyService.policyAppliesToUser(PolicyType.PersonalOwnership))) {
|
||||
if (!personalOwnershipPolicy) {
|
||||
const myVaultNode = this.getOrganizationFilterMyVault();
|
||||
headNode.children.push(myVaultNode);
|
||||
}
|
||||
if (await this.policyService.policyAppliesToUser(PolicyType.SingleOrg)) {
|
||||
if (singleOrgPolicy) {
|
||||
orgs = orgs.slice(0, 1);
|
||||
}
|
||||
if (orgs) {
|
||||
|
@ -586,18 +586,24 @@ export class VaultComponent implements OnInit, OnDestroy {
|
||||
async addCipher(cipherType?: CipherType) {
|
||||
const component = await this.editCipher(null);
|
||||
component.type = cipherType || this.activeFilter.cipherType;
|
||||
if (this.activeFilter.organizationId !== "MyVault") {
|
||||
if (
|
||||
this.activeFilter.organizationId !== "MyVault" &&
|
||||
this.activeFilter.organizationId != null
|
||||
) {
|
||||
component.organizationId = this.activeFilter.organizationId;
|
||||
component.collections = (
|
||||
await firstValueFrom(this.vaultFilterService.filteredCollections$)
|
||||
).filter((c) => !c.readOnly && c.id != null);
|
||||
}
|
||||
const selectedColId = this.activeFilter.collectionId;
|
||||
if (selectedColId !== "AllCollections") {
|
||||
component.organizationId = component.collections.find(
|
||||
(collection) => collection.id === selectedColId,
|
||||
)?.organizationId;
|
||||
component.collectionIds = [selectedColId];
|
||||
if (selectedColId !== "AllCollections" && selectedColId != null) {
|
||||
const selectedCollection = (
|
||||
await firstValueFrom(this.vaultFilterService.filteredCollections$)
|
||||
).find((c) => c.id === selectedColId);
|
||||
component.organizationId = selectedCollection?.organizationId;
|
||||
if (!selectedCollection.readOnly) {
|
||||
component.collectionIds = [selectedColId];
|
||||
}
|
||||
}
|
||||
component.folderId = this.activeFilter.folderId;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user