1
0
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:
Brandon Treston
2025-01-22 15:20:25 -05:00
committed by GitHub
parent ba4d762dc1
commit a949f793ed
163 changed files with 1972 additions and 1246 deletions

View File

@@ -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),

View File

@@ -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 (