mirror of
https://github.com/bitwarden/browser
synced 2025-12-06 00:13:28 +00:00
[PM-25918] Move required userId for export request up to component/command level (#14391)
* Require userId for KdfConfigService * cleanup KdfConfigService unit tests * Move required userId for export request up to component/command level * Fix service creation/dependency injection * Revert changes to kdf-config.service.spec cause by a bad rebase * Fix linting issue * Fix tests caused by bad rebase * Validate provided userId to equal the current active user * Create tests for vault-export.service Deleted old tests which since have been replaced with individual-vault-export.service.spec.ts --------- Co-authored-by: Thomas Avery <tavery@bitwarden.com> Co-authored-by: Daniel James Smith <djsmith85@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
a77fb354d8
commit
94764467e8
@@ -1097,7 +1097,6 @@ export default class MainBackground {
|
||||
this.encryptService,
|
||||
this.cryptoFunctionService,
|
||||
this.kdfConfigService,
|
||||
this.accountService,
|
||||
this.apiService,
|
||||
this.restrictedItemTypesService,
|
||||
);
|
||||
@@ -1113,13 +1112,13 @@ export default class MainBackground {
|
||||
this.cryptoFunctionService,
|
||||
this.collectionService,
|
||||
this.kdfConfigService,
|
||||
this.accountService,
|
||||
this.restrictedItemTypesService,
|
||||
);
|
||||
|
||||
this.exportService = new VaultExportService(
|
||||
this.individualVaultExportService,
|
||||
this.organizationVaultExportService,
|
||||
this.accountService,
|
||||
);
|
||||
|
||||
this.browserInitialInstallService = new BrowserInitialInstallService(this.stateProvider);
|
||||
|
||||
@@ -841,7 +841,6 @@ export class ServiceContainer {
|
||||
this.encryptService,
|
||||
this.cryptoFunctionService,
|
||||
this.kdfConfigService,
|
||||
this.accountService,
|
||||
this.apiService,
|
||||
this.restrictedItemTypesService,
|
||||
);
|
||||
@@ -857,13 +856,13 @@ export class ServiceContainer {
|
||||
this.cryptoFunctionService,
|
||||
this.collectionService,
|
||||
this.kdfConfigService,
|
||||
this.accountService,
|
||||
this.restrictedItemTypesService,
|
||||
);
|
||||
|
||||
this.exportService = new VaultExportService(
|
||||
this.individualExportService,
|
||||
this.organizationExportService,
|
||||
this.accountService,
|
||||
);
|
||||
|
||||
this.userAutoUnlockKeyService = new UserAutoUnlockKeyService(this.keyService);
|
||||
|
||||
@@ -70,10 +70,13 @@ export class ExportCommand {
|
||||
password = await this.promptPassword(password);
|
||||
}
|
||||
|
||||
const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||
|
||||
exportContent =
|
||||
options.organizationid == null
|
||||
? await this.exportService.getExport(format, password)
|
||||
? await this.exportService.getExport(userId, format, password)
|
||||
: await this.exportService.getOrganizationExport(
|
||||
userId,
|
||||
options.organizationid,
|
||||
format,
|
||||
password,
|
||||
|
||||
@@ -884,7 +884,6 @@ const safeProviders: SafeProvider[] = [
|
||||
EncryptService,
|
||||
CryptoFunctionServiceAbstraction,
|
||||
KdfConfigService,
|
||||
AccountServiceAbstraction,
|
||||
ApiServiceAbstraction,
|
||||
RestrictedItemTypesService,
|
||||
],
|
||||
@@ -906,14 +905,17 @@ const safeProviders: SafeProvider[] = [
|
||||
CryptoFunctionServiceAbstraction,
|
||||
CollectionService,
|
||||
KdfConfigService,
|
||||
AccountServiceAbstraction,
|
||||
RestrictedItemTypesService,
|
||||
],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: VaultExportServiceAbstraction,
|
||||
useClass: VaultExportService,
|
||||
deps: [IndividualVaultExportServiceAbstraction, OrganizationVaultExportServiceAbstraction],
|
||||
deps: [
|
||||
IndividualVaultExportServiceAbstraction,
|
||||
OrganizationVaultExportServiceAbstraction,
|
||||
AccountServiceAbstraction,
|
||||
],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: SearchServiceAbstraction,
|
||||
|
||||
@@ -7,7 +7,10 @@ module.exports = {
|
||||
testMatch: ["**/+(*.)+(spec).+(ts)"],
|
||||
preset: "ts-jest",
|
||||
testEnvironment: "jsdom",
|
||||
moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, {
|
||||
prefix: "<rootDir>/../../../../../",
|
||||
}),
|
||||
moduleNameMapper: pathsToModuleNameMapper(
|
||||
{ "@bitwarden/common/spec": ["libs/common/spec"], ...(compilerOptions?.paths ?? {}) },
|
||||
{
|
||||
prefix: "<rootDir>/../../../../../",
|
||||
},
|
||||
),
|
||||
};
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
|
||||
import { ExportedVault } from "../types";
|
||||
|
||||
import { ExportFormat } from "./vault-export.service.abstraction";
|
||||
|
||||
export abstract class IndividualVaultExportServiceAbstraction {
|
||||
abstract getExport: (format: ExportFormat) => Promise<ExportedVault>;
|
||||
abstract getPasswordProtectedExport: (password: string) => Promise<ExportedVault>;
|
||||
abstract getExport: (userId: UserId, format: ExportFormat) => Promise<ExportedVault>;
|
||||
abstract getPasswordProtectedExport: (userId: UserId, password: string) => Promise<ExportedVault>;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ import * as JSZip from "jszip";
|
||||
import { BehaviorSubject, of } from "rxjs";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { AccountInfo, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import {
|
||||
@@ -175,7 +174,6 @@ describe("VaultExportService", () => {
|
||||
let keyService: MockProxy<KeyService>;
|
||||
let encryptService: MockProxy<EncryptService>;
|
||||
let kdfConfigService: MockProxy<KdfConfigService>;
|
||||
let accountService: MockProxy<AccountService>;
|
||||
let apiService: MockProxy<ApiService>;
|
||||
let restrictedSubject: BehaviorSubject<RestrictedCipherType[]>;
|
||||
let restrictedItemTypesService: Partial<RestrictedItemTypesService>;
|
||||
@@ -191,7 +189,6 @@ describe("VaultExportService", () => {
|
||||
keyService = mock<KeyService>();
|
||||
encryptService = mock<EncryptService>();
|
||||
kdfConfigService = mock<KdfConfigService>();
|
||||
accountService = mock<AccountService>();
|
||||
apiService = mock<ApiService>();
|
||||
|
||||
keyService.userKey$.mockReturnValue(new BehaviorSubject("mockOriginalUserKey" as any));
|
||||
@@ -202,14 +199,6 @@ describe("VaultExportService", () => {
|
||||
isCipherRestricted$: jest.fn().mockReturnValue(of(false)),
|
||||
};
|
||||
|
||||
const accountInfo: AccountInfo = {
|
||||
email: "",
|
||||
emailVerified: true,
|
||||
name: undefined,
|
||||
};
|
||||
const activeAccount = { id: userId, ...accountInfo };
|
||||
accountService.activeAccount$ = new BehaviorSubject(activeAccount);
|
||||
|
||||
fetchMock = jest.fn().mockResolvedValue({});
|
||||
global.fetch = fetchMock;
|
||||
|
||||
@@ -236,7 +225,6 @@ describe("VaultExportService", () => {
|
||||
encryptService,
|
||||
cryptoFunctionService,
|
||||
kdfConfigService,
|
||||
accountService,
|
||||
apiService,
|
||||
restrictedItemTypesService as RestrictedItemTypesService,
|
||||
);
|
||||
@@ -245,7 +233,7 @@ describe("VaultExportService", () => {
|
||||
it("exports unencrypted user ciphers", async () => {
|
||||
cipherService.getAllDecrypted.mockResolvedValue(UserCipherViews.slice(0, 1));
|
||||
|
||||
const actual = await exportService.getExport("json");
|
||||
const actual = await exportService.getExport(userId, "json");
|
||||
expect(typeof actual.data).toBe("string");
|
||||
const exportedData = actual as ExportedVaultAsString;
|
||||
expectEqualCiphers(UserCipherViews.slice(0, 1), exportedData.data);
|
||||
@@ -254,7 +242,7 @@ describe("VaultExportService", () => {
|
||||
it("exports encrypted json user ciphers", async () => {
|
||||
cipherService.getAll.mockResolvedValue(UserCipherDomains.slice(0, 1));
|
||||
|
||||
const actual = await exportService.getExport("encrypted_json");
|
||||
const actual = await exportService.getExport(userId, "encrypted_json");
|
||||
expect(typeof actual.data).toBe("string");
|
||||
const exportedData = actual as ExportedVaultAsString;
|
||||
expectEqualCiphers(UserCipherDomains.slice(0, 1), exportedData.data);
|
||||
@@ -263,7 +251,7 @@ describe("VaultExportService", () => {
|
||||
it("does not unencrypted export trashed user items", async () => {
|
||||
cipherService.getAllDecrypted.mockResolvedValue(UserCipherViews);
|
||||
|
||||
const actual = await exportService.getExport("json");
|
||||
const actual = await exportService.getExport(userId, "json");
|
||||
expect(typeof actual.data).toBe("string");
|
||||
const exportedData = actual as ExportedVaultAsString;
|
||||
expectEqualCiphers(UserCipherViews.slice(0, 2), exportedData.data);
|
||||
@@ -272,7 +260,7 @@ describe("VaultExportService", () => {
|
||||
it("does not encrypted export trashed user items", async () => {
|
||||
cipherService.getAll.mockResolvedValue(UserCipherDomains);
|
||||
|
||||
const actual = await exportService.getExport("encrypted_json");
|
||||
const actual = await exportService.getExport(userId, "encrypted_json");
|
||||
expect(typeof actual.data).toBe("string");
|
||||
const exportedData = actual as ExportedVaultAsString;
|
||||
expectEqualCiphers(UserCipherDomains.slice(0, 2), exportedData.data);
|
||||
@@ -291,7 +279,7 @@ describe("VaultExportService", () => {
|
||||
const testCiphers = [UserCipherViews[0], cardCipher, UserCipherViews[1]];
|
||||
cipherService.getAllDecrypted.mockResolvedValue(testCiphers);
|
||||
|
||||
const actual = await exportService.getExport("json");
|
||||
const actual = await exportService.getExport(userId, "json");
|
||||
expect(typeof actual.data).toBe("string");
|
||||
const exportedData = actual as ExportedVaultAsString;
|
||||
|
||||
@@ -311,7 +299,7 @@ describe("VaultExportService", () => {
|
||||
const testCiphers = [UserCipherDomains[0], cardCipher, UserCipherDomains[1]];
|
||||
cipherService.getAll.mockResolvedValue(testCiphers);
|
||||
|
||||
const actual = await exportService.getExport("encrypted_json");
|
||||
const actual = await exportService.getExport(userId, "encrypted_json");
|
||||
expect(typeof actual.data).toBe("string");
|
||||
const exportedData = actual as ExportedVaultAsString;
|
||||
|
||||
@@ -323,7 +311,7 @@ describe("VaultExportService", () => {
|
||||
cipherService.getAllDecrypted.mockResolvedValue([]);
|
||||
folderService.getAllDecryptedFromState.mockResolvedValue([]);
|
||||
|
||||
const exportedVault = await exportService.getExport("zip");
|
||||
const exportedVault = await exportService.getExport(userId, "zip");
|
||||
|
||||
expect(exportedVault.type).toBe("application/zip");
|
||||
const exportZip = exportedVault as ExportedVaultAsBlob;
|
||||
@@ -348,7 +336,7 @@ describe("VaultExportService", () => {
|
||||
cipherService.getAllDecrypted.mockResolvedValue([cipherView, orgCipherView]);
|
||||
folderService.getAllDecryptedFromState.mockResolvedValue([]);
|
||||
|
||||
const exportedVault = await exportService.getExport("zip");
|
||||
const exportedVault = await exportService.getExport(userId, "zip");
|
||||
|
||||
const zip = await JSZip.loadAsync(exportedVault.data);
|
||||
const data = await zip.file("data.json")?.async("string");
|
||||
@@ -380,7 +368,7 @@ describe("VaultExportService", () => {
|
||||
global.Request = jest.fn(() => {}) as any;
|
||||
|
||||
await expect(async () => {
|
||||
await exportService.getExport("zip");
|
||||
await exportService.getExport(userId, "zip");
|
||||
}).rejects.toThrow("Error downloading attachment");
|
||||
},
|
||||
);
|
||||
@@ -408,7 +396,7 @@ describe("VaultExportService", () => {
|
||||
global.Request = jest.fn(() => {}) as any;
|
||||
|
||||
await expect(async () => {
|
||||
await exportService.getExport("zip");
|
||||
await exportService.getExport(userId, "zip");
|
||||
}).rejects.toThrow("Error decrypting attachment");
|
||||
});
|
||||
|
||||
@@ -434,7 +422,7 @@ describe("VaultExportService", () => {
|
||||
) as any;
|
||||
global.Request = jest.fn(() => {}) as any;
|
||||
|
||||
const exportedVault = await exportService.getExport("zip");
|
||||
const exportedVault = await exportService.getExport(userId, "zip");
|
||||
|
||||
expect(exportedVault.type).toBe("application/zip");
|
||||
const exportZip = exportedVault as ExportedVaultAsBlob;
|
||||
@@ -464,7 +452,7 @@ describe("VaultExportService", () => {
|
||||
jest.spyOn(Utils, "fromBufferToB64").mockReturnValue(salt);
|
||||
cipherService.getAllDecrypted.mockResolvedValue(UserCipherViews.slice(0, 1));
|
||||
|
||||
exportedVault = await exportService.getPasswordProtectedExport(password);
|
||||
exportedVault = await exportService.getPasswordProtectedExport(userId, password);
|
||||
exportString = exportedVault.data;
|
||||
exportObject = JSON.parse(exportString);
|
||||
});
|
||||
@@ -491,7 +479,7 @@ describe("VaultExportService", () => {
|
||||
|
||||
it("has a mac property", async () => {
|
||||
encryptService.encryptString.mockResolvedValue(mac);
|
||||
exportedVault = await exportService.getPasswordProtectedExport(password);
|
||||
exportedVault = await exportService.getPasswordProtectedExport(userId, password);
|
||||
exportString = exportedVault.data;
|
||||
exportObject = JSON.parse(exportString);
|
||||
|
||||
@@ -500,7 +488,7 @@ describe("VaultExportService", () => {
|
||||
|
||||
it("has data property", async () => {
|
||||
encryptService.encryptString.mockResolvedValue(data);
|
||||
exportedVault = await exportService.getPasswordProtectedExport(password);
|
||||
exportedVault = await exportService.getPasswordProtectedExport(userId, password);
|
||||
exportString = exportedVault.data;
|
||||
exportObject = JSON.parse(exportString);
|
||||
|
||||
@@ -508,7 +496,7 @@ describe("VaultExportService", () => {
|
||||
});
|
||||
|
||||
it("encrypts the data property", async () => {
|
||||
const unEncryptedExportVault = await exportService.getExport();
|
||||
const unEncryptedExportVault = await exportService.getExport(userId);
|
||||
|
||||
const unEncryptedExportString = unEncryptedExportVault.data;
|
||||
expect(exportObject.data).not.toEqual(unEncryptedExportString);
|
||||
@@ -520,7 +508,7 @@ describe("VaultExportService", () => {
|
||||
cipherService.getAllDecrypted.mockResolvedValue(UserCipherViews.slice(0, 1));
|
||||
folderService.folderViews$.mockReturnValue(of(UserFolderViews));
|
||||
|
||||
const actual = await exportService.getExport("json");
|
||||
const actual = await exportService.getExport(userId, "json");
|
||||
|
||||
expect(typeof actual.data).toBe("string");
|
||||
const exportedData = actual as ExportedVaultAsString;
|
||||
@@ -531,7 +519,7 @@ describe("VaultExportService", () => {
|
||||
cipherService.getAll.mockResolvedValue(UserCipherDomains.slice(0, 1));
|
||||
folderService.folders$.mockReturnValue(of(UserFolders));
|
||||
|
||||
const actual = await exportService.getExport("encrypted_json");
|
||||
const actual = await exportService.getExport(userId, "encrypted_json");
|
||||
|
||||
expect(typeof actual.data).toBe("string");
|
||||
const exportedData = actual as ExportedVaultAsString;
|
||||
|
||||
@@ -5,8 +5,6 @@ import * as papa from "papaparse";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { PinServiceAbstraction } from "@bitwarden/common/key-management/pin/pin.service.abstraction";
|
||||
@@ -49,7 +47,6 @@ export class IndividualVaultExportService
|
||||
encryptService: EncryptService,
|
||||
cryptoFunctionService: CryptoFunctionService,
|
||||
kdfConfigService: KdfConfigService,
|
||||
private accountService: AccountService,
|
||||
private apiService: ApiService,
|
||||
private restrictedItemTypesService: RestrictedItemTypesService,
|
||||
) {
|
||||
@@ -57,10 +54,10 @@ export class IndividualVaultExportService
|
||||
}
|
||||
|
||||
/** Creates an export of an individual vault (My Vault). Based on the provided format it will either be unencrypted, encrypted or password protected and in case zip is selected will include attachments
|
||||
* @param userId The userId of the account requesting the export
|
||||
* @param format The format of the export
|
||||
*/
|
||||
async getExport(format: ExportFormat = "csv"): Promise<ExportedVault> {
|
||||
const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||
async getExport(userId: UserId, format: ExportFormat = "csv"): Promise<ExportedVault> {
|
||||
if (format === "encrypted_json") {
|
||||
return this.getEncryptedExport(userId);
|
||||
} else if (format === "zip") {
|
||||
@@ -70,12 +67,15 @@ export class IndividualVaultExportService
|
||||
}
|
||||
|
||||
/** Creates a password protected export of an individual vault (My Vault) as a JSON file
|
||||
* @param userId The userId of the account requesting the export
|
||||
* @param password The password to encrypt the export with
|
||||
* @returns A password-protected encrypted individual vault export
|
||||
*/
|
||||
async getPasswordProtectedExport(password: string): Promise<ExportedVaultAsString> {
|
||||
const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||
const exportVault = await this.getExport("json");
|
||||
async getPasswordProtectedExport(
|
||||
userId: UserId,
|
||||
password: string,
|
||||
): Promise<ExportedVaultAsString> {
|
||||
const exportVault = await this.getExport(userId, "json");
|
||||
|
||||
if (exportVault.type !== "text/plain") {
|
||||
throw new Error("Unexpected export type");
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { OrganizationId } from "@bitwarden/common/types/guid";
|
||||
import { UserId, OrganizationId } from "@bitwarden/common/types/guid";
|
||||
|
||||
import { ExportedVaultAsString } from "../types";
|
||||
|
||||
@@ -6,11 +6,13 @@ import { ExportFormat } from "./vault-export.service.abstraction";
|
||||
|
||||
export abstract class OrganizationVaultExportServiceAbstraction {
|
||||
abstract getPasswordProtectedExport: (
|
||||
userId: UserId,
|
||||
organizationId: OrganizationId,
|
||||
password: string,
|
||||
onlyManagedCollections: boolean,
|
||||
) => Promise<ExportedVaultAsString>;
|
||||
abstract getOrganizationExport: (
|
||||
userId: UserId,
|
||||
organizationId: OrganizationId,
|
||||
format: ExportFormat,
|
||||
onlyManagedCollections: boolean,
|
||||
|
||||
@@ -10,8 +10,6 @@ import {
|
||||
CollectionDetailsResponse,
|
||||
CollectionView,
|
||||
} from "@bitwarden/admin-console/common";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { PinServiceAbstraction } from "@bitwarden/common/key-management/pin/pin.service.abstraction";
|
||||
@@ -52,25 +50,26 @@ export class OrganizationVaultExportService
|
||||
cryptoFunctionService: CryptoFunctionService,
|
||||
private collectionService: CollectionService,
|
||||
kdfConfigService: KdfConfigService,
|
||||
private accountService: AccountService,
|
||||
private restrictedItemTypesService: RestrictedItemTypesService,
|
||||
) {
|
||||
super(pinService, encryptService, cryptoFunctionService, kdfConfigService);
|
||||
}
|
||||
|
||||
/** Creates a password protected export of an organizational vault.
|
||||
* @param userId The userId of the account requesting the export
|
||||
* @param organizationId The organization id
|
||||
* @param password The password to protect the export
|
||||
* @param onlyManagedCollections If true only managed collections will be exported
|
||||
* @returns The exported vault
|
||||
*/
|
||||
async getPasswordProtectedExport(
|
||||
userId: UserId,
|
||||
organizationId: OrganizationId,
|
||||
password: string,
|
||||
onlyManagedCollections: boolean,
|
||||
): Promise<ExportedVaultAsString> {
|
||||
const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||
const exportVault = await this.getOrganizationExport(
|
||||
userId,
|
||||
organizationId,
|
||||
"json",
|
||||
onlyManagedCollections,
|
||||
@@ -84,6 +83,7 @@ export class OrganizationVaultExportService
|
||||
}
|
||||
|
||||
/** Creates an export of an organizational vault. Based on the provided format it will either be unencrypted, encrypted
|
||||
* @param userId The userId of the account requesting the export
|
||||
* @param organizationId The organization id
|
||||
* @param format The format of the export
|
||||
* @param onlyManagedCollections If true only managed collections will be exported
|
||||
@@ -94,6 +94,7 @@ export class OrganizationVaultExportService
|
||||
* @throws Error if the organization policies prevent the export
|
||||
*/
|
||||
async getOrganizationExport(
|
||||
userId: UserId,
|
||||
organizationId: OrganizationId,
|
||||
format: ExportFormat = "csv",
|
||||
onlyManagedCollections: boolean,
|
||||
@@ -105,7 +106,6 @@ export class OrganizationVaultExportService
|
||||
if (format === "zip") {
|
||||
throw new Error("Zip export not supported for organization");
|
||||
}
|
||||
const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||
|
||||
if (format === "encrypted_json") {
|
||||
return {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { OrganizationId } from "@bitwarden/common/types/guid";
|
||||
import { UserId, OrganizationId } from "@bitwarden/common/types/guid";
|
||||
|
||||
import { ExportedVault } from "../types";
|
||||
|
||||
@@ -6,8 +6,13 @@ export const EXPORT_FORMATS = ["csv", "json", "encrypted_json", "zip"] as const;
|
||||
export type ExportFormat = (typeof EXPORT_FORMATS)[number];
|
||||
|
||||
export abstract class VaultExportServiceAbstraction {
|
||||
abstract getExport: (format: ExportFormat, password: string) => Promise<ExportedVault>;
|
||||
abstract getExport: (
|
||||
userId: UserId,
|
||||
format: ExportFormat,
|
||||
password: string,
|
||||
) => Promise<ExportedVault>;
|
||||
abstract getOrganizationExport: (
|
||||
userId: UserId,
|
||||
organizationId: OrganizationId,
|
||||
format: ExportFormat,
|
||||
password: string,
|
||||
|
||||
@@ -1,355 +1,153 @@
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
import { BehaviorSubject, of } from "rxjs";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { AccountInfo, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import {
|
||||
EncryptedString,
|
||||
EncString,
|
||||
} from "@bitwarden/common/key-management/crypto/models/enc-string";
|
||||
import { PinServiceAbstraction } from "@bitwarden/common/key-management/pin/pin.service.abstraction";
|
||||
import { CipherWithIdExport } from "@bitwarden/common/models/export/cipher-with-ids.export";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { UserId } 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 } from "@bitwarden/common/vault/enums";
|
||||
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
|
||||
import { Folder } from "@bitwarden/common/vault/models/domain/folder";
|
||||
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,
|
||||
KdfConfigService,
|
||||
KeyService,
|
||||
KdfType,
|
||||
} from "@bitwarden/key-management";
|
||||
import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec";
|
||||
import { OrganizationId, UserId } from "@bitwarden/common/types/guid";
|
||||
|
||||
import { BuildTestObject, GetUniqueString } from "../../../../../../common/spec";
|
||||
import { ExportedVault, ExportedVaultAsString } from "../types";
|
||||
|
||||
import { IndividualVaultExportService } from "./individual-vault-export.service";
|
||||
|
||||
const UserCipherViews = [
|
||||
generateCipherView(false),
|
||||
generateCipherView(false),
|
||||
generateCipherView(true),
|
||||
];
|
||||
|
||||
const UserCipherDomains = [
|
||||
generateCipherDomain(false),
|
||||
generateCipherDomain(false),
|
||||
generateCipherDomain(true),
|
||||
];
|
||||
|
||||
const UserFolderViews = [generateFolderView(), generateFolderView()];
|
||||
|
||||
const UserFolders = [generateFolder(), generateFolder()];
|
||||
|
||||
function generateCipherView(deleted: boolean) {
|
||||
return BuildTestObject(
|
||||
{
|
||||
id: GetUniqueString("id"),
|
||||
notes: GetUniqueString("notes"),
|
||||
type: CipherType.Login,
|
||||
login: BuildTestObject<LoginView>(
|
||||
{
|
||||
username: GetUniqueString("username"),
|
||||
password: GetUniqueString("password"),
|
||||
},
|
||||
LoginView,
|
||||
),
|
||||
collectionIds: null,
|
||||
deletedDate: deleted ? new Date() : null,
|
||||
},
|
||||
CipherView,
|
||||
);
|
||||
}
|
||||
|
||||
function generateCipherDomain(deleted: boolean) {
|
||||
return BuildTestObject(
|
||||
{
|
||||
id: GetUniqueString("id"),
|
||||
notes: new EncString(GetUniqueString("notes")),
|
||||
type: CipherType.Login,
|
||||
login: BuildTestObject<Login>(
|
||||
{
|
||||
username: new EncString(GetUniqueString("username")),
|
||||
password: new EncString(GetUniqueString("password")),
|
||||
},
|
||||
Login,
|
||||
),
|
||||
collectionIds: null,
|
||||
deletedDate: deleted ? new Date() : null,
|
||||
},
|
||||
Cipher,
|
||||
);
|
||||
}
|
||||
|
||||
function generateFolderView() {
|
||||
return BuildTestObject(
|
||||
{
|
||||
id: GetUniqueString("id"),
|
||||
name: GetUniqueString("name"),
|
||||
revisionDate: new Date(),
|
||||
},
|
||||
FolderView,
|
||||
);
|
||||
}
|
||||
|
||||
function generateFolder() {
|
||||
const actual = Folder.fromJSON({
|
||||
revisionDate: new Date("2022-08-04T01:06:40.441Z").toISOString(),
|
||||
name: "name" as EncryptedString,
|
||||
id: "id",
|
||||
});
|
||||
return actual;
|
||||
}
|
||||
|
||||
function expectEqualCiphers(ciphers: CipherView[] | Cipher[], jsonResult: string) {
|
||||
const actual = JSON.stringify(JSON.parse(jsonResult).items);
|
||||
const items: CipherWithIdExport[] = [];
|
||||
ciphers.forEach((c: CipherView | Cipher) => {
|
||||
const item = new CipherWithIdExport();
|
||||
item.build(c);
|
||||
items.push(item);
|
||||
});
|
||||
|
||||
expect(actual).toEqual(JSON.stringify(items));
|
||||
}
|
||||
|
||||
function expectEqualFolderViews(folderViews: FolderView[] | Folder[], jsonResult: string) {
|
||||
const actual = JSON.stringify(JSON.parse(jsonResult).folders);
|
||||
const folders: FolderResponse[] = [];
|
||||
folderViews.forEach((c) => {
|
||||
const folder = new FolderResponse();
|
||||
folder.id = c.id;
|
||||
folder.name = c.name.toString();
|
||||
folders.push(folder);
|
||||
});
|
||||
|
||||
expect(actual.length).toBeGreaterThan(0);
|
||||
expect(actual).toEqual(JSON.stringify(folders));
|
||||
}
|
||||
|
||||
function expectEqualFolders(folders: Folder[], jsonResult: string) {
|
||||
const actual = JSON.stringify(JSON.parse(jsonResult).folders);
|
||||
const items: Folder[] = [];
|
||||
folders.forEach((c) => {
|
||||
const item = new Folder();
|
||||
item.id = c.id;
|
||||
item.name = c.name;
|
||||
items.push(item);
|
||||
});
|
||||
|
||||
expect(actual.length).toBeGreaterThan(0);
|
||||
expect(actual).toEqual(JSON.stringify(items));
|
||||
}
|
||||
import { IndividualVaultExportServiceAbstraction } from "./individual-vault-export.service.abstraction";
|
||||
import { OrganizationVaultExportServiceAbstraction } from "./org-vault-export.service.abstraction";
|
||||
import { VaultExportService } from "./vault-export.service";
|
||||
|
||||
/** Tests the vault export service which handles exporting both individual and organizational vaults */
|
||||
describe("VaultExportService", () => {
|
||||
let exportService: IndividualVaultExportService;
|
||||
let cryptoFunctionService: MockProxy<CryptoFunctionService>;
|
||||
let cipherService: MockProxy<CipherService>;
|
||||
let pinService: MockProxy<PinServiceAbstraction>;
|
||||
let folderService: MockProxy<FolderService>;
|
||||
let keyService: MockProxy<KeyService>;
|
||||
let encryptService: MockProxy<EncryptService>;
|
||||
let accountService: MockProxy<AccountService>;
|
||||
let kdfConfigService: MockProxy<KdfConfigService>;
|
||||
let apiService: MockProxy<ApiService>;
|
||||
let restrictedItemTypesService: Partial<RestrictedItemTypesService>;
|
||||
let service: VaultExportService;
|
||||
let individualVaultExportService: MockProxy<IndividualVaultExportServiceAbstraction>;
|
||||
let organizationVaultExportService: MockProxy<OrganizationVaultExportServiceAbstraction>;
|
||||
let accountService: FakeAccountService;
|
||||
const mockUserId = Utils.newGuid() as UserId;
|
||||
const mockOrganizationId = Utils.newGuid() as OrganizationId;
|
||||
|
||||
beforeEach(() => {
|
||||
cryptoFunctionService = mock<CryptoFunctionService>();
|
||||
cipherService = mock<CipherService>();
|
||||
pinService = mock<PinServiceAbstraction>();
|
||||
folderService = mock<FolderService>();
|
||||
keyService = mock<KeyService>();
|
||||
encryptService = mock<EncryptService>();
|
||||
accountService = mock<AccountService>();
|
||||
apiService = mock<ApiService>();
|
||||
individualVaultExportService = mock<IndividualVaultExportServiceAbstraction>();
|
||||
organizationVaultExportService = mock<OrganizationVaultExportServiceAbstraction>();
|
||||
accountService = mockAccountServiceWith(mockUserId);
|
||||
|
||||
kdfConfigService = mock<KdfConfigService>();
|
||||
|
||||
folderService.folderViews$.mockReturnValue(of(UserFolderViews));
|
||||
folderService.folders$.mockReturnValue(of(UserFolders));
|
||||
kdfConfigService.getKdfConfig.mockResolvedValue(DEFAULT_KDF_CONFIG);
|
||||
encryptService.encryptString.mockResolvedValue(new EncString("encrypted"));
|
||||
keyService.userKey$.mockReturnValue(new BehaviorSubject("mockOriginalUserKey" as any));
|
||||
const userId = "" as UserId;
|
||||
const accountInfo: AccountInfo = {
|
||||
email: "",
|
||||
emailVerified: true,
|
||||
name: undefined,
|
||||
};
|
||||
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,
|
||||
pinService,
|
||||
keyService,
|
||||
encryptService,
|
||||
cryptoFunctionService,
|
||||
kdfConfigService,
|
||||
service = new VaultExportService(
|
||||
individualVaultExportService,
|
||||
organizationVaultExportService,
|
||||
accountService,
|
||||
apiService,
|
||||
restrictedItemTypesService as RestrictedItemTypesService,
|
||||
);
|
||||
});
|
||||
|
||||
it("exports unencrypted user ciphers", async () => {
|
||||
cipherService.getAllDecrypted.mockResolvedValue(UserCipherViews.slice(0, 1));
|
||||
describe("getExport", () => {
|
||||
it("calls checkForImpersonation with userId", async () => {
|
||||
const spy = jest.spyOn(service as any, "checkForImpersonation");
|
||||
|
||||
const actual = await exportService.getExport("json");
|
||||
expect(typeof actual.data).toBe("string");
|
||||
const exportedData = actual as ExportedVaultAsString;
|
||||
expectEqualCiphers(UserCipherViews.slice(0, 1), exportedData.data);
|
||||
});
|
||||
await service.getExport(mockUserId, "json", "");
|
||||
expect(spy).toHaveBeenCalledWith(mockUserId);
|
||||
});
|
||||
|
||||
it("exports encrypted json user ciphers", async () => {
|
||||
cipherService.getAll.mockResolvedValue(UserCipherDomains.slice(0, 1));
|
||||
it("validates the given userId matches the current authenticated user", async () => {
|
||||
const anotherUserId = "another-user-id" as UserId;
|
||||
|
||||
const actual = await exportService.getExport("encrypted_json");
|
||||
expect(typeof actual.data).toBe("string");
|
||||
const exportedData = actual as ExportedVaultAsString;
|
||||
expectEqualCiphers(UserCipherDomains.slice(0, 1), exportedData.data);
|
||||
});
|
||||
await expect(service.getExport(anotherUserId, "json", "")).rejects.toThrow(
|
||||
"UserId does not match the currently authenticated user",
|
||||
);
|
||||
|
||||
it("does not unencrypted export trashed user items", async () => {
|
||||
cipherService.getAllDecrypted.mockResolvedValue(UserCipherViews);
|
||||
expect(individualVaultExportService.getExport).not.toHaveBeenCalledWith(mockUserId, "json");
|
||||
});
|
||||
|
||||
const actual = await exportService.getExport("json");
|
||||
expect(typeof actual.data).toBe("string");
|
||||
const exportedData = actual as ExportedVaultAsString;
|
||||
expectEqualCiphers(UserCipherViews.slice(0, 2), exportedData.data);
|
||||
});
|
||||
it("calls getExport when password is empty", async () => {
|
||||
await service.getExport(mockUserId, "json", "");
|
||||
expect(individualVaultExportService.getExport).toHaveBeenCalledWith(mockUserId, "json");
|
||||
});
|
||||
|
||||
it("does not encrypted export trashed user items", async () => {
|
||||
cipherService.getAll.mockResolvedValue(UserCipherDomains);
|
||||
it("throws error if format is csv and password is provided", async () => {
|
||||
await expect(service.getExport(mockUserId, "csv", "secret")).rejects.toThrow(
|
||||
"CSV does not support password protected export",
|
||||
);
|
||||
expect(individualVaultExportService.getPasswordProtectedExport).not.toHaveBeenCalled();
|
||||
expect(individualVaultExportService.getExport).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
const actual = await exportService.getExport("encrypted_json");
|
||||
expect(typeof actual.data).toBe("string");
|
||||
const exportedData = actual as ExportedVaultAsString;
|
||||
expectEqualCiphers(UserCipherDomains.slice(0, 2), exportedData.data);
|
||||
});
|
||||
it("calls getPasswordProtectedExport when password is provided and format is not csv", async () => {
|
||||
await service.getExport(mockUserId, "json", "somePassword");
|
||||
expect(individualVaultExportService.getPasswordProtectedExport).toHaveBeenCalledWith(
|
||||
mockUserId,
|
||||
"somePassword",
|
||||
);
|
||||
});
|
||||
|
||||
describe("password protected export", () => {
|
||||
let exportedVault: ExportedVault;
|
||||
let exportString: string;
|
||||
let exportObject: any;
|
||||
let mac: MockProxy<EncString>;
|
||||
let data: MockProxy<EncString>;
|
||||
const password = "password";
|
||||
const salt = "salt";
|
||||
|
||||
describe("export json object", () => {
|
||||
beforeEach(async () => {
|
||||
mac = mock<EncString>();
|
||||
data = mock<EncString>();
|
||||
|
||||
mac.encryptedString = "mac" as EncryptedString;
|
||||
data.encryptedString = "encData" as EncryptedString;
|
||||
|
||||
jest.spyOn(Utils, "fromBufferToB64").mockReturnValue(salt);
|
||||
cipherService.getAllDecrypted.mockResolvedValue(UserCipherViews.slice(0, 1));
|
||||
|
||||
exportedVault = await exportService.getPasswordProtectedExport(password);
|
||||
expect(typeof exportedVault.data).toBe("string");
|
||||
exportString = (exportedVault as ExportedVaultAsString).data;
|
||||
exportObject = JSON.parse(exportString);
|
||||
});
|
||||
|
||||
it("specifies it is encrypted", () => {
|
||||
expect(exportObject.encrypted).toBe(true);
|
||||
});
|
||||
|
||||
it("specifies it's password protected", () => {
|
||||
expect(exportObject.passwordProtected).toBe(true);
|
||||
});
|
||||
|
||||
it("specifies salt", () => {
|
||||
expect(exportObject.salt).toEqual("salt");
|
||||
});
|
||||
|
||||
it("specifies kdfIterations", () => {
|
||||
expect(exportObject.kdfIterations).toEqual(PBKDF2KdfConfig.ITERATIONS.defaultValue);
|
||||
});
|
||||
|
||||
it("has kdfType", () => {
|
||||
expect(exportObject.kdfType).toEqual(KdfType.PBKDF2_SHA256);
|
||||
});
|
||||
|
||||
it("has a mac property", async () => {
|
||||
encryptService.encryptString.mockResolvedValue(mac);
|
||||
|
||||
exportedVault = await exportService.getPasswordProtectedExport(password);
|
||||
|
||||
expect(typeof exportedVault.data).toBe("string");
|
||||
exportString = (exportedVault as ExportedVaultAsString).data;
|
||||
exportObject = JSON.parse(exportString);
|
||||
expect(exportObject.encKeyValidation_DO_NOT_EDIT).toEqual(mac.encryptedString);
|
||||
});
|
||||
|
||||
it("has data property", async () => {
|
||||
encryptService.encryptString.mockResolvedValue(data);
|
||||
|
||||
exportedVault = await exportService.getPasswordProtectedExport(password);
|
||||
|
||||
expect(typeof exportedVault.data).toBe("string");
|
||||
exportString = (exportedVault as ExportedVaultAsString).data;
|
||||
exportObject = JSON.parse(exportString);
|
||||
expect(exportObject.data).toEqual(data.encryptedString);
|
||||
});
|
||||
|
||||
it("encrypts the data property", async () => {
|
||||
const unEncryptedExportVault = await exportService.getExport();
|
||||
|
||||
expect(typeof unEncryptedExportVault.data).toBe("string");
|
||||
const unEncryptedExportString = (unEncryptedExportVault as ExportedVaultAsString).data;
|
||||
expect(exportObject.data).not.toEqual(unEncryptedExportString);
|
||||
});
|
||||
it("uses default format csv if not provided", async () => {
|
||||
await service.getExport(mockUserId);
|
||||
expect(individualVaultExportService.getExport).toHaveBeenCalledWith(mockUserId, "csv");
|
||||
});
|
||||
});
|
||||
|
||||
it("exported unencrypted object contains folders", async () => {
|
||||
cipherService.getAllDecrypted.mockResolvedValue(UserCipherViews.slice(0, 1));
|
||||
describe("getOrganizationExport", () => {
|
||||
it("calls checkForImpersonation with userId", async () => {
|
||||
const spy = jest.spyOn(service as any, "checkForImpersonation");
|
||||
|
||||
const actual = await exportService.getExport("json");
|
||||
await service.getOrganizationExport(mockUserId, mockOrganizationId, "json", "");
|
||||
expect(spy).toHaveBeenCalledWith(mockUserId);
|
||||
});
|
||||
|
||||
expect(typeof actual.data).toBe("string");
|
||||
const exportedData = actual as ExportedVaultAsString;
|
||||
expectEqualFolderViews(UserFolderViews, exportedData.data);
|
||||
});
|
||||
it("validates the given userId matches the current authenticated user", async () => {
|
||||
const anotherUserId = "another-user-id" as UserId;
|
||||
|
||||
it("exported encrypted json contains folders", async () => {
|
||||
cipherService.getAll.mockResolvedValue(UserCipherDomains.slice(0, 1));
|
||||
await expect(
|
||||
service.getOrganizationExport(anotherUserId, mockOrganizationId, "json", ""),
|
||||
).rejects.toThrow("UserId does not match the currently authenticated user");
|
||||
|
||||
const actual = await exportService.getExport("encrypted_json");
|
||||
expect(organizationVaultExportService.getOrganizationExport).not.toHaveBeenCalledWith(
|
||||
mockUserId,
|
||||
mockOrganizationId,
|
||||
"json",
|
||||
);
|
||||
});
|
||||
|
||||
expect(typeof actual.data).toBe("string");
|
||||
const exportedData = actual as ExportedVaultAsString;
|
||||
expectEqualFolders(UserFolders, exportedData.data);
|
||||
it("calls getOrganizationExport when password is empty", async () => {
|
||||
await service.getOrganizationExport(mockUserId, mockOrganizationId, "json", "");
|
||||
expect(organizationVaultExportService.getOrganizationExport).toHaveBeenCalledWith(
|
||||
mockUserId,
|
||||
mockOrganizationId,
|
||||
"json",
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
it("throws error if format is csv and password is provided", async () => {
|
||||
await expect(
|
||||
service.getOrganizationExport(mockUserId, mockOrganizationId, "csv", "secret"),
|
||||
).rejects.toThrow("CSV does not support password protected export");
|
||||
expect(organizationVaultExportService.getPasswordProtectedExport).not.toHaveBeenCalled();
|
||||
expect(organizationVaultExportService.getOrganizationExport).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("calls getPasswordProtectedExport when password is provided and format is not csv", async () => {
|
||||
await service.getOrganizationExport(mockUserId, mockOrganizationId, "json", "somePassword");
|
||||
expect(organizationVaultExportService.getPasswordProtectedExport).toHaveBeenCalledWith(
|
||||
mockUserId,
|
||||
mockOrganizationId,
|
||||
"somePassword",
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
it("when calling getOrganizationExport without a password it passes onlyManagedCollection param on", async () => {
|
||||
await service.getOrganizationExport(mockUserId, mockOrganizationId, "json", "", true);
|
||||
expect(organizationVaultExportService.getOrganizationExport).toHaveBeenCalledWith(
|
||||
mockUserId,
|
||||
mockOrganizationId,
|
||||
"json",
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
it("when calling getOrganizationExport with a password it passes onlyManagedCollection param on", async () => {
|
||||
await service.getOrganizationExport(
|
||||
mockUserId,
|
||||
mockOrganizationId,
|
||||
"json",
|
||||
"somePassword",
|
||||
true,
|
||||
);
|
||||
expect(organizationVaultExportService.getPasswordProtectedExport).toHaveBeenCalledWith(
|
||||
mockUserId,
|
||||
mockOrganizationId,
|
||||
"somePassword",
|
||||
true,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
export class FolderResponse {
|
||||
id: string = null;
|
||||
name: string = null;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { OrganizationId } from "@bitwarden/common/types/guid";
|
||||
import { UserId, OrganizationId } from "@bitwarden/common/types/guid";
|
||||
|
||||
import { ExportedVault } from "../types";
|
||||
|
||||
@@ -11,26 +15,35 @@ export class VaultExportService implements VaultExportServiceAbstraction {
|
||||
constructor(
|
||||
private individualVaultExportService: IndividualVaultExportServiceAbstraction,
|
||||
private organizationVaultExportService: OrganizationVaultExportServiceAbstraction,
|
||||
private accountService: AccountService,
|
||||
) {}
|
||||
|
||||
/** Creates an export of an individual vault (My vault). Based on the provided format it will either be unencrypted, encrypted or password protected
|
||||
* @param userId The userId of the account requesting the export
|
||||
* @param format The format of the export
|
||||
* @param password An optional password if the export should be password-protected
|
||||
* @returns The exported vault
|
||||
* @throws Error if the format is csv and a password is provided
|
||||
*/
|
||||
async getExport(format: ExportFormat = "csv", password: string = ""): Promise<ExportedVault> {
|
||||
async getExport(
|
||||
userId: UserId,
|
||||
format: ExportFormat = "csv",
|
||||
password: string = "",
|
||||
): Promise<ExportedVault> {
|
||||
await this.checkForImpersonation(userId);
|
||||
|
||||
if (!Utils.isNullOrWhitespace(password)) {
|
||||
if (format == "csv") {
|
||||
throw new Error("CSV does not support password protected export");
|
||||
}
|
||||
|
||||
return this.individualVaultExportService.getPasswordProtectedExport(password);
|
||||
return this.individualVaultExportService.getPasswordProtectedExport(userId, password);
|
||||
}
|
||||
return this.individualVaultExportService.getExport(format);
|
||||
return this.individualVaultExportService.getExport(userId, format);
|
||||
}
|
||||
|
||||
/** Creates an export of an organizational vault. Based on the provided format it will either be unencrypted, encrypted or password protected
|
||||
* @param userId The userId of the account requesting the export
|
||||
* @param organizationId The organization id
|
||||
* @param format The format of the export
|
||||
* @param password The password to protect the export
|
||||
@@ -43,17 +56,21 @@ export class VaultExportService implements VaultExportServiceAbstraction {
|
||||
* @throws Error if the organization policies prevent the export
|
||||
*/
|
||||
async getOrganizationExport(
|
||||
userId: UserId,
|
||||
organizationId: OrganizationId,
|
||||
format: ExportFormat,
|
||||
password: string,
|
||||
onlyManagedCollections = false,
|
||||
): Promise<ExportedVault> {
|
||||
await this.checkForImpersonation(userId);
|
||||
|
||||
if (!Utils.isNullOrWhitespace(password)) {
|
||||
if (format == "csv") {
|
||||
throw new Error("CSV does not support password protected export");
|
||||
}
|
||||
|
||||
return this.organizationVaultExportService.getPasswordProtectedExport(
|
||||
userId,
|
||||
organizationId,
|
||||
password,
|
||||
onlyManagedCollections,
|
||||
@@ -61,9 +78,21 @@ export class VaultExportService implements VaultExportServiceAbstraction {
|
||||
}
|
||||
|
||||
return this.organizationVaultExportService.getOrganizationExport(
|
||||
userId,
|
||||
organizationId,
|
||||
format,
|
||||
onlyManagedCollections,
|
||||
);
|
||||
}
|
||||
|
||||
/** Checks if the provided userId matches the currently authenticated user
|
||||
* @param userId The userId to check
|
||||
* @throws Error if the userId does not match the currently authenticated user
|
||||
*/
|
||||
private async checkForImpersonation(userId: UserId): Promise<void> {
|
||||
const currentUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||
if (userId !== currentUserId) {
|
||||
throw new Error("UserId does not match the currently authenticated user");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
import { ReactiveFormsModule, UntypedFormBuilder, Validators } from "@angular/forms";
|
||||
import {
|
||||
combineLatest,
|
||||
firstValueFrom,
|
||||
map,
|
||||
merge,
|
||||
Observable,
|
||||
@@ -460,9 +461,11 @@ export class ExportComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
}
|
||||
|
||||
protected async getExportData(): Promise<ExportedVault> {
|
||||
const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||
return Utils.isNullOrWhitespace(this.organizationId)
|
||||
? this.exportService.getExport(this.format, this.filePassword)
|
||||
? this.exportService.getExport(userId, this.format, this.filePassword)
|
||||
: this.exportService.getOrganizationExport(
|
||||
userId,
|
||||
this.organizationId,
|
||||
this.format,
|
||||
this.filePassword,
|
||||
|
||||
Reference in New Issue
Block a user