1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-10 21:33:27 +00:00

[PM-23920] Admin Console - adopt strongly typed guids (#15814)

Update organization, collection and policy to use strongly typed IDs
This commit is contained in:
Thomas Rittson
2025-08-06 15:27:52 +10:00
committed by GitHub
parent 598348fcc1
commit 61cd0c4f51
36 changed files with 122 additions and 67 deletions

View File

@@ -4,7 +4,10 @@ import { CollectionAccessSelectionView } from "./collection-access-selection.vie
import { CollectionAccessDetailsResponse } from "./collection.response";
import { CollectionView } from "./collection.view";
// TODO: this is used to represent the pseudo "Unassigned" collection as well as
// the user's personal vault (as a pseudo organization). This should be separated out into different values.
export const Unassigned = "unassigned";
export type Unassigned = typeof Unassigned;
export class CollectionAdminView extends CollectionView {
groups: CollectionAccessSelectionView[] = [];

View File

@@ -54,7 +54,7 @@ describe("Collection", () => {
it("Decrypt", async () => {
const collection = new Collection();
collection.id = "id";
collection.id = "id" as CollectionId;
collection.organizationId = "orgId" as OrganizationId;
collection.name = mockEnc("encName");
collection.externalId = "extId";

View File

@@ -1,5 +1,6 @@
import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string";
import Domain, { EncryptableKeys } from "@bitwarden/common/platform/models/domain/domain-base";
import { CollectionId, OrganizationId } from "@bitwarden/common/types/guid";
import { OrgKey } from "@bitwarden/common/types/key";
import { CollectionData } from "./collection.data";
@@ -13,8 +14,8 @@ export const CollectionTypes = {
export type CollectionType = (typeof CollectionTypes)[keyof typeof CollectionTypes];
export class Collection extends Domain {
id: string | undefined;
organizationId: string | undefined;
id: CollectionId | undefined;
organizationId: OrganizationId | undefined;
name: EncString | undefined;
externalId: string | undefined;
readOnly: boolean = false;

View File

@@ -2,6 +2,7 @@ import { Jsonify } from "type-fest";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { View } from "@bitwarden/common/models/view/view";
import { CollectionId, OrganizationId } from "@bitwarden/common/types/guid";
import { ITreeNodeObject } from "@bitwarden/common/vault/models/domain/tree-node";
import { Collection, CollectionType, CollectionTypes } from "./collection";
@@ -10,8 +11,8 @@ import { CollectionAccessDetailsResponse } from "./collection.response";
export const NestingDelimiter = "/";
export class CollectionView implements View, ITreeNodeObject {
id: string | undefined;
organizationId: string | undefined;
id: CollectionId | undefined;
organizationId: OrganizationId | undefined;
name: string = "";
externalId: string | undefined;
// readOnly applies to the items within a collection

View File

@@ -5,7 +5,7 @@ import { combineLatest, Observable, of, switchMap } from "rxjs";
// eslint-disable-next-line no-restricted-imports
import { CollectionService } from "@bitwarden/admin-console/common";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { UserId } from "@bitwarden/common/types/guid";
import { OrganizationId, UserId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { DefaultSingleNudgeService } from "../default-single-nudge.service";
@@ -42,7 +42,7 @@ export class EmptyVaultNudgeService extends DefaultSingleNudgeService {
const orgIds = new Set(orgs.map((org) => org.id));
const canCreateCollections = orgs.some((org) => org.canCreateNewCollections);
const hasManageCollections = collections.some(
(c) => c.manage && orgIds.has(c.organizationId!),
(c) => c.manage && orgIds.has(c.organizationId! as OrganizationId),
);
// When the user has dismissed the nudge or spotlight, return the nudge status directly

View File

@@ -5,7 +5,7 @@ import { combineLatest, Observable, of, switchMap } from "rxjs";
// eslint-disable-next-line no-restricted-imports
import { CollectionService } from "@bitwarden/admin-console/common";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { UserId } from "@bitwarden/common/types/guid";
import { OrganizationId, UserId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { DefaultSingleNudgeService } from "../default-single-nudge.service";
@@ -46,7 +46,7 @@ export class VaultSettingsImportNudgeService extends DefaultSingleNudgeService {
const orgIds = new Set(orgs.map((org) => org.id));
const canCreateCollections = orgs.some((org) => org.canCreateNewCollections);
const hasManageCollections = collections.some(
(c) => c.manage && orgIds.has(c.organizationId!),
(c) => c.manage && orgIds.has(c.organizationId! as OrganizationId),
);
// When the user has dismissed the nudge or spotlight, return the nudge status directly

View File

@@ -3,12 +3,13 @@
import { Jsonify } from "type-fest";
import { ProductTierType } from "../../../billing/enums";
import { OrganizationId } from "../../../types/guid";
import { OrganizationUserStatusType, OrganizationUserType, ProviderType } from "../../enums";
import { PermissionsApi } from "../api/permissions.api";
import { OrganizationData } from "../data/organization.data";
export class Organization {
id: string;
id: OrganizationId;
name: string;
status: OrganizationUserStatusType;
@@ -99,7 +100,7 @@ export class Organization {
return;
}
this.id = obj.id;
this.id = obj.id as OrganizationId;
this.name = obj.name;
this.status = obj.status;
this.type = obj.type;

View File

@@ -2,14 +2,14 @@
// @ts-strict-ignore
import { ListResponse } from "../../../models/response/list.response";
import Domain from "../../../platform/models/domain/domain-base";
import { PolicyId } from "../../../types/guid";
import { OrganizationId, PolicyId } from "../../../types/guid";
import { PolicyType } from "../../enums";
import { PolicyData } from "../data/policy.data";
import { PolicyResponse } from "../response/policy.response";
export class Policy extends Domain {
id: PolicyId;
organizationId: string;
organizationId: OrganizationId;
type: PolicyType;
data: any;
@@ -26,7 +26,7 @@ export class Policy extends Domain {
}
this.id = obj.id;
this.organizationId = obj.organizationId;
this.organizationId = obj.organizationId as OrganizationId;
this.type = obj.type;
this.data = obj.data;
this.enabled = obj.enabled;

View File

@@ -4,10 +4,12 @@
// eslint-disable-next-line no-restricted-imports
import { Collection as CollectionDomain, CollectionView } from "@bitwarden/admin-console/common";
import { CollectionId } from "../../types/guid";
import { CollectionExport } from "./collection.export";
export class CollectionWithIdExport extends CollectionExport {
id: string;
id: CollectionId;
static toView(req: CollectionWithIdExport, view = new CollectionView()) {
view.id = req.id;

View File

@@ -5,13 +5,14 @@
import { Collection as CollectionDomain, CollectionView } from "@bitwarden/admin-console/common";
import { EncString } from "../../key-management/crypto/models/enc-string";
import { emptyGuid, OrganizationId } from "../../types/guid";
import { safeGetString } from "./utils";
export class CollectionExport {
static template(): CollectionExport {
const req = new CollectionExport();
req.organizationId = "00000000-0000-0000-0000-000000000000";
req.organizationId = emptyGuid as OrganizationId;
req.name = "Collection name";
req.externalId = null;
return req;
@@ -35,7 +36,7 @@ export class CollectionExport {
return domain;
}
organizationId: string;
organizationId: OrganizationId;
name: string;
externalId: string;

View File

@@ -20,3 +20,8 @@ export type OrganizationIntegrationConfigurationId = Opaque<
string,
"OrganizationIntegrationConfigurationId"
>;
/**
* A string representation of an empty guid.
*/
export const emptyGuid = "00000000-0000-0000-0000-000000000000";

View File

@@ -9,6 +9,7 @@ import { normalizeExpiryYearFormat } from "@bitwarden/common/autofill/utils";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service";
import { CollectionId, OrganizationId } from "@bitwarden/common/types/guid";
import { FieldType, SecureNoteType, CipherType } from "@bitwarden/common/vault/enums";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { FieldView } from "@bitwarden/common/vault/models/view/field.view";
@@ -20,7 +21,7 @@ import { SecureNoteView } from "@bitwarden/common/vault/models/view/secure-note.
import { ImportResult } from "../models/import-result";
export abstract class BaseImporter {
organizationId: string = null;
organizationId: OrganizationId = null;
// FIXME: This should be replaced by injecting the log service.
protected logService: LogService = new ConsoleLogService(false);
@@ -279,7 +280,7 @@ export abstract class BaseImporter {
result.collections = result.folders.map((f) => {
const collection = new CollectionView();
collection.name = f.name;
collection.id = f.id ?? undefined; // folder id may be null, which is not suitable for collections.
collection.id = (f.id as CollectionId) ?? undefined; // folder id may be null, which is not suitable for collections.
return collection;
});
result.folderRelationships = [];

View File

@@ -1,4 +1,5 @@
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { OrganizationId } from "@bitwarden/common/types/guid";
import {
testData as TestData,
@@ -103,7 +104,7 @@ describe("Keeper CSV Importer", () => {
});
it("should create collections, with subcollections and relationships", async () => {
importer.organizationId = Utils.newGuid();
importer.organizationId = Utils.newGuid() as OrganizationId;
const result = await importer.parse(TestData);
expect(result != null).toBe(true);
@@ -126,7 +127,7 @@ describe("Keeper CSV Importer", () => {
});
it("should create collections tree, with child collections and relationships", async () => {
importer.organizationId = Utils.newGuid();
importer.organizationId = Utils.newGuid() as OrganizationId;
const result = await importer.parse(testDataMultiCollection);
expect(result != null).toBe(true);

View File

@@ -1,4 +1,5 @@
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { OrganizationId } from "@bitwarden/common/types/guid";
import { testData as TestData } from "../spec-data/keeper-json/testdata.json";
@@ -93,7 +94,7 @@ describe("Keeper Json Importer", () => {
});
it("should create collections if part of an organization", async () => {
importer.organizationId = Utils.newGuid();
importer.organizationId = Utils.newGuid() as OrganizationId;
const result = await importer.parse(testDataJson);
expect(result != null).toBe(true);

View File

@@ -1,4 +1,5 @@
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { OrganizationId } from "@bitwarden/common/types/guid";
import {
credentialsData,
@@ -79,7 +80,7 @@ describe("Netwrix Password Secure CSV Importer", () => {
});
it("should parse an item and create a collection when importing into an organization", async () => {
importer.organizationId = Utils.newGuid();
importer.organizationId = Utils.newGuid() as OrganizationId;
const result = await importer.parse(credentialsData);
expect(result).not.toBeNull();
@@ -93,7 +94,7 @@ describe("Netwrix Password Secure CSV Importer", () => {
});
it("should parse multiple collections", async () => {
importer.organizationId = Utils.newGuid();
importer.organizationId = Utils.newGuid() as OrganizationId;
const result = await importer.parse(credentialsDataWithFolders);
expect(result).not.toBeNull();

View File

@@ -1,4 +1,5 @@
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { OrganizationId } from "@bitwarden/common/types/guid";
import { SecureNoteType, CipherType, FieldType } from "@bitwarden/common/vault/enums";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { IdentityView } from "@bitwarden/common/vault/models/view/identity.view";
@@ -224,7 +225,7 @@ describe("NordPass CSV Importer", () => {
});
it("should parse an item and create a collection if organizationId is set", async () => {
importer.organizationId = Utils.newGuid();
importer.organizationId = Utils.newGuid() as OrganizationId;
const result = await importer.parse(secureNoteData);
expect(result).not.toBeNull();

View File

@@ -1,4 +1,5 @@
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { OrganizationId } from "@bitwarden/common/types/guid";
import { FieldType, SecureNoteType, CipherType } from "@bitwarden/common/vault/enums";
import { FieldView } from "@bitwarden/common/vault/models/view/field.view";
@@ -691,7 +692,7 @@ describe("1Password 1Pux Importer", () => {
it("should create collections if part of an organization", async () => {
const importer = new OnePassword1PuxImporter();
importer.organizationId = Utils.newGuid();
importer.organizationId = Utils.newGuid() as OrganizationId;
const result = await importer.parse(SanitizedExportJson);
expect(result != null).toBe(true);

View File

@@ -1,3 +1,4 @@
import { OrganizationId } from "@bitwarden/common/types/guid";
import { CipherType } from "@bitwarden/common/vault/enums";
import { ImportResult } from "../../models/import-result";
@@ -146,7 +147,7 @@ describe("PasswordXPCsvImporter", () => {
});
it("should convert folders to collections when importing into an organization", async () => {
importer.organizationId = "someOrg";
importer.organizationId = "someOrg" as OrganizationId;
const result: ImportResult = await importer.parse(withFolders);
expect(result.success).toBe(true);
expect(result.ciphers.length).toBe(5);
@@ -172,7 +173,7 @@ describe("PasswordXPCsvImporter", () => {
});
it("should convert multi-level folders to collections when importing into an organization", async () => {
importer.organizationId = "someOrg";
importer.organizationId = "someOrg" as OrganizationId;
const result: ImportResult = await importer.parse(withMultipleFolders);
expect(result.success).toBe(true);
expect(result.ciphers.length).toBe(5);

View File

@@ -1,6 +1,7 @@
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
// eslint-disable-next-line no-restricted-imports
import { CollectionView } from "@bitwarden/admin-console/common";
import { OrganizationId } from "@bitwarden/common/types/guid";
import { FieldType, SecureNoteType } from "@bitwarden/common/vault/enums";
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
import { CipherType } from "@bitwarden/sdk-internal";
@@ -485,7 +486,7 @@ describe("Password Depot 17 Xml Importer", () => {
it("should parse groups nodes into collections when importing into an organization", async () => {
const importer = new PasswordDepot17XmlImporter();
importer.organizationId = "someOrgId";
importer.organizationId = "someOrgId" as OrganizationId;
const collection = new CollectionView();
collection.name = "tempDB";
const actual = [collection];

View File

@@ -2,6 +2,7 @@ import { MockProxy } from "jest-mock-extended";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { OrganizationId } from "@bitwarden/common/types/guid";
import { FieldType, CipherType } from "@bitwarden/common/vault/enums";
import { testData } from "../spec-data/protonpass-json/protonpass.json";
@@ -90,7 +91,7 @@ describe("Protonpass Json Importer", () => {
it("should create collections if part of an organization", async () => {
const testDataJson = JSON.stringify(testData);
importer.organizationId = Utils.newGuid();
importer.organizationId = Utils.newGuid() as OrganizationId;
const result = await importer.parse(testDataJson);
expect(result != null).toBe(true);

View File

@@ -1,3 +1,4 @@
import { OrganizationId } from "@bitwarden/common/types/guid";
import { FieldType, CipherType } from "@bitwarden/common/vault/enums";
import { FieldView } from "@bitwarden/common/vault/models/view/field.view";
@@ -236,7 +237,7 @@ describe("PSONO JSON Importer", () => {
it("should create collections if part of an organization", async () => {
const importer = new PsonoJsonImporter();
importer.organizationId = "someOrg";
importer.organizationId = "someOrg" as OrganizationId;
const result = await importer.parse(FoldersTestDataJson);
expect(result != null).toBe(true);

View File

@@ -1,3 +1,4 @@
import { OrganizationId } from "@bitwarden/common/types/guid";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { LoginUriView } from "@bitwarden/common/vault/models/view/login-uri.view";
import { LoginView } from "@bitwarden/common/vault/models/view/login.view";
@@ -73,7 +74,7 @@ describe("Zoho Vault CSV Importer", () => {
it("should create collection and assign ciphers when importing into an organization", async () => {
const importer = new ZohoVaultCsvImporter();
importer.organizationId = "someOrgId";
importer.organizationId = "someOrgId" as OrganizationId;
const result = await importer.parse(samplezohovaultcsvdata);
expect(result != null).toBe(true);
expect(result.success).toBe(true);

View File

@@ -9,6 +9,7 @@ import { PinServiceAbstraction } from "@bitwarden/common/key-management/pin/pin.
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { MockSdkService } from "@bitwarden/common/platform/spec/mock-sdk.service";
import { CollectionId, OrganizationId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
@@ -67,7 +68,7 @@ describe("ImportService", () => {
describe("getImporterInstance", () => {
describe("Get bitPasswordProtected importer", () => {
let importer: Importer;
const organizationId = Utils.newGuid();
const organizationId = Utils.newGuid() as OrganizationId;
const password = Utils.newGuid();
const promptForPassword_callback = async () => {
return password;
@@ -98,7 +99,7 @@ describe("ImportService", () => {
});
describe("setImportTarget", () => {
const organizationId = Utils.newGuid();
const organizationId = Utils.newGuid() as OrganizationId;
let importResult: ImportResult;
@@ -145,19 +146,19 @@ describe("ImportService", () => {
});
const mockImportTargetCollection = new CollectionView();
mockImportTargetCollection.id = "myImportTarget";
mockImportTargetCollection.id = "myImportTarget" as CollectionId;
mockImportTargetCollection.name = "myImportTarget";
mockImportTargetCollection.organizationId = organizationId;
const mockCollection1 = new CollectionView();
mockCollection1.id = "collection1";
mockCollection1.id = "collection1" as CollectionId;
mockCollection1.name = "collection1";
mockCollection1.organizationId = organizationId;
const mockCollection2 = new CollectionView();
mockCollection1.id = "collection2";
mockCollection1.name = "collection2";
mockCollection1.organizationId = organizationId;
mockCollection2.id = "collection2" as CollectionId;
mockCollection2.name = "collection2";
mockCollection2.organizationId = organizationId;
it("passing importTarget adds it to collections", async () => {
await importService["setImportTarget"](

View File

@@ -19,6 +19,7 @@ import { ErrorResponse } from "@bitwarden/common/models/response/error.response"
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { OrganizationId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
import { CipherType, toCipherTypeName } from "@bitwarden/common/vault/enums";
@@ -130,7 +131,7 @@ export class ImportService implements ImportServiceAbstraction {
async import(
importer: Importer,
fileContents: string,
organizationId: string = null,
organizationId: OrganizationId = null,
selectedImportTarget: FolderView | CollectionView = null,
canAccessImportExport: boolean,
): Promise<ImportResult> {
@@ -204,7 +205,7 @@ export class ImportService implements ImportServiceAbstraction {
getImporter(
format: ImportType | "bitwardenpasswordprotected",
promptForPassword_callback: () => Promise<string>,
organizationId: string = null,
organizationId: OrganizationId = null,
): Importer {
if (promptForPassword_callback == null) {
return null;
@@ -393,7 +394,10 @@ export class ImportService implements ImportServiceAbstraction {
return await this.importApiService.postImportCiphers(request);
}
private async handleOrganizationalImport(importResult: ImportResult, organizationId: string) {
private async handleOrganizationalImport(
importResult: ImportResult,
organizationId: OrganizationId,
) {
const request = new ImportOrganizationCiphersRequest();
const activeUserId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),

View File

@@ -14,6 +14,7 @@ import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { CollectionId, OrganizationId } from "@bitwarden/common/types/guid";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { SelectComponent } from "@bitwarden/components";
@@ -33,9 +34,9 @@ const createMockCollection = (
canEdit = true,
): CollectionView => {
return {
id,
id: id as CollectionId,
name,
organizationId,
organizationId: organizationId as OrganizationId,
externalId: "",
readOnly,
hidePasswords: false,