1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-10 13:23:34 +00:00

[PM-5459] Move libs/exporter to libs/tools/ (#7380)

* Move libs/exporter into libs/tools/*

Migrating all files from libs/exporter over to libs/tools/export/vault-export/vault-export-core
Rename package to vault-export-core
Fix all file paths

* Update libs and tsconfig imports

* Fix client imports

* Fix eslint, jest and package-lock.json

* Update CODEOWNERS

* Add README.md to whitelist-capital-letters

* Fix vault-export-service tests not running

* Update libs/tools/export/vault-export/README.md

Co-authored-by: Vincent Salucci <26154748+vincentsalucci@users.noreply.github.com>

* Fix types imports

* Export types from vault-export-core

* Fixed content of README

---------

Co-authored-by: Daniel James Smith <djsmith85@users.noreply.github.com>
Co-authored-by: Vincent Salucci <26154748+vincentsalucci@users.noreply.github.com>
This commit is contained in:
Daniel James Smith
2024-02-13 20:22:37 +01:00
committed by GitHub
parent c8b04729cb
commit 9980c3feb9
46 changed files with 414 additions and 80 deletions

View File

@@ -190,14 +190,6 @@ import { SyncNotifierService } from "@bitwarden/common/vault/services/sync/sync-
import { SyncService } from "@bitwarden/common/vault/services/sync/sync.service";
import { TotpService } from "@bitwarden/common/vault/services/totp.service";
import { VaultSettingsService } from "@bitwarden/common/vault/services/vault-settings/vault-settings.service";
import {
VaultExportService,
VaultExportServiceAbstraction,
OrganizationVaultExportService,
OrganizationVaultExportServiceAbstraction,
IndividualVaultExportService,
IndividualVaultExportServiceAbstraction,
} from "@bitwarden/exporter/vault-export";
import {
ImportApiService,
ImportApiServiceAbstraction,
@@ -205,6 +197,14 @@ import {
ImportServiceAbstraction,
} from "@bitwarden/importer/core";
import { PasswordRepromptService } from "@bitwarden/vault";
import {
VaultExportService,
VaultExportServiceAbstraction,
OrganizationVaultExportService,
OrganizationVaultExportServiceAbstraction,
IndividualVaultExportService,
IndividualVaultExportServiceAbstraction,
} from "@bitwarden/vault-export-core";
import { AuthGuard } from "../auth/guards/auth.guard";
import { UnauthGuard } from "../auth/guards/unauth.guard";

View File

@@ -16,7 +16,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { EncryptedExportType } from "@bitwarden/common/tools/enums/encrypted-export-type.enum";
import { DialogService } from "@bitwarden/components";
import { VaultExportServiceAbstraction } from "@bitwarden/exporter/vault-export";
import { VaultExportServiceAbstraction } from "@bitwarden/vault-export-core";
import { PasswordStrengthComponent } from "../../password-strength/password-strength.component";

View File

@@ -19,6 +19,6 @@
},
"dependencies": {
"@bitwarden/common": "file:../common",
"@bitwarden/exporter": "file:../exporter"
"@bitwarden/vault-export-core": "file:../tools/export/vault-export/vault-export-core"
}
}

View File

@@ -15,7 +15,7 @@ import {
BitwardenJsonExport,
BitwardenUnEncryptedIndividualJsonExport,
BitwardenUnEncryptedOrgJsonExport,
} from "@bitwarden/exporter/vault-export/bitwarden-json-export-types";
} from "@bitwarden/vault-export-core";
import { ImportResult } from "../../models/import-result";
import { BaseImporter } from "../base-importer";

View File

@@ -5,7 +5,7 @@ import { KdfType } from "@bitwarden/common/platform/enums";
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { BitwardenPasswordProtectedFileFormat } from "@bitwarden/exporter/vault-export/bitwarden-json-export-types";
import { BitwardenPasswordProtectedFileFormat } from "@bitwarden/vault-export-core";
import { ImportResult } from "../../models/import-result";
import { Importer } from "../importer";

View File

@@ -9,7 +9,7 @@
"@bitwarden/billing": ["../billing/src"],
"@bitwarden/common/*": ["../common/src/*"],
"@bitwarden/components": ["../components/src"],
"@bitwarden/exporter/*": ["../exporter/src/*"],
"@bitwarden/vault-export-core": ["../tools/export/vault-export/vault-export-core/src"],
"@bitwarden/importer/core": ["../importer/src"],
"@bitwarden/importer/ui": ["../importer/src/components"],
"@bitwarden/platform": ["../platform/src"],

View File

@@ -0,0 +1,15 @@
# Vault Export
This folder contains 2 packages that can be used to export a users or an organizational vault.
## vault-export-core
Package name: `@bitwarden/vault-export-core`
Contains all types, models, and services to export a user or organization's vault.
Currently in use by the Bitwarden Web Vault, CLI, desktop app and browser extension
## vault-export-ui
Package name: `@bitwarden/vault-export-ui`

View File

@@ -1,15 +1,13 @@
const { pathsToModuleNameMapper } = require("ts-jest");
const { compilerOptions } = require("../shared/tsconfig.libs");
const sharedConfig = require("../shared/jest.config.ts");
const { compilerOptions } = require("../../../../shared/tsconfig.libs");
/** @type {import('jest').Config} */
module.exports = {
...sharedConfig,
testMatch: ["**/+(*.)+(spec).+(ts)"],
preset: "ts-jest",
testEnvironment: "jsdom",
moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, {
prefix: "<rootDir>/",
prefix: "<rootDir>/../../../",
}),
};

