mirror of
https://github.com/bitwarden/browser
synced 2025-12-11 22:03:36 +00:00
[PM-10749] [BEEEP] New export format: Zip with attachments (#10465)
* Add new export format: zip * Restrict zip export to just individual vaults * Add tests * Remove unused import * Fix build error * Fix tests * Fix test * Fix retrieval of ciphers by passing in activeUserId * Guard feature behind `export-attachments`-feature-flag * Extend cipher filter to also filter out any ciphers that are assigned to an organization * Added apiService to retrieve AttachmentData (metaData) and then download the attachment - Added ApiService as a depdency within DI for VaultExportService/IndividualVaultExportService - Added unit tests for filtering ciphers - Added unit test for downloading attachment metadata and attachments * Moved attachment decryption into a separate method and added unit tests * Added null check for creating the base attachment folder * Move format check for zip within Org export into an early return/throw * Add feature flag guard on the CLI * Extend ExportScopeCallout to display an individual export will contain attachment when zip-format is selected * Fix adding/removing the zip-export option based on selected vault and state of `export-attachments` feature-flag * Separate AAA visually using whitespace within tests * Remove unused error var * Write test that verifies different http request failures when retrieving attachment data * Remove uneeded ignore lint rule * Rewrite test to actually check that ciphers assigned to an org are filtered out * Introduce ExportedVault return type (#13842) * Define ExportedVault type unioned by 2 new types that describe a plain-text export vs a blob-based zip-export * Extend static getFileName to handle formats and add unit-tests * Introduce new export return type throughout the vault export module - Update abstractions - Update return types within implementations - Update callers/consumers to handle the new return value - Fix all unit tests * Add support for new export return type and fix download of blobs via CLI * Add documentation to public methods --------- Co-authored-by: Daniel James Smith <djsmith85@users.noreply.github.com> --------- Co-authored-by: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> Co-authored-by: Daniel James Smith <djsmith85@users.noreply.github.com>
This commit is contained in:
@@ -3007,6 +3007,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"exportingIndividualVaultWithAttachmentsDescription": {
|
||||
"message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
|
||||
"placeholders": {
|
||||
"email": {
|
||||
"content": "$1",
|
||||
"example": "name@example.com"
|
||||
}
|
||||
}
|
||||
},
|
||||
"exportingOrganizationVaultTitle": {
|
||||
"message": "Exporting organization vault"
|
||||
},
|
||||
|
||||
@@ -1026,6 +1026,7 @@ export default class MainBackground {
|
||||
this.cryptoFunctionService,
|
||||
this.kdfConfigService,
|
||||
this.accountService,
|
||||
this.apiService,
|
||||
);
|
||||
|
||||
this.organizationVaultExportService = new OrganizationVaultExportService(
|
||||
|
||||
@@ -795,6 +795,7 @@ export class ServiceContainer {
|
||||
this.cryptoFunctionService,
|
||||
this.kdfConfigService,
|
||||
this.accountService,
|
||||
this.apiService,
|
||||
);
|
||||
|
||||
this.organizationExportService = new OrganizationVaultExportService(
|
||||
|
||||
@@ -7,11 +7,15 @@ import { EventCollectionService } from "@bitwarden/common/abstractions/event/eve
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import { EventType } from "@bitwarden/common/enums";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import {
|
||||
ExportFormat,
|
||||
EXPORT_FORMATS,
|
||||
VaultExportServiceAbstraction,
|
||||
ExportedVault,
|
||||
ExportedVaultAsBlob,
|
||||
} from "@bitwarden/vault-export-core";
|
||||
|
||||
import { Response } from "../models/response";
|
||||
@@ -22,6 +26,7 @@ export class ExportCommand {
|
||||
private exportService: VaultExportServiceAbstraction,
|
||||
private policyService: PolicyService,
|
||||
private eventCollectionService: EventCollectionService,
|
||||
private configService: ConfigService,
|
||||
) {}
|
||||
|
||||
async run(options: OptionValues): Promise<Response> {
|
||||
@@ -42,6 +47,13 @@ export class ExportCommand {
|
||||
const format =
|
||||
password && options.format == "json" ? "encrypted_json" : (options.format ?? "csv");
|
||||
|
||||
if (
|
||||
format == "zip" &&
|
||||
!(await this.configService.getFeatureFlag(FeatureFlag.ExportAttachments))
|
||||
) {
|
||||
return Response.badRequest("Exporting attachments is not supported in this environment.");
|
||||
}
|
||||
|
||||
if (!this.isSupportedExportFormat(format)) {
|
||||
return Response.badRequest(
|
||||
`'${format}' is not a supported export format. Supported formats: ${EXPORT_FORMATS.join(
|
||||
@@ -54,7 +66,7 @@ export class ExportCommand {
|
||||
return Response.error("`" + options.organizationid + "` is not a GUID.");
|
||||
}
|
||||
|
||||
let exportContent: string = null;
|
||||
let exportContent: ExportedVault = null;
|
||||
try {
|
||||
if (format === "encrypted_json") {
|
||||
password = await this.promptPassword(password);
|
||||
@@ -78,34 +90,28 @@ export class ExportCommand {
|
||||
} catch (e) {
|
||||
return Response.error(e);
|
||||
}
|
||||
return await this.saveFile(exportContent, options, format);
|
||||
return await this.saveFile(exportContent, options);
|
||||
}
|
||||
|
||||
private async saveFile(
|
||||
exportContent: string,
|
||||
options: OptionValues,
|
||||
format: ExportFormat,
|
||||
): Promise<Response> {
|
||||
private async saveFile(exportContent: ExportedVault, options: OptionValues): Promise<Response> {
|
||||
try {
|
||||
const fileName = this.getFileName(format, options.organizationid != null ? "org" : null);
|
||||
return await CliUtils.saveResultToFile(exportContent, options.output, fileName);
|
||||
if (exportContent.type === "application/zip") {
|
||||
exportContent = exportContent as ExportedVaultAsBlob;
|
||||
const arrayBuffer = await exportContent.data.arrayBuffer();
|
||||
const buffer = Buffer.from(arrayBuffer);
|
||||
return await CliUtils.saveResultToFile(buffer, options.output, exportContent.fileName);
|
||||
}
|
||||
|
||||
return await CliUtils.saveResultToFile(
|
||||
exportContent.data,
|
||||
options.output,
|
||||
exportContent.fileName,
|
||||
);
|
||||
} catch (e) {
|
||||
return Response.error(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private getFileName(format: ExportFormat, prefix?: string) {
|
||||
if (format === "encrypted_json") {
|
||||
if (prefix == null) {
|
||||
prefix = "encrypted";
|
||||
} else {
|
||||
prefix = "encrypted_" + prefix;
|
||||
}
|
||||
format = "json";
|
||||
}
|
||||
return this.exportService.getFileName(prefix, format);
|
||||
}
|
||||
|
||||
private async promptPassword(password: string | boolean) {
|
||||
// boolean => flag set with no value, we need to prompt for password
|
||||
// string => flag set with value, use this value for password
|
||||
|
||||
@@ -501,6 +501,7 @@ export class VaultProgram extends BaseProgram {
|
||||
this.serviceContainer.exportService,
|
||||
this.serviceContainer.policyService,
|
||||
this.serviceContainer.eventCollectionService,
|
||||
this.serviceContainer.configService,
|
||||
);
|
||||
const response = await command.run(options);
|
||||
this.processResponse(response);
|
||||
|
||||
@@ -2490,6 +2490,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"exportingIndividualVaultWithAttachmentsDescription": {
|
||||
"message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
|
||||
"placeholders": {
|
||||
"email": {
|
||||
"content": "$1",
|
||||
"example": "name@example.com"
|
||||
}
|
||||
}
|
||||
},
|
||||
"exportingOrganizationVaultTitle": {
|
||||
"message": "Exporting organization vault"
|
||||
},
|
||||
|
||||
@@ -6762,6 +6762,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"exportingIndividualVaultWithAttachmentsDescription": {
|
||||
"message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
|
||||
"placeholders": {
|
||||
"email": {
|
||||
"content": "$1",
|
||||
"example": "name@example.com"
|
||||
}
|
||||
}
|
||||
},
|
||||
"exportingOrganizationVaultDesc": {
|
||||
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
|
||||
"placeholders": {
|
||||
|
||||
Reference in New Issue
Block a user