[PM-3040] [BEEEP] Extend json-export to include passwordhistory and vault item dates (created, updated, deleted) (#5917)
* Add password history to json exports Change callout to not mention missing password history any longer * Added item meta dates to json exports Added vault items creation-/revision-/deleted-dates to json exports * Removed unnecessary promises * Add bitwarden-json-export types Define types Use types in vault-export-service Move existing password-protected type to export-types * Use bitwarden-json-export types in bitwarden-json-importer * Clean up passwordHistory if needed * Define and use bitwarden-csv-export-types
This commit is contained in:
parent
b56bb19c02
commit
15f29c5fb1
|
@ -5454,8 +5454,8 @@
|
||||||
"exportingOrganizationVaultTitle": {
|
"exportingOrganizationVaultTitle": {
|
||||||
"message": "Exporting organization vault"
|
"message": "Exporting organization vault"
|
||||||
},
|
},
|
||||||
"exportingPersonalVaultDescription": {
|
"exportingIndividualVaultDescription": {
|
||||||
"message": "Only the individual vault items associated with $EMAIL$ will be exported. Organization vault items will not be included. Only vault item information will be exported and will not include associated password history or attachments.",
|
"message": "Only the individual vault items associated with $EMAIL$ will be exported. Organization vault items will not be included. Only vault item information will be exported and will not include associated attachments.",
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"email": {
|
"email": {
|
||||||
"content": "$1",
|
"content": "$1",
|
||||||
|
|
|
@ -35,7 +35,7 @@ export class ExportScopeCalloutComponent implements OnInit {
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
title: "exportingPersonalVaultTitle",
|
title: "exportingPersonalVaultTitle",
|
||||||
description: "exportingPersonalVaultDescription",
|
description: "exportingIndividualVaultDescription",
|
||||||
scopeIdentifier: await this.stateService.getEmail(),
|
scopeIdentifier: await this.stateService.getEmail(),
|
||||||
};
|
};
|
||||||
this.show = true;
|
this.show = true;
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { CardExport } from "./card.export";
|
||||||
import { FieldExport } from "./field.export";
|
import { FieldExport } from "./field.export";
|
||||||
import { IdentityExport } from "./identity.export";
|
import { IdentityExport } from "./identity.export";
|
||||||
import { LoginExport } from "./login.export";
|
import { LoginExport } from "./login.export";
|
||||||
|
import { PasswordHistoryExport } from "./password-history.export";
|
||||||
import { SecureNoteExport } from "./secure-note.export";
|
import { SecureNoteExport } from "./secure-note.export";
|
||||||
|
|
||||||
export class CipherExport {
|
export class CipherExport {
|
||||||
|
@ -26,6 +27,10 @@ export class CipherExport {
|
||||||
req.card = null;
|
req.card = null;
|
||||||
req.identity = null;
|
req.identity = null;
|
||||||
req.reprompt = CipherRepromptType.None;
|
req.reprompt = CipherRepromptType.None;
|
||||||
|
req.passwordHistory = [];
|
||||||
|
req.creationDate = null;
|
||||||
|
req.revisionDate = null;
|
||||||
|
req.deletedDate = null;
|
||||||
return req;
|
return req;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,6 +68,13 @@ export class CipherExport {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (req.passwordHistory != null) {
|
||||||
|
view.passwordHistory = req.passwordHistory.map((ph) => PasswordHistoryExport.toView(ph));
|
||||||
|
}
|
||||||
|
|
||||||
|
view.creationDate = req.creationDate;
|
||||||
|
view.revisionDate = req.revisionDate;
|
||||||
|
view.deletedDate = req.deletedDate;
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,6 +108,13 @@ export class CipherExport {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (req.passwordHistory != null) {
|
||||||
|
domain.passwordHistory = req.passwordHistory.map((ph) => PasswordHistoryExport.toDomain(ph));
|
||||||
|
}
|
||||||
|
|
||||||
|
domain.creationDate = req.creationDate;
|
||||||
|
domain.revisionDate = req.revisionDate;
|
||||||
|
domain.deletedDate = req.deletedDate;
|
||||||
return domain;
|
return domain;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,6 +131,10 @@ export class CipherExport {
|
||||||
card: CardExport;
|
card: CardExport;
|
||||||
identity: IdentityExport;
|
identity: IdentityExport;
|
||||||
reprompt: CipherRepromptType;
|
reprompt: CipherRepromptType;
|
||||||
|
passwordHistory: PasswordHistoryExport[] = null;
|
||||||
|
revisionDate: Date = null;
|
||||||
|
creationDate: Date = null;
|
||||||
|
deletedDate: Date = null;
|
||||||
|
|
||||||
// Use build method instead of ctor so that we can control order of JSON stringify for pretty print
|
// Use build method instead of ctor so that we can control order of JSON stringify for pretty print
|
||||||
build(o: CipherView | CipherDomain) {
|
build(o: CipherView | CipherDomain) {
|
||||||
|
@ -152,5 +175,17 @@ export class CipherExport {
|
||||||
this.identity = new IdentityExport(o.identity);
|
this.identity = new IdentityExport(o.identity);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (o.passwordHistory != null) {
|
||||||
|
if (o instanceof CipherView) {
|
||||||
|
this.passwordHistory = o.passwordHistory.map((ph) => new PasswordHistoryExport(ph));
|
||||||
|
} else {
|
||||||
|
this.passwordHistory = o.passwordHistory.map((ph) => new PasswordHistoryExport(ph));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.creationDate = o.creationDate;
|
||||||
|
this.revisionDate = o.revisionDate;
|
||||||
|
this.deletedDate = o.deletedDate;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
import { EncString } from "../../platform/models/domain/enc-string";
|
||||||
|
import { Password } from "../../vault/models/domain/password";
|
||||||
|
import { PasswordHistoryView } from "../../vault/models/view/password-history.view";
|
||||||
|
|
||||||
|
export class PasswordHistoryExport {
|
||||||
|
static template(): PasswordHistoryExport {
|
||||||
|
const req = new PasswordHistoryExport();
|
||||||
|
req.password = null;
|
||||||
|
req.lastUsedDate = null;
|
||||||
|
return req;
|
||||||
|
}
|
||||||
|
|
||||||
|
static toView(req: PasswordHistoryExport, view = new PasswordHistoryView()) {
|
||||||
|
view.password = req.password;
|
||||||
|
view.lastUsedDate = req.lastUsedDate;
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
static toDomain(req: PasswordHistoryExport, domain = new Password()) {
|
||||||
|
domain.password = req.password != null ? new EncString(req.password) : null;
|
||||||
|
domain.lastUsedDate = req.lastUsedDate;
|
||||||
|
return domain;
|
||||||
|
}
|
||||||
|
|
||||||
|
password: string;
|
||||||
|
lastUsedDate: Date = null;
|
||||||
|
|
||||||
|
constructor(o?: PasswordHistoryView | Password) {
|
||||||
|
if (o == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (o instanceof PasswordHistoryView) {
|
||||||
|
this.password = o.password;
|
||||||
|
} else {
|
||||||
|
this.password = o.password?.encryptedString;
|
||||||
|
}
|
||||||
|
this.lastUsedDate = o.lastUsedDate;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type";
|
||||||
|
|
||||||
|
export type BitwardenCsvExportType = {
|
||||||
|
type: string;
|
||||||
|
name: string;
|
||||||
|
notes: string;
|
||||||
|
fields: string;
|
||||||
|
reprompt: CipherRepromptType;
|
||||||
|
// Login props
|
||||||
|
login_uri: string[];
|
||||||
|
login_username: string;
|
||||||
|
login_password: string;
|
||||||
|
login_totp: string;
|
||||||
|
favorite: number | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type BitwardenCsvIndividualExportType = BitwardenCsvExportType & {
|
||||||
|
folder: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type BitwardenCsvOrgExportType = BitwardenCsvExportType & {
|
||||||
|
collections: string[] | null;
|
||||||
|
};
|
|
@ -0,0 +1,51 @@
|
||||||
|
import {
|
||||||
|
CipherWithIdExport,
|
||||||
|
CollectionWithIdExport,
|
||||||
|
FolderWithIdExport,
|
||||||
|
} from "@bitwarden/common/models/export";
|
||||||
|
|
||||||
|
// Base
|
||||||
|
export type BitwardenJsonExport = {
|
||||||
|
encrypted: boolean;
|
||||||
|
items: CipherWithIdExport[];
|
||||||
|
};
|
||||||
|
|
||||||
|
// Decrypted
|
||||||
|
export type BitwardenUnEncryptedJsonExport = BitwardenJsonExport & {
|
||||||
|
encrypted: false;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type BitwardenUnEncryptedIndividualJsonExport = BitwardenUnEncryptedJsonExport & {
|
||||||
|
folders: FolderWithIdExport[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type BitwardenUnEncryptedOrgJsonExport = BitwardenUnEncryptedJsonExport & {
|
||||||
|
collections: CollectionWithIdExport[];
|
||||||
|
};
|
||||||
|
|
||||||
|
// Account-encrypted
|
||||||
|
export type BitwardenEncryptedJsonExport = BitwardenJsonExport & {
|
||||||
|
encrypted: true;
|
||||||
|
encKeyValidation_DO_NOT_EDIT: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type BitwardenEncryptedIndividualJsonExport = BitwardenEncryptedJsonExport & {
|
||||||
|
folders: FolderWithIdExport[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type BitwardenEncryptedOrgJsonExport = BitwardenEncryptedJsonExport & {
|
||||||
|
collections: CollectionWithIdExport[];
|
||||||
|
};
|
||||||
|
|
||||||
|
// Password-protected
|
||||||
|
export type BitwardenPasswordProtectedFileFormat = {
|
||||||
|
encrypted: boolean;
|
||||||
|
passwordProtected: boolean;
|
||||||
|
salt: string;
|
||||||
|
kdfIterations: number;
|
||||||
|
kdfMemory?: number;
|
||||||
|
kdfParallelism?: number;
|
||||||
|
kdfType: number;
|
||||||
|
encKeyValidation_DO_NOT_EDIT: string;
|
||||||
|
data: string;
|
||||||
|
};
|
|
@ -1,11 +0,0 @@
|
||||||
export interface BitwardenPasswordProtectedFileFormat {
|
|
||||||
encrypted: boolean;
|
|
||||||
passwordProtected: boolean;
|
|
||||||
salt: string;
|
|
||||||
kdfIterations: number;
|
|
||||||
kdfMemory?: number;
|
|
||||||
kdfParallelism?: number;
|
|
||||||
kdfType: number;
|
|
||||||
encKeyValidation_DO_NOT_EDIT: string;
|
|
||||||
data: string;
|
|
||||||
}
|
|
|
@ -26,7 +26,18 @@ import { CollectionView } from "@bitwarden/common/vault/models/view/collection.v
|
||||||
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
|
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
|
||||||
|
|
||||||
import { ExportHelper } from "../../export-helper";
|
import { ExportHelper } from "../../export-helper";
|
||||||
import { BitwardenPasswordProtectedFileFormat } from "../bitwarden-password-protected-types";
|
import {
|
||||||
|
BitwardenCsvExportType,
|
||||||
|
BitwardenCsvIndividualExportType,
|
||||||
|
BitwardenCsvOrgExportType,
|
||||||
|
} from "../bitwarden-csv-export-type";
|
||||||
|
import {
|
||||||
|
BitwardenEncryptedIndividualJsonExport,
|
||||||
|
BitwardenEncryptedOrgJsonExport,
|
||||||
|
BitwardenUnEncryptedIndividualJsonExport,
|
||||||
|
BitwardenUnEncryptedOrgJsonExport,
|
||||||
|
BitwardenPasswordProtectedFileFormat,
|
||||||
|
} from "../bitwarden-json-export-types";
|
||||||
|
|
||||||
import { ExportFormat, VaultExportServiceAbstraction } from "./vault-export.service.abstraction";
|
import { ExportFormat, VaultExportServiceAbstraction } from "./vault-export.service.abstraction";
|
||||||
|
|
||||||
|
@ -123,7 +134,7 @@ export class VaultExportService implements VaultExportServiceAbstraction {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const exportCiphers: any[] = [];
|
const exportCiphers: BitwardenCsvIndividualExportType[] = [];
|
||||||
decCiphers.forEach((c) => {
|
decCiphers.forEach((c) => {
|
||||||
// only export logins and secure notes
|
// only export logins and secure notes
|
||||||
if (c.type !== CipherType.Login && c.type !== CipherType.SecureNote) {
|
if (c.type !== CipherType.Login && c.type !== CipherType.SecureNote) {
|
||||||
|
@ -133,7 +144,7 @@ export class VaultExportService implements VaultExportServiceAbstraction {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const cipher: any = {};
|
const cipher = {} as BitwardenCsvIndividualExportType;
|
||||||
cipher.folder =
|
cipher.folder =
|
||||||
c.folderId != null && foldersMap.has(c.folderId) ? foldersMap.get(c.folderId).name : null;
|
c.folderId != null && foldersMap.has(c.folderId) ? foldersMap.get(c.folderId).name : null;
|
||||||
cipher.favorite = c.favorite ? 1 : null;
|
cipher.favorite = c.favorite ? 1 : null;
|
||||||
|
@ -143,7 +154,7 @@ export class VaultExportService implements VaultExportServiceAbstraction {
|
||||||
|
|
||||||
return papa.unparse(exportCiphers);
|
return papa.unparse(exportCiphers);
|
||||||
} else {
|
} else {
|
||||||
const jsonDoc: any = {
|
const jsonDoc: BitwardenUnEncryptedIndividualJsonExport = {
|
||||||
encrypted: false,
|
encrypted: false,
|
||||||
folders: [],
|
folders: [],
|
||||||
items: [],
|
items: [],
|
||||||
|
@ -193,7 +204,7 @@ export class VaultExportService implements VaultExportServiceAbstraction {
|
||||||
|
|
||||||
const encKeyValidation = await this.cryptoService.encrypt(Utils.newGuid());
|
const encKeyValidation = await this.cryptoService.encrypt(Utils.newGuid());
|
||||||
|
|
||||||
const jsonDoc: any = {
|
const jsonDoc: BitwardenEncryptedIndividualJsonExport = {
|
||||||
encrypted: true,
|
encrypted: true,
|
||||||
encKeyValidation_DO_NOT_EDIT: encKeyValidation.encryptedString,
|
encKeyValidation_DO_NOT_EDIT: encKeyValidation.encryptedString,
|
||||||
folders: [],
|
folders: [],
|
||||||
|
@ -269,14 +280,14 @@ export class VaultExportService implements VaultExportServiceAbstraction {
|
||||||
collectionsMap.set(c.id, c);
|
collectionsMap.set(c.id, c);
|
||||||
});
|
});
|
||||||
|
|
||||||
const exportCiphers: any[] = [];
|
const exportCiphers: BitwardenCsvOrgExportType[] = [];
|
||||||
decCiphers.forEach((c) => {
|
decCiphers.forEach((c) => {
|
||||||
// only export logins and secure notes
|
// only export logins and secure notes
|
||||||
if (c.type !== CipherType.Login && c.type !== CipherType.SecureNote) {
|
if (c.type !== CipherType.Login && c.type !== CipherType.SecureNote) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const cipher: any = {};
|
const cipher = {} as BitwardenCsvOrgExportType;
|
||||||
cipher.collections = [];
|
cipher.collections = [];
|
||||||
if (c.collectionIds != null) {
|
if (c.collectionIds != null) {
|
||||||
cipher.collections = c.collectionIds
|
cipher.collections = c.collectionIds
|
||||||
|
@ -289,7 +300,7 @@ export class VaultExportService implements VaultExportServiceAbstraction {
|
||||||
|
|
||||||
return papa.unparse(exportCiphers);
|
return papa.unparse(exportCiphers);
|
||||||
} else {
|
} else {
|
||||||
const jsonDoc: any = {
|
const jsonDoc: BitwardenUnEncryptedOrgJsonExport = {
|
||||||
encrypted: false,
|
encrypted: false,
|
||||||
collections: [],
|
collections: [],
|
||||||
items: [],
|
items: [],
|
||||||
|
@ -317,20 +328,17 @@ export class VaultExportService implements VaultExportServiceAbstraction {
|
||||||
|
|
||||||
promises.push(
|
promises.push(
|
||||||
this.apiService.getCollections(organizationId).then((c) => {
|
this.apiService.getCollections(organizationId).then((c) => {
|
||||||
const collectionPromises: any = [];
|
|
||||||
if (c != null && c.data != null && c.data.length > 0) {
|
if (c != null && c.data != null && c.data.length > 0) {
|
||||||
c.data.forEach((r) => {
|
c.data.forEach((r) => {
|
||||||
const collection = new Collection(new CollectionData(r as CollectionDetailsResponse));
|
const collection = new Collection(new CollectionData(r as CollectionDetailsResponse));
|
||||||
collections.push(collection);
|
collections.push(collection);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return Promise.all(collectionPromises);
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
promises.push(
|
promises.push(
|
||||||
this.apiService.getCiphersOrganization(organizationId).then((c) => {
|
this.apiService.getCiphersOrganization(organizationId).then((c) => {
|
||||||
const cipherPromises: any = [];
|
|
||||||
if (c != null && c.data != null && c.data.length > 0) {
|
if (c != null && c.data != null && c.data.length > 0) {
|
||||||
c.data
|
c.data
|
||||||
.filter((item) => item.deletedDate === null)
|
.filter((item) => item.deletedDate === null)
|
||||||
|
@ -339,7 +347,6 @@ export class VaultExportService implements VaultExportServiceAbstraction {
|
||||||
ciphers.push(cipher);
|
ciphers.push(cipher);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return Promise.all(cipherPromises);
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -348,7 +355,7 @@ export class VaultExportService implements VaultExportServiceAbstraction {
|
||||||
const orgKey = await this.cryptoService.getOrgKey(organizationId);
|
const orgKey = await this.cryptoService.getOrgKey(organizationId);
|
||||||
const encKeyValidation = await this.cryptoService.encrypt(Utils.newGuid(), orgKey);
|
const encKeyValidation = await this.cryptoService.encrypt(Utils.newGuid(), orgKey);
|
||||||
|
|
||||||
const jsonDoc: any = {
|
const jsonDoc: BitwardenEncryptedOrgJsonExport = {
|
||||||
encrypted: true,
|
encrypted: true,
|
||||||
encKeyValidation_DO_NOT_EDIT: encKeyValidation.encryptedString,
|
encKeyValidation_DO_NOT_EDIT: encKeyValidation.encryptedString,
|
||||||
collections: [],
|
collections: [],
|
||||||
|
@ -369,7 +376,7 @@ export class VaultExportService implements VaultExportServiceAbstraction {
|
||||||
return JSON.stringify(jsonDoc, null, " ");
|
return JSON.stringify(jsonDoc, null, " ");
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildCommonCipher(cipher: any, c: CipherView) {
|
private buildCommonCipher(cipher: BitwardenCsvExportType, c: CipherView): BitwardenCsvExportType {
|
||||||
cipher.type = null;
|
cipher.type = null;
|
||||||
cipher.name = c.name;
|
cipher.name = c.name;
|
||||||
cipher.notes = c.notes;
|
cipher.notes = c.notes;
|
||||||
|
@ -382,7 +389,7 @@ export class VaultExportService implements VaultExportServiceAbstraction {
|
||||||
cipher.login_totp = null;
|
cipher.login_totp = null;
|
||||||
|
|
||||||
if (c.fields) {
|
if (c.fields) {
|
||||||
c.fields.forEach((f: any) => {
|
c.fields.forEach((f) => {
|
||||||
if (!cipher.fields) {
|
if (!cipher.fields) {
|
||||||
cipher.fields = "";
|
cipher.fields = "";
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -313,6 +313,9 @@ export abstract class BaseImporter {
|
||||||
if (cipher.fields != null && cipher.fields.length === 0) {
|
if (cipher.fields != null && cipher.fields.length === 0) {
|
||||||
cipher.fields = null;
|
cipher.fields = null;
|
||||||
}
|
}
|
||||||
|
if (cipher.passwordHistory != null && cipher.passwordHistory.length === 0) {
|
||||||
|
cipher.passwordHistory = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected processKvp(
|
protected processKvp(
|
||||||
|
|
|
@ -6,13 +6,21 @@ import {
|
||||||
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
||||||
|
import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view";
|
||||||
|
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
|
||||||
|
import {
|
||||||
|
BitwardenEncryptedIndividualJsonExport,
|
||||||
|
BitwardenEncryptedOrgJsonExport,
|
||||||
|
BitwardenJsonExport,
|
||||||
|
BitwardenUnEncryptedIndividualJsonExport,
|
||||||
|
BitwardenUnEncryptedOrgJsonExport,
|
||||||
|
} from "@bitwarden/exporter/vault-export/bitwarden-json-export-types";
|
||||||
|
|
||||||
import { ImportResult } from "../../models/import-result";
|
import { ImportResult } from "../../models/import-result";
|
||||||
import { BaseImporter } from "../base-importer";
|
import { BaseImporter } from "../base-importer";
|
||||||
import { Importer } from "../importer";
|
import { Importer } from "../importer";
|
||||||
|
|
||||||
export class BitwardenJsonImporter extends BaseImporter implements Importer {
|
export class BitwardenJsonImporter extends BaseImporter implements Importer {
|
||||||
private results: any;
|
|
||||||
private result: ImportResult;
|
private result: ImportResult;
|
||||||
|
|
||||||
protected constructor(
|
protected constructor(
|
||||||
|
@ -24,25 +32,27 @@ export class BitwardenJsonImporter extends BaseImporter implements Importer {
|
||||||
|
|
||||||
async parse(data: string): Promise<ImportResult> {
|
async parse(data: string): Promise<ImportResult> {
|
||||||
this.result = new ImportResult();
|
this.result = new ImportResult();
|
||||||
this.results = JSON.parse(data);
|
const results: BitwardenJsonExport = JSON.parse(data);
|
||||||
if (this.results == null || this.results.items == null) {
|
if (results == null || results.items == null) {
|
||||||
this.result.success = false;
|
this.result.success = false;
|
||||||
return this.result;
|
return this.result;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.results.encrypted) {
|
if (results.encrypted) {
|
||||||
await this.parseEncrypted();
|
await this.parseEncrypted(results as any);
|
||||||
} else {
|
} else {
|
||||||
this.parseDecrypted();
|
await this.parseDecrypted(results as any);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.result;
|
return this.result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async parseEncrypted() {
|
private async parseEncrypted(
|
||||||
if (this.results.encKeyValidation_DO_NOT_EDIT != null) {
|
results: BitwardenEncryptedIndividualJsonExport | BitwardenEncryptedOrgJsonExport
|
||||||
|
) {
|
||||||
|
if (results.encKeyValidation_DO_NOT_EDIT != null) {
|
||||||
const orgKey = await this.cryptoService.getOrgKey(this.organizationId);
|
const orgKey = await this.cryptoService.getOrgKey(this.organizationId);
|
||||||
const encKeyValidation = new EncString(this.results.encKeyValidation_DO_NOT_EDIT);
|
const encKeyValidation = new EncString(results.encKeyValidation_DO_NOT_EDIT);
|
||||||
const encKeyValidationDecrypt = await this.cryptoService.decryptToUtf8(
|
const encKeyValidationDecrypt = await this.cryptoService.decryptToUtf8(
|
||||||
encKeyValidation,
|
encKeyValidation,
|
||||||
orgKey
|
orgKey
|
||||||
|
@ -54,30 +64,11 @@ export class BitwardenJsonImporter extends BaseImporter implements Importer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const groupingsMap = new Map<string, number>();
|
const groupingsMap = this.organization
|
||||||
|
? await this.parseCollections(results as BitwardenEncryptedOrgJsonExport)
|
||||||
|
: await this.parseFolders(results as BitwardenEncryptedIndividualJsonExport);
|
||||||
|
|
||||||
if (this.organization && this.results.collections != null) {
|
for (const c of results.items) {
|
||||||
for (const c of this.results.collections as CollectionWithIdExport[]) {
|
|
||||||
const collection = CollectionWithIdExport.toDomain(c);
|
|
||||||
if (collection != null) {
|
|
||||||
collection.organizationId = this.organizationId;
|
|
||||||
const view = await collection.decrypt();
|
|
||||||
groupingsMap.set(c.id, this.result.collections.length);
|
|
||||||
this.result.collections.push(view);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (!this.organization && this.results.folders != null) {
|
|
||||||
for (const f of this.results.folders as FolderWithIdExport[]) {
|
|
||||||
const folder = FolderWithIdExport.toDomain(f);
|
|
||||||
if (folder != null) {
|
|
||||||
const view = await folder.decrypt();
|
|
||||||
groupingsMap.set(f.id, this.result.folders.length);
|
|
||||||
this.result.folders.push(view);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const c of this.results.items as CipherWithIdExport[]) {
|
|
||||||
const cipher = CipherWithIdExport.toDomain(c);
|
const cipher = CipherWithIdExport.toDomain(c);
|
||||||
// reset ids incase they were set for some reason
|
// reset ids incase they were set for some reason
|
||||||
cipher.id = null;
|
cipher.id = null;
|
||||||
|
@ -113,28 +104,14 @@ export class BitwardenJsonImporter extends BaseImporter implements Importer {
|
||||||
this.result.success = true;
|
this.result.success = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private parseDecrypted() {
|
private async parseDecrypted(
|
||||||
const groupingsMap = new Map<string, number>();
|
results: BitwardenUnEncryptedIndividualJsonExport | BitwardenUnEncryptedOrgJsonExport
|
||||||
if (this.organization && this.results.collections != null) {
|
) {
|
||||||
this.results.collections.forEach((c: CollectionWithIdExport) => {
|
const groupingsMap = this.organization
|
||||||
const collection = CollectionWithIdExport.toView(c);
|
? await this.parseCollections(results as BitwardenUnEncryptedOrgJsonExport)
|
||||||
if (collection != null) {
|
: await this.parseFolders(results as BitwardenUnEncryptedIndividualJsonExport);
|
||||||
collection.organizationId = null;
|
|
||||||
groupingsMap.set(c.id, this.result.collections.length);
|
|
||||||
this.result.collections.push(collection);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if (!this.organization && this.results.folders != null) {
|
|
||||||
this.results.folders.forEach((f: FolderWithIdExport) => {
|
|
||||||
const folder = FolderWithIdExport.toView(f);
|
|
||||||
if (folder != null) {
|
|
||||||
groupingsMap.set(f.id, this.result.folders.length);
|
|
||||||
this.result.folders.push(folder);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.results.items.forEach((c: CipherWithIdExport) => {
|
results.items.forEach((c) => {
|
||||||
const cipher = CipherWithIdExport.toView(c);
|
const cipher = CipherWithIdExport.toView(c);
|
||||||
// reset ids incase they were set for some reason
|
// reset ids incase they were set for some reason
|
||||||
cipher.id = null;
|
cipher.id = null;
|
||||||
|
@ -168,4 +145,60 @@ export class BitwardenJsonImporter extends BaseImporter implements Importer {
|
||||||
|
|
||||||
this.result.success = true;
|
this.result.success = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async parseFolders(
|
||||||
|
data: BitwardenUnEncryptedIndividualJsonExport | BitwardenEncryptedIndividualJsonExport
|
||||||
|
): Promise<Map<string, number>> | null {
|
||||||
|
if (data.folders == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const groupingsMap = new Map<string, number>();
|
||||||
|
|
||||||
|
for (const f of data.folders) {
|
||||||
|
let folderView: FolderView;
|
||||||
|
if (data.encrypted) {
|
||||||
|
const folder = FolderWithIdExport.toDomain(f);
|
||||||
|
if (folder != null) {
|
||||||
|
folderView = await folder.decrypt();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
folderView = FolderWithIdExport.toView(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (folderView != null) {
|
||||||
|
groupingsMap.set(f.id, this.result.folders.length);
|
||||||
|
this.result.folders.push(folderView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return groupingsMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async parseCollections(
|
||||||
|
data: BitwardenUnEncryptedOrgJsonExport | BitwardenEncryptedOrgJsonExport
|
||||||
|
): Promise<Map<string, number>> | null {
|
||||||
|
if (data.collections == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const groupingsMap = new Map<string, number>();
|
||||||
|
|
||||||
|
for (const c of data.collections) {
|
||||||
|
let collectionView: CollectionView;
|
||||||
|
if (data.encrypted) {
|
||||||
|
const collection = CollectionWithIdExport.toDomain(c);
|
||||||
|
collection.organizationId = this.organizationId;
|
||||||
|
collectionView = await collection.decrypt();
|
||||||
|
} else {
|
||||||
|
collectionView = CollectionWithIdExport.toView(c);
|
||||||
|
collectionView.organizationId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (collectionView != null) {
|
||||||
|
groupingsMap.set(c.id, this.result.collections.length);
|
||||||
|
this.result.collections.push(collectionView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return groupingsMap;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.se
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
||||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||||
import { BitwardenPasswordProtectedFileFormat } from "@bitwarden/exporter/vault-export/bitwarden-password-protected-types";
|
import { BitwardenPasswordProtectedFileFormat } from "@bitwarden/exporter/vault-export/bitwarden-json-export-types";
|
||||||
|
|
||||||
import { ImportResult } from "../../models/import-result";
|
import { ImportResult } from "../../models/import-result";
|
||||||
import { Importer } from "../importer";
|
import { Importer } from "../importer";
|
||||||
|
|
Loading…
Reference in New Issue