mirror of
https://github.com/bitwarden/browser
synced 2025-12-16 16:23:44 +00:00
[PM-15506] Implement vNextOrganizationService (#12839)
* [PM-15506] Wire up vNextOrganizationService for libs/common and libs/angular (#12683) * Wire up vNextOrganizationService in PolicyService * Wire vNextOrganizationService in SyncService * wire vNextOrganizationService for EventCollectionService * wire vNextOrganizationService for KeyConnectorService * wire up vNextOrganizationService for CipherAuthorizationService * Wire up vNextOrganizationService in PolicyService * Wire vNextOrganizationService in SyncService * wire vNextOrganizationService for EventCollectionService * wire vNextOrganizationService for KeyConnectorService * wire up vNextOrganizationService for CipherAuthorizationService * wire vNextOrganizationService for share.component * wire vNextOrganizationService for collections.component * wire vNextOrganizationServcie for add-account-credit-dialog * wire vNextOrganizationService for vault-filter.service * fix browser errors for vNextOrganizationService implementation in libs * fix desktop errors for vNextOrganizationService implementation for libs * fix linter errors * fix CLI errors on vNextOrganizationServcie implementations for libs * [PM-15506] Wire up vNextOrganizationService for web client (#12810) PR to a feature branch, no need to review until this goes to main. * implement vNextOrganization service for browser client (#12844) PR to feature branch, no need for review yet. * wire vNextOrganizationService for licence and some web router guards * wire vNextOrganizationService in tests * remove vNext notation for OrganizationService and related * Merge branch 'main' into ac/pm-15506-vNextOrganizationService * fix tsstrict error * fix test, fix ts strict error
This commit is contained in:
@@ -1,11 +1,13 @@
|
||||
import { mock } from "jest-mock-extended";
|
||||
import { firstValueFrom, of } from "rxjs";
|
||||
import { Observable, firstValueFrom, of } from "rxjs";
|
||||
|
||||
import { CollectionService, CollectionView } from "@bitwarden/admin-console/common";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { CollectionId, UserId } from "@bitwarden/common/types/guid";
|
||||
|
||||
import { OrganizationService } from "../../admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { Organization } from "../../admin-console/models/domain/organization";
|
||||
import { CollectionId } from "../../types/guid";
|
||||
import { FakeAccountService, mockAccountServiceWith } from "../../../spec";
|
||||
import { CipherView } from "../models/view/cipher.view";
|
||||
|
||||
import {
|
||||
@@ -18,6 +20,8 @@ describe("CipherAuthorizationService", () => {
|
||||
|
||||
const mockCollectionService = mock<CollectionService>();
|
||||
const mockOrganizationService = mock<OrganizationService>();
|
||||
const mockUserId = Utils.newGuid() as UserId;
|
||||
let mockAccountService: FakeAccountService;
|
||||
|
||||
// Mock factories
|
||||
const createMockCipher = (
|
||||
@@ -42,6 +46,7 @@ describe("CipherAuthorizationService", () => {
|
||||
isAdmin = false,
|
||||
editAnyCollection = false,
|
||||
} = {}) => ({
|
||||
id: "org1",
|
||||
allowAdminAccessToAllCollectionItems,
|
||||
canEditAllCiphers,
|
||||
canEditUnassignedCiphers,
|
||||
@@ -53,9 +58,11 @@ describe("CipherAuthorizationService", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockAccountService = mockAccountServiceWith(mockUserId);
|
||||
cipherAuthorizationService = new DefaultCipherAuthorizationService(
|
||||
mockCollectionService,
|
||||
mockOrganizationService,
|
||||
mockAccountService,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -72,7 +79,9 @@ describe("CipherAuthorizationService", () => {
|
||||
it("should return true if isAdminConsoleAction is true and cipher is unassigned", (done) => {
|
||||
const cipher = createMockCipher("org1", []) as CipherView;
|
||||
const organization = createMockOrganization({ canEditUnassignedCiphers: true });
|
||||
mockOrganizationService.get$.mockReturnValue(of(organization as Organization));
|
||||
mockOrganizationService.organizations$.mockReturnValue(
|
||||
of([organization]) as Observable<Organization[]>,
|
||||
);
|
||||
|
||||
cipherAuthorizationService.canDeleteCipher$(cipher, [], true).subscribe((result) => {
|
||||
expect(result).toBe(true);
|
||||
@@ -83,11 +92,13 @@ describe("CipherAuthorizationService", () => {
|
||||
it("should return true if isAdminConsoleAction is true and user can edit all ciphers in the org", (done) => {
|
||||
const cipher = createMockCipher("org1", ["col1"]) as CipherView;
|
||||
const organization = createMockOrganization({ canEditAllCiphers: true });
|
||||
mockOrganizationService.get$.mockReturnValue(of(organization as Organization));
|
||||
mockOrganizationService.organizations$.mockReturnValue(
|
||||
of([organization]) as Observable<Organization[]>,
|
||||
);
|
||||
|
||||
cipherAuthorizationService.canDeleteCipher$(cipher, [], true).subscribe((result) => {
|
||||
expect(result).toBe(true);
|
||||
expect(mockOrganizationService.get$).toHaveBeenCalledWith("org1");
|
||||
expect(mockOrganizationService.organizations$).toHaveBeenCalledWith(mockUserId);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -95,7 +106,7 @@ describe("CipherAuthorizationService", () => {
|
||||
it("should return false if isAdminConsoleAction is true but user does not have permission to edit unassigned ciphers", (done) => {
|
||||
const cipher = createMockCipher("org1", []) as CipherView;
|
||||
const organization = createMockOrganization({ canEditUnassignedCiphers: false });
|
||||
mockOrganizationService.get$.mockReturnValue(of(organization as Organization));
|
||||
mockOrganizationService.organizations$.mockReturnValue(of([organization] as Organization[]));
|
||||
|
||||
cipherAuthorizationService.canDeleteCipher$(cipher, [], true).subscribe((result) => {
|
||||
expect(result).toBe(false);
|
||||
@@ -106,8 +117,8 @@ describe("CipherAuthorizationService", () => {
|
||||
it("should return true if activeCollectionId is provided and has manage permission", (done) => {
|
||||
const cipher = createMockCipher("org1", ["col1", "col2"]) as CipherView;
|
||||
const activeCollectionId = "col1" as CollectionId;
|
||||
const org = createMockOrganization();
|
||||
mockOrganizationService.get$.mockReturnValue(of(org as Organization));
|
||||
const organization = createMockOrganization();
|
||||
mockOrganizationService.organizations$.mockReturnValue(of([organization] as Organization[]));
|
||||
|
||||
const allCollections = [
|
||||
createMockCollection("col1", true),
|
||||
@@ -132,8 +143,8 @@ describe("CipherAuthorizationService", () => {
|
||||
it("should return false if activeCollectionId is provided and manage permission is not present", (done) => {
|
||||
const cipher = createMockCipher("org1", ["col1", "col2"]) as CipherView;
|
||||
const activeCollectionId = "col1" as CollectionId;
|
||||
const org = createMockOrganization();
|
||||
mockOrganizationService.get$.mockReturnValue(of(org as Organization));
|
||||
const organization = createMockOrganization();
|
||||
mockOrganizationService.organizations$.mockReturnValue(of([organization] as Organization[]));
|
||||
|
||||
const allCollections = [
|
||||
createMockCollection("col1", false),
|
||||
@@ -157,8 +168,8 @@ describe("CipherAuthorizationService", () => {
|
||||
|
||||
it("should return true if any collection has manage permission", (done) => {
|
||||
const cipher = createMockCipher("org1", ["col1", "col2", "col3"]) as CipherView;
|
||||
const org = createMockOrganization();
|
||||
mockOrganizationService.get$.mockReturnValue(of(org as Organization));
|
||||
const organization = createMockOrganization();
|
||||
mockOrganizationService.organizations$.mockReturnValue(of([organization] as Organization[]));
|
||||
|
||||
const allCollections = [
|
||||
createMockCollection("col1", false),
|
||||
@@ -182,8 +193,8 @@ describe("CipherAuthorizationService", () => {
|
||||
|
||||
it("should return false if no collection has manage permission", (done) => {
|
||||
const cipher = createMockCipher("org1", ["col1", "col2"]) as CipherView;
|
||||
const org = createMockOrganization();
|
||||
mockOrganizationService.get$.mockReturnValue(of(org as Organization));
|
||||
const organization = createMockOrganization();
|
||||
mockOrganizationService.organizations$.mockReturnValue(of([organization] as Organization[]));
|
||||
|
||||
const allCollections = [
|
||||
createMockCollection("col1", false),
|
||||
@@ -216,7 +227,9 @@ describe("CipherAuthorizationService", () => {
|
||||
it("should return true for admin users", async () => {
|
||||
const cipher = createMockCipher("org1", []) as CipherView;
|
||||
const organization = createMockOrganization({ isAdmin: true });
|
||||
mockOrganizationService.get$.mockReturnValue(of(organization as Organization));
|
||||
mockOrganizationService.organizations$.mockReturnValue(
|
||||
of([organization] as Organization[]),
|
||||
);
|
||||
|
||||
const result = await firstValueFrom(
|
||||
cipherAuthorizationService.canCloneCipher$(cipher, true),
|
||||
@@ -227,7 +240,9 @@ describe("CipherAuthorizationService", () => {
|
||||
it("should return true for custom user with canEditAnyCollection", async () => {
|
||||
const cipher = createMockCipher("org1", []) as CipherView;
|
||||
const organization = createMockOrganization({ editAnyCollection: true });
|
||||
mockOrganizationService.get$.mockReturnValue(of(organization as Organization));
|
||||
mockOrganizationService.organizations$.mockReturnValue(
|
||||
of([organization] as Organization[]),
|
||||
);
|
||||
|
||||
const result = await firstValueFrom(
|
||||
cipherAuthorizationService.canCloneCipher$(cipher, true),
|
||||
@@ -240,7 +255,9 @@ describe("CipherAuthorizationService", () => {
|
||||
it("should return true if at least one cipher collection has manage permission", async () => {
|
||||
const cipher = createMockCipher("org1", ["col1", "col2"]) as CipherView;
|
||||
const organization = createMockOrganization();
|
||||
mockOrganizationService.get$.mockReturnValue(of(organization as Organization));
|
||||
mockOrganizationService.organizations$.mockReturnValue(
|
||||
of([organization] as Organization[]),
|
||||
);
|
||||
|
||||
const allCollections = [
|
||||
createMockCollection("col1", true),
|
||||
@@ -257,7 +274,9 @@ describe("CipherAuthorizationService", () => {
|
||||
it("should return false if no collection has manage permission", async () => {
|
||||
const cipher = createMockCipher("org1", ["col1", "col2"]) as CipherView;
|
||||
const organization = createMockOrganization();
|
||||
mockOrganizationService.get$.mockReturnValue(of(organization as Organization));
|
||||
mockOrganizationService.organizations$.mockReturnValue(
|
||||
of([organization] as Organization[]),
|
||||
);
|
||||
|
||||
const allCollections = [
|
||||
createMockCollection("col1", false),
|
||||
|
||||
@@ -3,9 +3,10 @@
|
||||
import { map, Observable, of, shareReplay, switchMap } from "rxjs";
|
||||
|
||||
import { CollectionService } from "@bitwarden/admin-console/common";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { CollectionId } from "@bitwarden/common/types/guid";
|
||||
|
||||
import { OrganizationService } from "../../admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { CollectionId } from "../../types/guid";
|
||||
import { Cipher } from "../models/domain/cipher";
|
||||
import { CipherView } from "../models/view/cipher.view";
|
||||
|
||||
@@ -51,8 +52,14 @@ export class DefaultCipherAuthorizationService implements CipherAuthorizationSer
|
||||
constructor(
|
||||
private collectionService: CollectionService,
|
||||
private organizationService: OrganizationService,
|
||||
private accountService: AccountService,
|
||||
) {}
|
||||
|
||||
private organization$ = (cipher: CipherLike) =>
|
||||
this.accountService.activeAccount$.pipe(
|
||||
switchMap((account) => this.organizationService.organizations$(account?.id)),
|
||||
map((orgs) => orgs.find((org) => org.id === cipher.organizationId)),
|
||||
);
|
||||
/**
|
||||
*
|
||||
* {@link CipherAuthorizationService.canDeleteCipher$}
|
||||
@@ -66,7 +73,7 @@ export class DefaultCipherAuthorizationService implements CipherAuthorizationSer
|
||||
return of(true);
|
||||
}
|
||||
|
||||
return this.organizationService.get$(cipher.organizationId).pipe(
|
||||
return this.organization$(cipher).pipe(
|
||||
switchMap((organization) => {
|
||||
if (isAdminConsoleAction) {
|
||||
// If the user is an admin, they can delete an unassigned cipher
|
||||
@@ -104,7 +111,7 @@ export class DefaultCipherAuthorizationService implements CipherAuthorizationSer
|
||||
return of(true);
|
||||
}
|
||||
|
||||
return this.organizationService.get$(cipher.organizationId).pipe(
|
||||
return this.organization$(cipher).pipe(
|
||||
switchMap((organization) => {
|
||||
// Admins and custom users can always clone when in the Admin Console
|
||||
if (
|
||||
|
||||
Reference in New Issue
Block a user