View File

@@ -1,7 +1,7 @@
{
"name": "@bitwarden/exporter",
"name": "@bitwarden/vault-export-core",
"version": "0.0.0",
"description": "Home for all Bitwarden exporters.",
"description": "Home for all Bitwarden vault exporters.",
"keywords": [
"bitwarden"
],
@@ -18,6 +18,6 @@
"build:watch": "npm run clean && tsc -watch"
},
"dependencies": {
"@bitwarden/common": "file:../common"
"@bitwarden/common": "file:../../../../common"
}
}

View File

@@ -1,3 +1,5 @@
export * from "./types";
export * from "./services/vault-export.service.abstraction";
export * from "./services/vault-export.service";
export * from "./services/org-vault-export.service.abstraction";

View File

@@ -7,9 +7,7 @@ import { Utils } from "@bitwarden/common/platform/misc/utils";
import { CipherType } from "@bitwarden/common/vault/enums";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { BitwardenCsvExportType } from "../bitwarden-csv-export-type";
import { BitwardenPasswordProtectedFileFormat } from "../bitwarden-json-export-types";
import { BitwardenCsvExportType, BitwardenPasswordProtectedFileFormat } from "../types";
export class BaseVaultExportService {
constructor(
protected cryptoService: CryptoService,

View File

@@ -18,7 +18,7 @@ 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 { BuildTestObject, GetUniqueString } from "../../../../common/spec";
import { BuildTestObject, GetUniqueString } from "../../../../../../common/spec";
import { IndividualVaultExportService } from "./individual-vault-export.service";

View File

@@ -13,11 +13,11 @@ 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 { BitwardenCsvIndividualExportType } from "../bitwarden-csv-export-type";
import {
BitwardenCsvIndividualExportType,
BitwardenEncryptedIndividualJsonExport,
BitwardenUnEncryptedIndividualJsonExport,
} from "../bitwarden-json-export-types";
} from "../types";
import { BaseVaultExportService } from "./base-vault-export.service";
import { IndividualVaultExportServiceAbstraction } from "./individual-vault-export.service.abstraction";

View File

@@ -17,11 +17,11 @@ import { CollectionDetailsResponse } from "@bitwarden/common/vault/models/respon
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view";
import { BitwardenCsvOrgExportType } from "../bitwarden-csv-export-type";
import {
BitwardenCsvOrgExportType,
BitwardenEncryptedOrgJsonExport,
BitwardenUnEncryptedOrgJsonExport,
} from "../bitwarden-json-export-types";
} from "../types";
import { BaseVaultExportService } from "./base-vault-export.service";
import { OrganizationVaultExportServiceAbstraction } from "./org-vault-export.service.abstraction";

View File

