1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-08-27 23:31:41 +02:00

add check for PersonalOwnershipPolicy in vault filters (#9570)

This commit is contained in:
Nick Krantz 2024-06-10 14:25:21 -05:00 committed by GitHub
parent d594b680f9
commit cbc34950fb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 129 additions and 35 deletions

View File

@ -3,6 +3,8 @@ import { FormBuilder } from "@angular/forms";
import { BehaviorSubject, skipWhile } from "rxjs"; import { BehaviorSubject, skipWhile } from "rxjs";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; 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 { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { ProductType } from "@bitwarden/common/enums"; import { ProductType } from "@bitwarden/common/enums";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@ -23,6 +25,7 @@ describe("VaultPopupListFiltersService", () => {
const folderViews$ = new BehaviorSubject([]); const folderViews$ = new BehaviorSubject([]);
const cipherViews$ = new BehaviorSubject({}); const cipherViews$ = new BehaviorSubject({});
const decryptedCollections$ = new BehaviorSubject<CollectionView[]>([]); const decryptedCollections$ = new BehaviorSubject<CollectionView[]>([]);
const policyAppliesToActiveUser$ = new BehaviorSubject<boolean>(false);
const collectionService = { const collectionService = {
decryptedCollections$, decryptedCollections$,
@ -45,9 +48,15 @@ describe("VaultPopupListFiltersService", () => {
t: (key: string) => key, t: (key: string) => key,
} as I18nService; } as I18nService;
const policyService = {
policyAppliesToActiveUser$: jest.fn(() => policyAppliesToActiveUser$),
};
beforeEach(() => { beforeEach(() => {
memberOrganizations$.next([]); memberOrganizations$.next([]);
decryptedCollections$.next([]); decryptedCollections$.next([]);
policyAppliesToActiveUser$.next(false);
policyService.policyAppliesToActiveUser$.mockClear();
collectionService.getAllNested = () => Promise.resolve([]); collectionService.getAllNested = () => Promise.resolve([]);
TestBed.configureTestingModule({ TestBed.configureTestingModule({
@ -72,6 +81,10 @@ describe("VaultPopupListFiltersService", () => {
provide: CollectionService, provide: CollectionService,
useValue: collectionService, useValue: collectionService,
}, },
{
provide: PolicyService,
useValue: policyService,
},
{ provide: FormBuilder, useClass: FormBuilder }, { provide: FormBuilder, useClass: FormBuilder },
], ],
}); });
@ -127,6 +140,65 @@ describe("VaultPopupListFiltersService", () => {
}); });
}); });
describe("PersonalOwnership policy", () => {
it('calls policyAppliesToActiveUser$ with "PersonalOwnership"', () => {
expect(policyService.policyAppliesToActiveUser$).toHaveBeenCalledWith(
PolicyType.PersonalOwnership,
);
});
it("returns an empty array when the policy applies and there is a single organization", (done) => {
policyAppliesToActiveUser$.next(true);
memberOrganizations$.next([
{ name: "bobby's org", id: "1234-3323-23223" },
] as Organization[]);
service.organizations$.subscribe((organizations) => {
expect(organizations).toEqual([]);
done();
});
});
it('adds "myVault" when the policy does not apply and there are multiple organizations', (done) => {
policyAppliesToActiveUser$.next(false);
const orgs = [
{ name: "bobby's org", id: "1234-3323-23223" },
{ name: "alice's org", id: "2223-4343-99888" },
] as Organization[];
memberOrganizations$.next(orgs);
service.organizations$.subscribe((organizations) => {
expect(organizations.map((o) => o.label)).toEqual([
"myVault",
"alice's org",
"bobby's org",
]);
done();
});
});
it('does not add "myVault" the policy applies and there are multiple organizations', (done) => {
policyAppliesToActiveUser$.next(true);
const orgs = [
{ name: "bobby's org", id: "1234-3323-23223" },
{ name: "alice's org", id: "2223-3242-99888" },
{ name: "catherine's org", id: "77733-4343-99888" },
] as Organization[];
memberOrganizations$.next(orgs);
service.organizations$.subscribe((organizations) => {
expect(organizations.map((o) => o.label)).toEqual([
"alice's org",
"bobby's org",
"catherine's org",
]);
done();
});
});
});
describe("icons", () => { describe("icons", () => {
it("sets family icon for family organizations", (done) => { it("sets family icon for family organizations", (done) => {
const orgs = [ const orgs = [

View File

@ -13,6 +13,8 @@ import {
import { DynamicTreeNode } from "@bitwarden/angular/vault/vault-filter/models/dynamic-tree-node.model"; import { DynamicTreeNode } from "@bitwarden/angular/vault/vault-filter/models/dynamic-tree-node.model";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; 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 { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { ProductType } from "@bitwarden/common/enums"; import { ProductType } from "@bitwarden/common/enums";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@ -88,6 +90,7 @@ export class VaultPopupListFiltersService {
private i18nService: I18nService, private i18nService: I18nService,
private collectionService: CollectionService, private collectionService: CollectionService,
private formBuilder: FormBuilder, private formBuilder: FormBuilder,
private policyService: PolicyService,
) { ) {
this.filterForm.controls.organization.valueChanges this.filterForm.controls.organization.valueChanges
.pipe(takeUntilDestroyed()) .pipe(takeUntilDestroyed())
@ -167,44 +170,63 @@ export class VaultPopupListFiltersService {
/** /**
* Organization array structured to be directly passed to `ChipSelectComponent` * Organization array structured to be directly passed to `ChipSelectComponent`
*/ */
organizations$: Observable<ChipSelectOption<Organization>[]> = organizations$: Observable<ChipSelectOption<Organization>[]> = combineLatest([
this.organizationService.memberOrganizations$.pipe( this.organizationService.memberOrganizations$,
map((orgs) => orgs.sort(Utils.getSortFunction(this.i18nService, "name"))), this.policyService.policyAppliesToActiveUser$(PolicyType.PersonalOwnership),
map((orgs) => { ]).pipe(
if (!orgs.length) { map(([orgs, personalOwnershipApplies]): [Organization[], boolean] => [
return []; orgs.sort(Utils.getSortFunction(this.i18nService, "name")),
} personalOwnershipApplies,
]),
map(([orgs, personalOwnershipApplies]) => {
// When there are no organizations return an empty array,
// resulting in the org filter being hidden
if (!orgs.length) {
return [];
}
return [ // When there is only one organization and personal ownership policy applies,
// When the user is a member of an organization, make the "My Vault" option available // return an empty array, resulting in the org filter being hidden
{ if (orgs.length === 1 && personalOwnershipApplies) {
value: { id: MY_VAULT_ID } as Organization, return [];
label: this.i18nService.t("myVault"), }
icon: "bwi-user",
},
...orgs.map((org) => {
let icon = "bwi-business";
if (!org.enabled) { const myVaultOrg: ChipSelectOption<Organization>[] = [];
// Show a warning icon if the organization is deactivated
icon = "bwi-exclamation-triangle tw-text-danger";
} else if (
org.planProductType === ProductType.Families ||
org.planProductType === ProductType.Free
) {
// Show a family icon if the organization is a family or free org
icon = "bwi-family";
}
return { // Only add "My vault" if personal ownership policy does not apply
value: org, if (!personalOwnershipApplies) {
label: org.name, myVaultOrg.push({
icon, value: { id: MY_VAULT_ID } as Organization,
}; label: this.i18nService.t("myVault"),
}), icon: "bwi-user",
]; });
}), }
);
return [
...myVaultOrg,
...orgs.map((org) => {
let icon = "bwi-business";
if (!org.enabled) {
// Show a warning icon if the organization is deactivated
icon = "bwi-exclamation-triangle tw-text-danger";
} else if (
org.planProductType === ProductType.Families ||
org.planProductType === ProductType.Free
) {
// Show a family icon if the organization is a family or free org
icon = "bwi-family";
}
return {
value: org,
label: org.name,
icon,
};
}),
];
}),
);
/** /**
* Folder array structured to be directly passed to `ChipSelectComponent` * Folder array structured to be directly passed to `ChipSelectComponent`