1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-06 00:13:28 +00:00

[PM-21451] [Vault] [CLI] Changes to Enforce "Remove card item type policy" (#15187)

* Created new service to get restricted types for the CLI

* Created service for cli to get restricted types

* Utilized restriction service in commands

* Renamed function

* Refactored service and made it simpler to check when a cipher type is restricted or not

* Moved service to common so it can be utilized on the cli

* Refactored service to use restricted type service

* Removed userId passing from commands

* Exclude restrict types from export

* Added missing dependency

* Added missing dependency

* Added missing dependency

* Added service utils commit from desktop PR

* refactored to use reusable function

* updated reference

* updated reference

* Fixed merge conflicts

* Refactired services to use isCipherRestricted

* Refactored restricted item types service

* Updated services to use the reafctored item types service
This commit is contained in:
SmithThe4th
2025-06-23 12:04:56 -04:00
committed by GitHub
parent 2e8c0de719
commit e291e2df0a
24 changed files with 444 additions and 113 deletions

View File

@@ -25,6 +25,10 @@ import { AttachmentView } from "@bitwarden/common/vault/models/view/attachment.v
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
import { LoginView } from "@bitwarden/common/vault/models/view/login.view";
import {
RestrictedCipherType,
RestrictedItemTypesService,
} from "@bitwarden/common/vault/services/restricted-item-types.service";
import {
DEFAULT_KDF_CONFIG,
PBKDF2KdfConfig,
@@ -170,6 +174,8 @@ describe("VaultExportService", () => {
let kdfConfigService: MockProxy<KdfConfigService>;
let accountService: MockProxy<AccountService>;
let apiService: MockProxy<ApiService>;
let restrictedSubject: BehaviorSubject<RestrictedCipherType[]>;
let restrictedItemTypesService: Partial<RestrictedItemTypesService>;
let fetchMock: jest.Mock;
const userId = "" as UserId;
@@ -186,6 +192,12 @@ describe("VaultExportService", () => {
apiService = mock<ApiService>();
keyService.userKey$.mockReturnValue(new BehaviorSubject("mockOriginalUserKey" as any));
restrictedSubject = new BehaviorSubject<RestrictedCipherType[]>([]);
restrictedItemTypesService = {
restricted$: new BehaviorSubject<RestrictedCipherType[]>([]),
isCipherRestricted: jest.fn().mockReturnValue(false),
isCipherRestricted$: jest.fn().mockReturnValue(of(false)),
};
const accountInfo: AccountInfo = {
email: "",
@@ -223,6 +235,7 @@ describe("VaultExportService", () => {
kdfConfigService,
accountService,
apiService,
restrictedItemTypesService as RestrictedItemTypesService,
);
});
@@ -262,6 +275,46 @@ describe("VaultExportService", () => {
expectEqualCiphers(UserCipherDomains.slice(0, 2), exportedData.data);
});
it("does not unencrypted export restricted user items", async () => {
restrictedSubject.next([{ cipherType: CipherType.Card, allowViewOrgIds: [] }]);
const cardCipher = generateCipherView(false);
cardCipher.type = CipherType.Card;
(restrictedItemTypesService.isCipherRestricted as jest.Mock)
.mockReturnValueOnce(false)
.mockReturnValueOnce(true) // cardCipher - restricted
.mockReturnValueOnce(false);
const testCiphers = [UserCipherViews[0], cardCipher, UserCipherViews[1]];
cipherService.getAllDecrypted.mockResolvedValue(testCiphers);
const actual = await exportService.getExport("json");
expect(typeof actual.data).toBe("string");
const exportedData = actual as ExportedVaultAsString;
expectEqualCiphers([UserCipherViews[0], UserCipherViews[1]], exportedData.data);
});
it("does not encrypted export restricted user items", async () => {
restrictedSubject.next([{ cipherType: CipherType.Card, allowViewOrgIds: [] }]);
const cardCipher = generateCipherDomain(false);
cardCipher.type = CipherType.Card;
(restrictedItemTypesService.isCipherRestricted as jest.Mock)
.mockReturnValueOnce(false)
.mockReturnValueOnce(true) // cardCipher - restricted
.mockReturnValueOnce(false);
const testCiphers = [UserCipherDomains[0], cardCipher, UserCipherDomains[1]];
cipherService.getAll.mockResolvedValue(testCiphers);
const actual = await exportService.getExport("encrypted_json");
expect(typeof actual.data).toBe("string");
const exportedData = actual as ExportedVaultAsString;
expectEqualCiphers([UserCipherDomains[0], UserCipherDomains[1]], exportedData.data);
});
describe("zip export", () => {
it("contains data.json", async () => {
cipherService.getAllDecrypted.mockResolvedValue([]);

View File

@@ -20,6 +20,7 @@ import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
import { Folder } from "@bitwarden/common/vault/models/domain/folder";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
import { RestrictedItemTypesService } from "@bitwarden/common/vault/services/restricted-item-types.service";
import { KdfConfigService, KeyService } from "@bitwarden/key-management";
import {
@@ -50,6 +51,7 @@ export class IndividualVaultExportService
kdfConfigService: KdfConfigService,
private accountService: AccountService,
private apiService: ApiService,
private restrictedItemTypesService: RestrictedItemTypesService,
) {
super(pinService, encryptService, cryptoFunctionService, kdfConfigService);
}
@@ -169,9 +171,15 @@ export class IndividualVaultExportService
}),
);
const restrictions = await firstValueFrom(this.restrictedItemTypesService.restricted$);
promises.push(
this.cipherService.getAllDecrypted(activeUserId).then((ciphers) => {
decCiphers = ciphers.filter((f) => f.deletedDate == null);
decCiphers = ciphers.filter(
(f) =>
f.deletedDate == null &&
!this.restrictedItemTypesService.isCipherRestricted(f, restrictions),
);
}),
);
@@ -203,9 +211,15 @@ export class IndividualVaultExportService
}),
);
const restrictions = await firstValueFrom(this.restrictedItemTypesService.restricted$);
promises.push(
this.cipherService.getAll(activeUserId).then((c) => {
ciphers = c.filter((f) => f.deletedDate == null);
ciphers = c.filter(
(f) =>
f.deletedDate == null &&
!this.restrictedItemTypesService.isCipherRestricted(f, restrictions),
);
}),
);

View File

@@ -24,6 +24,7 @@ import { CipherType } from "@bitwarden/common/vault/enums";
import { CipherData } from "@bitwarden/common/vault/models/data/cipher.data";
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { RestrictedItemTypesService } from "@bitwarden/common/vault/services/restricted-item-types.service";
import { KdfConfigService, KeyService } from "@bitwarden/key-management";
import {
@@ -52,6 +53,7 @@ export class OrganizationVaultExportService
private collectionService: CollectionService,
kdfConfigService: KdfConfigService,
private accountService: AccountService,
private restrictedItemTypesService: RestrictedItemTypesService,
) {
super(pinService, encryptService, cryptoFunctionService, kdfConfigService);
}
@@ -133,6 +135,8 @@ export class OrganizationVaultExportService
const decCiphers: CipherView[] = [];
const promises = [];
const restrictions = await firstValueFrom(this.restrictedItemTypesService.restricted$);
promises.push(
this.apiService.getOrganizationExport(organizationId).then((exportData) => {
const exportPromises: any = [];
@@ -156,7 +160,11 @@ export class OrganizationVaultExportService
const cipher = new Cipher(new CipherData(c));
exportPromises.push(
this.cipherService.decrypt(cipher, activeUserId).then((decCipher) => {
decCiphers.push(decCipher);
if (
!this.restrictedItemTypesService.isCipherRestricted(decCipher, restrictions)
) {
decCiphers.push(decCipher);
}
}),
);
});
@@ -176,7 +184,7 @@ export class OrganizationVaultExportService
private async getOrganizationEncryptedExport(organizationId: string): Promise<string> {
const collections: Collection[] = [];
const ciphers: Cipher[] = [];
let ciphers: Cipher[] = [];
const promises = [];
promises.push(
@@ -190,15 +198,17 @@ export class OrganizationVaultExportService
}),
);
const restrictions = await firstValueFrom(this.restrictedItemTypesService.restricted$);
promises.push(
this.apiService.getCiphersOrganization(organizationId).then((c) => {
if (c != null && c.data != null && c.data.length > 0) {
c.data
ciphers = c.data
.filter((item) => item.deletedDate === null)
.forEach((item) => {
const cipher = new Cipher(new CipherData(item));
ciphers.push(cipher);
});
.map((item) => new Cipher(new CipherData(item)))
.filter(
(cipher) => !this.restrictedItemTypesService.isCipherRestricted(cipher, restrictions),
);
}
}),
);
@@ -231,11 +241,14 @@ export class OrganizationVaultExportService
);
await Promise.all(promises);
const restrictions = await firstValueFrom(this.restrictedItemTypesService.restricted$);
decCiphers = allDecCiphers.filter(
(f) =>
f.deletedDate == null &&
f.organizationId == organizationId &&
decCollections.some((dC) => f.collectionIds.some((cId) => dC.id === cId)),
decCollections.some((dC) => f.collectionIds.some((cId) => dC.id === cId)) &&
!this.restrictedItemTypesService.isCipherRestricted(f, restrictions),
);
if (format === "csv") {
@@ -267,11 +280,14 @@ export class OrganizationVaultExportService
await Promise.all(promises);
const restrictions = await firstValueFrom(this.restrictedItemTypesService.restricted$);
encCiphers = allCiphers.filter(
(f) =>
f.deletedDate == null &&
f.organizationId == organizationId &&
encCollections.some((eC) => f.collectionIds.some((cId) => eC.id === cId)),
encCollections.some((eC) => f.collectionIds.some((cId) => eC.id === cId)) &&
!this.restrictedItemTypesService.isCipherRestricted(f, restrictions),
);
return this.BuildEncryptedExport(organizationId, encCollections, encCiphers);

View File

@@ -19,6 +19,10 @@ import { Login } from "@bitwarden/common/vault/models/domain/login";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
import { LoginView } from "@bitwarden/common/vault/models/view/login.view";
import {
RestrictedCipherType,
RestrictedItemTypesService,
} from "@bitwarden/common/vault/services/restricted-item-types.service";
import {
DEFAULT_KDF_CONFIG,
PBKDF2KdfConfig,
@@ -159,6 +163,7 @@ describe("VaultExportService", () => {
let accountService: MockProxy<AccountService>;
let kdfConfigService: MockProxy<KdfConfigService>;
let apiService: MockProxy<ApiService>;
let restrictedItemTypesService: Partial<RestrictedItemTypesService>;
beforeEach(() => {
cryptoFunctionService = mock<CryptoFunctionService>();
@@ -186,6 +191,12 @@ describe("VaultExportService", () => {
const activeAccount = { id: userId, ...accountInfo };
accountService.activeAccount$ = new BehaviorSubject(activeAccount);
restrictedItemTypesService = {
restricted$: new BehaviorSubject<RestrictedCipherType[]>([]),
isCipherRestricted: jest.fn().mockReturnValue(false),
isCipherRestricted$: jest.fn().mockReturnValue(of(false)),
};
exportService = new IndividualVaultExportService(
folderService,
cipherService,
@@ -196,6 +207,7 @@ describe("VaultExportService", () => {
kdfConfigService,
accountService,
apiService,
restrictedItemTypesService as RestrictedItemTypesService,
);
});