@@ -0,0 +1,289 @@
import { mock, MockProxy } from "jest-mock-extended";
import { KdfConfig } from "@bitwarden/common/auth/models/domain/kdf-config";
import { CipherWithIdExport } from "@bitwarden/common/models/export/cipher-with-ids.export";
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { KdfType, PBKDF2_ITERATIONS } from "@bitwarden/common/platform/enums";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { EncryptedString, EncString } from "@bitwarden/common/platform/models/domain/enc-string";
import { StateService } from "@bitwarden/common/platform/services/state.service";
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 { BuildTestObject, GetUniqueString } from "../../../../../../common/spec";
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));
}
describe("VaultExportService", () => {
let exportService: IndividualVaultExportService;
let cryptoFunctionService: MockProxy<CryptoFunctionService>;
let cipherService: MockProxy<CipherService>;
let folderService: MockProxy<FolderService>;
let cryptoService: MockProxy<CryptoService>;
let stateService: MockProxy<StateService>;
beforeEach(() => {
cryptoFunctionService = mock<CryptoFunctionService>();
cipherService = mock<CipherService>();
folderService = mock<FolderService>();
cryptoService = mock<CryptoService>();
stateService = mock<StateService>();
folderService.getAllDecryptedFromState.mockResolvedValue(UserFolderViews);
folderService.getAllFromState.mockResolvedValue(UserFolders);
stateService.getKdfType.mockResolvedValue(KdfType.PBKDF2_SHA256);
stateService.getKdfConfig.mockResolvedValue(new KdfConfig(PBKDF2_ITERATIONS.defaultValue));
cryptoService.encrypt.mockResolvedValue(new EncString("encrypted"));
exportService = new IndividualVaultExportService(
folderService,
cipherService,
cryptoService,
cryptoFunctionService,
stateService,
);
});
it("exports unencrypted user ciphers", async () => {
cipherService.getAllDecrypted.mockResolvedValue(UserCipherViews.slice(0, 1));
const actual = await exportService.getExport("json");
expectEqualCiphers(UserCipherViews.slice(0, 1), actual);
});
it("exports encrypted json user ciphers", async () => {
cipherService.getAll.mockResolvedValue(UserCipherDomains.slice(0, 1));
const actual = await exportService.getExport("encrypted_json");
expectEqualCiphers(UserCipherDomains.slice(0, 1), actual);
});
it("does not unencrypted export trashed user items", async () => {
cipherService.getAllDecrypted.mockResolvedValue(UserCipherViews);
const actual = await exportService.getExport("json");
expectEqualCiphers(UserCipherViews.slice(0, 2), actual);
});
it("does not encrypted export trashed user items", async () => {
cipherService.getAll.mockResolvedValue(UserCipherDomains);
const actual = await exportService.getExport("encrypted_json");
expectEqualCiphers(UserCipherDomains.slice(0, 2), actual);
});
describe("password protected export", () => {
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));
exportString = await exportService.getPasswordProtectedExport(password);
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(PBKDF2_ITERATIONS.defaultValue);
});
it("has kdfType", () => {
expect(exportObject.kdfType).toEqual(KdfType.PBKDF2_SHA256);
});
it("has a mac property", async () => {
cryptoService.encrypt.mockResolvedValue(mac);
exportString = await exportService.getPasswordProtectedExport(password);
exportObject = JSON.parse(exportString);
expect(exportObject.encKeyValidation_DO_NOT_EDIT).toEqual(mac.encryptedString);
});
it("has data property", async () => {
cryptoService.encrypt.mockResolvedValue(data);
exportString = await exportService.getPasswordProtectedExport(password);
exportObject = JSON.parse(exportString);
expect(exportObject.data).toEqual(data.encryptedString);
});
it("encrypts the data property", async () => {
const unencrypted = await exportService.getExport();
expect(exportObject.data).not.toEqual(unencrypted);
});
});
});
it("exported unencrypted object contains folders", async () => {
cipherService.getAllDecrypted.mockResolvedValue(UserCipherViews.slice(0, 1));
await folderService.getAllDecryptedFromState();
const actual = await exportService.getExport("json");
expectEqualFolderViews(UserFolderViews, actual);
});
it("exported encrypted json contains folders", async () => {
cipherService.getAll.mockResolvedValue(UserCipherDomains.slice(0, 1));
await folderService.getAllFromState();
const actual = await exportService.getExport("encrypted_json");
expectEqualFolders(UserFolders, actual);
});
});
export class FolderResponse {
id: string = null;
name: string = null;
}

View File

@@ -1,7 +1,6 @@
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { ExportHelper } from "../../export-helper";
import { ExportHelper } from "./export-helper";
import { IndividualVaultExportServiceAbstraction } from "./individual-vault-export.service.abstraction";
import { OrganizationVaultExportServiceAbstraction } from "./org-vault-export.service.abstraction";
import { ExportFormat, VaultExportServiceAbstraction } from "./vault-export.service.abstraction";

View File

@@ -0,0 +1,2 @@
export * from "./bitwarden-csv-export-type";
export * from "./bitwarden-json-export-types";

View File

@@ -1,5 +1,5 @@
{
"extends": "../shared/tsconfig.libs",
"extends": "../../../../shared/tsconfig.libs",
"include": ["src"],
"exclude": ["node_modules", "dist"]
}