mirror of
https://github.com/bitwarden/jslib
synced 2026-01-10 12:33:55 +00:00
Feature/password protected export (#689)
* Simplify password protected file format * no items to import is not an error * Await inner importer * Add export format type * Error if import file is password protected * Update tests * Test password protected with normat json importer * Simplify imports * Ignore code coverage directory * Expand importer options without changing display options * Import password require import error handling * Use interface * Fix curlies * linter fixes * Add null of empty util * Lint fixes * run prettier * Move import options to separate enum file * Fix imports
This commit is contained in:
@@ -4,7 +4,10 @@ import { ApiService } from "../abstractions/api.service";
|
||||
import { CipherService } from "../abstractions/cipher.service";
|
||||
import { CryptoService } from "../abstractions/crypto.service";
|
||||
import { CryptoFunctionService } from "../abstractions/cryptoFunction.service";
|
||||
import { ExportService as ExportServiceAbstraction } from "../abstractions/export.service";
|
||||
import {
|
||||
ExportFormat,
|
||||
ExportService as ExportServiceAbstraction,
|
||||
} from "../abstractions/export.service";
|
||||
import { FolderService } from "../abstractions/folder.service";
|
||||
import { CipherType } from "../enums/cipherType";
|
||||
import { KdfType } from "../enums/kdfType";
|
||||
@@ -33,7 +36,11 @@ export class ExportService implements ExportServiceAbstraction {
|
||||
private cryptoFunctionService: CryptoFunctionService
|
||||
) {}
|
||||
|
||||
async getExport(format: "csv" | "json" | "encrypted_json" = "csv"): Promise<string> {
|
||||
async getExport(format: ExportFormat = "csv", organizationId?: string): Promise<string> {
|
||||
if (organizationId) {
|
||||
return await this.getOrganizationExport(organizationId, format);
|
||||
}
|
||||
|
||||
if (format === "encrypted_json") {
|
||||
return this.getEncryptedExport();
|
||||
} else {
|
||||
@@ -41,14 +48,10 @@ export class ExportService implements ExportServiceAbstraction {
|
||||
}
|
||||
}
|
||||
|
||||
async getPasswordProtectedExport(
|
||||
password: string,
|
||||
format: "csv" | "json" | "encrypted_json" = "csv",
|
||||
organizationId?: string
|
||||
): Promise<string> {
|
||||
async getPasswordProtectedExport(password: string, organizationId?: string): Promise<string> {
|
||||
const clearText = organizationId
|
||||
? await this.getOrganizationExport(organizationId, format)
|
||||
: await this.getExport(format);
|
||||
? await this.getOrganizationExport(organizationId, "json")
|
||||
: await this.getExport("json");
|
||||
|
||||
const salt = Utils.fromBufferToB64(await this.cryptoFunctionService.randomBytes(16));
|
||||
const kdfIterations = 100000;
|
||||
@@ -65,7 +68,6 @@ export class ExportService implements ExportServiceAbstraction {
|
||||
const jsonDoc: any = {
|
||||
encrypted: true,
|
||||
passwordProtected: true,
|
||||
format: format,
|
||||
salt: salt,
|
||||
kdfIterations: kdfIterations,
|
||||
kdfType: KdfType.PBKDF2_SHA256,
|
||||
@@ -78,7 +80,7 @@ export class ExportService implements ExportServiceAbstraction {
|
||||
|
||||
async getOrganizationExport(
|
||||
organizationId: string,
|
||||
format: "csv" | "json" | "encrypted_json" = "csv"
|
||||
format: ExportFormat = "csv"
|
||||
): Promise<string> {
|
||||
if (format === "encrypted_json") {
|
||||
return this.getOrganizationEncryptedExport(organizationId);
|
||||
|
||||
@@ -4,12 +4,15 @@ import { CollectionService } from "../abstractions/collection.service";
|
||||
import { CryptoService } from "../abstractions/crypto.service";
|
||||
import { FolderService } from "../abstractions/folder.service";
|
||||
import { I18nService } from "../abstractions/i18n.service";
|
||||
import {
|
||||
ImportOption,
|
||||
ImportService as ImportServiceAbstraction,
|
||||
} from "../abstractions/import.service";
|
||||
import { ImportService as ImportServiceAbstraction } from "../abstractions/import.service";
|
||||
import { PlatformUtilsService } from "../abstractions/platformUtils.service";
|
||||
import { CipherType } from "../enums/cipherType";
|
||||
import {
|
||||
featuredImportOptions,
|
||||
ImportOption,
|
||||
ImportType,
|
||||
regularImportOptions,
|
||||
} from "../enums/importOptions";
|
||||
import { AscendoCsvImporter } from "../importers/ascendoCsvImporter";
|
||||
import { AvastCsvImporter } from "../importers/avastCsvImporter";
|
||||
import { AvastJsonImporter } from "../importers/avastJsonImporter";
|
||||
@@ -30,6 +33,7 @@ import { EnpassJsonImporter } from "../importers/enpassJsonImporter";
|
||||
import { FirefoxCsvImporter } from "../importers/firefoxCsvImporter";
|
||||
import { FSecureFskImporter } from "../importers/fsecureFskImporter";
|
||||
import { GnomeJsonImporter } from "../importers/gnomeJsonImporter";
|
||||
import { ImportError } from "../importers/importError";
|
||||
import { Importer } from "../importers/importer";
|
||||
import { KasperskyTxtImporter } from "../importers/kasperskyTxtImporter";
|
||||
import { KeePass2XmlImporter } from "../importers/keepass2XmlImporter";
|
||||
@@ -76,75 +80,6 @@ import { KvpRequest } from "../models/request/kvpRequest";
|
||||
import { ErrorResponse } from "../models/response/errorResponse";
|
||||
import { CipherView } from "../models/view/cipherView";
|
||||
|
||||
const featuredImportOptions = [
|
||||
{ id: "bitwardenjson", name: "Bitwarden (json)" },
|
||||
{ id: "bitwardencsv", name: "Bitwarden (csv)" },
|
||||
{ id: "chromecsv", name: "Chrome (csv)" },
|
||||
{ id: "dashlanejson", name: "Dashlane (json)" },
|
||||
{ id: "firefoxcsv", name: "Firefox (csv)" },
|
||||
{ id: "keepass2xml", name: "KeePass 2 (xml)" },
|
||||
{ id: "lastpasscsv", name: "LastPass (csv)" },
|
||||
{ id: "safaricsv", name: "Safari and macOS (csv)" },
|
||||
{ id: "1password1pif", name: "1Password (1pif)" },
|
||||
] as const;
|
||||
|
||||
const regularImportOptions = [
|
||||
{ id: "keepassxcsv", name: "KeePassX (csv)" },
|
||||
{ id: "1passwordwincsv", name: "1Password 6 and 7 Windows (csv)" },
|
||||
{ id: "1passwordmaccsv", name: "1Password 6 and 7 Mac (csv)" },
|
||||
{ id: "roboformcsv", name: "RoboForm (csv)" },
|
||||
{ id: "keepercsv", name: "Keeper (csv)" },
|
||||
// Temporarily remove this option for the Feb release
|
||||
// { id: "keeperjson", name: "Keeper (json)" },
|
||||
{ id: "enpasscsv", name: "Enpass (csv)" },
|
||||
{ id: "enpassjson", name: "Enpass (json)" },
|
||||
{ id: "safeincloudxml", name: "SafeInCloud (xml)" },
|
||||
{ id: "pwsafexml", name: "Password Safe (xml)" },
|
||||
{ id: "stickypasswordxml", name: "Sticky Password (xml)" },
|
||||
{ id: "msecurecsv", name: "mSecure (csv)" },
|
||||
{ id: "truekeycsv", name: "True Key (csv)" },
|
||||
{ id: "passwordbossjson", name: "Password Boss (json)" },
|
||||
{ id: "zohovaultcsv", name: "Zoho Vault (csv)" },
|
||||
{ id: "splashidcsv", name: "SplashID (csv)" },
|
||||
{ id: "passworddragonxml", name: "Password Dragon (xml)" },
|
||||
{ id: "padlockcsv", name: "Padlock (csv)" },
|
||||
{ id: "passboltcsv", name: "Passbolt (csv)" },
|
||||
{ id: "clipperzhtml", name: "Clipperz (html)" },
|
||||
{ id: "aviracsv", name: "Avira (csv)" },
|
||||
{ id: "saferpasscsv", name: "SaferPass (csv)" },
|
||||
{ id: "upmcsv", name: "Universal Password Manager (csv)" },
|
||||
{ id: "ascendocsv", name: "Ascendo DataVault (csv)" },
|
||||
{ id: "meldiumcsv", name: "Meldium (csv)" },
|
||||
{ id: "passkeepcsv", name: "PassKeep (csv)" },
|
||||
{ id: "operacsv", name: "Opera (csv)" },
|
||||
{ id: "vivaldicsv", name: "Vivaldi (csv)" },
|
||||
{ id: "gnomejson", name: "GNOME Passwords and Keys/Seahorse (json)" },
|
||||
{ id: "blurcsv", name: "Blur (csv)" },
|
||||
{ id: "passwordagentcsv", name: "Password Agent (csv)" },
|
||||
{ id: "passpackcsv", name: "Passpack (csv)" },
|
||||
{ id: "passmanjson", name: "Passman (json)" },
|
||||
{ id: "avastcsv", name: "Avast Passwords (csv)" },
|
||||
{ id: "avastjson", name: "Avast Passwords (json)" },
|
||||
{ id: "fsecurefsk", name: "F-Secure KEY (fsk)" },
|
||||
{ id: "kasperskytxt", name: "Kaspersky Password Manager (txt)" },
|
||||
{ id: "remembearcsv", name: "RememBear (csv)" },
|
||||
{ id: "passwordwallettxt", name: "PasswordWallet (txt)" },
|
||||
{ id: "mykicsv", name: "Myki (csv)" },
|
||||
{ id: "securesafecsv", name: "SecureSafe (csv)" },
|
||||
{ id: "logmeoncecsv", name: "LogMeOnce (csv)" },
|
||||
{ id: "blackberrycsv", name: "BlackBerry Password Keeper (csv)" },
|
||||
{ id: "buttercupcsv", name: "Buttercup (csv)" },
|
||||
{ id: "codebookcsv", name: "Codebook (csv)" },
|
||||
{ id: "encryptrcsv", name: "Encryptr (csv)" },
|
||||
{ id: "yoticsv", name: "Yoti (csv)" },
|
||||
{ id: "nordpasscsv", name: "Nordpass (csv)" },
|
||||
] as const;
|
||||
|
||||
export type ImportType =
|
||||
| typeof featuredImportOptions[number]["id"]
|
||||
| typeof regularImportOptions[number]["id"]
|
||||
| "bitwardenpasswordprotected";
|
||||
|
||||
export class ImportService implements ImportServiceAbstraction {
|
||||
featuredImportOptions = featuredImportOptions as readonly ImportOption[];
|
||||
|
||||
@@ -168,11 +103,11 @@ export class ImportService implements ImportServiceAbstraction {
|
||||
importer: Importer,
|
||||
fileContents: string,
|
||||
organizationId: string = null
|
||||
): Promise<Error> {
|
||||
): Promise<ImportError> {
|
||||
const importResult = await importer.parse(fileContents);
|
||||
if (importResult.success) {
|
||||
if (importResult.folders.length === 0 && importResult.ciphers.length === 0) {
|
||||
return new Error(this.i18nService.t("importNothingError"));
|
||||
return new ImportError(this.i18nService.t("importNothingError"));
|
||||
} else if (importResult.ciphers.length > 0) {
|
||||
const halfway = Math.floor(importResult.ciphers.length / 2);
|
||||
const last = importResult.ciphers.length - 1;
|
||||
@@ -182,7 +117,7 @@ export class ImportService implements ImportServiceAbstraction {
|
||||
this.badData(importResult.ciphers[halfway]) &&
|
||||
this.badData(importResult.ciphers[last])
|
||||
) {
|
||||
return new Error(this.i18nService.t("importFormatError"));
|
||||
return new ImportError(this.i18nService.t("importFormatError"));
|
||||
}
|
||||
}
|
||||
try {
|
||||
@@ -194,15 +129,18 @@ export class ImportService implements ImportServiceAbstraction {
|
||||
return null;
|
||||
} else {
|
||||
if (!Utils.isNullOrWhitespace(importResult.errorMessage)) {
|
||||
return new Error(importResult.errorMessage);
|
||||
return new ImportError(importResult.errorMessage, importResult.missingPassword);
|
||||
} else {
|
||||
return new Error(this.i18nService.t("importFormatError"));
|
||||
return new ImportError(
|
||||
this.i18nService.t("importFormatError"),
|
||||
importResult.missingPassword
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getImporter(
|
||||
format: ImportType,
|
||||
format: ImportType | "bitwardenpasswordprotected",
|
||||
organizationId: string = null,
|
||||
password: string = null
|
||||
): Importer {
|
||||
@@ -214,7 +152,7 @@ export class ImportService implements ImportServiceAbstraction {
|
||||
return importer;
|
||||
}
|
||||
|
||||
private getImporterInstance(format: ImportType, password: string) {
|
||||
private getImporterInstance(format: ImportType | "bitwardenpasswordprotected", password: string) {
|
||||
if (format == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -226,7 +164,6 @@ export class ImportService implements ImportServiceAbstraction {
|
||||
return new BitwardenJsonImporter(this.cryptoService, this.i18nService);
|
||||
case "bitwardenpasswordprotected":
|
||||
return new BitwardenPasswordProtectedImporter(
|
||||
this,
|
||||
this.cryptoService,
|
||||
this.i18nService,
|
||||
password
|
||||
@@ -394,9 +331,9 @@ export class ImportService implements ImportServiceAbstraction {
|
||||
);
|
||||
}
|
||||
|
||||
private handleServerError(errorResponse: ErrorResponse, importResult: ImportResult): Error {
|
||||
private handleServerError(errorResponse: ErrorResponse, importResult: ImportResult): ImportError {
|
||||
if (errorResponse.validationErrors == null) {
|
||||
return new Error(errorResponse.message);
|
||||
return new ImportError(errorResponse.message);
|
||||
}
|
||||
|
||||
let errorMessage = "";
|
||||
@@ -434,6 +371,6 @@ export class ImportService implements ImportServiceAbstraction {
|
||||
errorMessage += "[" + itemType + '] "' + item.name + '": ' + value;
|
||||
});
|
||||
|
||||
return new Error(errorMessage);
|
||||
return new ImportError(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user