merged latest EC-598, fix conflicts

This commit is contained in:
jng 2023-08-17 10:48:06 -04:00
commit 9a2a7ef6cb
16 changed files with 350 additions and 148 deletions

View File

@ -396,7 +396,7 @@ export default class MainBackground {
// AuthService should send the messages to the background not popup.
send = (subscriber: string, arg: any = {}) => {
const message = Object.assign({}, { command: subscriber }, arg);
that.runtimeBackground.processMessage(message, that as any, null);
that.runtimeBackground.processMessage(message, that as any);
};
})();
this.authService = new AuthService(

View File

@ -52,7 +52,11 @@ export default class RuntimeBackground {
sender: chrome.runtime.MessageSender,
sendResponse: any
) => {
const messagesWithResponse = ["fido2RegisterCredentialRequest", "fido2GetCredentialRequest"];
const messagesWithResponse = [
"checkFido2FeatureEnabled",
"fido2RegisterCredentialRequest",
"fido2GetCredentialRequest",
];
if (messagesWithResponse.includes(msg.command)) {
this.processMessage(msg, sender).then(
@ -233,6 +237,8 @@ export default class RuntimeBackground {
case "fido2AbortRequest":
this.abortControllers.get(msg.abortedRequestId)?.abort();
break;
case "checkFido2FeatureEnabled":
return await this.main.fido2ClientService.isFido2FeatureEnabled();
case "fido2RegisterCredentialRequest":
return await this.main.fido2ClientService
.createCredential(msg.data, this.createAbortController(msg.requestId))

View File

@ -1,65 +1,80 @@
import { Message, MessageType } from "./messaging/message";
import { Messenger } from "./messaging/messenger";
const s = document.createElement("script");
s.src = chrome.runtime.getURL("content/fido2/page-script.js");
(document.head || document.documentElement).appendChild(s);
function checkFido2FeatureEnabled() {
chrome.runtime.sendMessage(
{ command: "checkFido2FeatureEnabled" },
(response: { result?: boolean }) => initializeFido2ContentScript(response.result)
);
}
const messenger = Messenger.forDOMCommunication(window);
messenger.handler = async (message, abortController) => {
const abortHandler = () =>
chrome.runtime.sendMessage({
command: "fido2AbortRequest",
abortedRequestId: message.metadata.requestId,
});
abortController.signal.addEventListener("abort", abortHandler);
if (message.type === MessageType.CredentialCreationRequest) {
return new Promise((resolve, reject) => {
chrome.runtime.sendMessage(
{
command: "fido2RegisterCredentialRequest",
data: message.data,
requestId: message.metadata.requestId,
},
(response) => {
if (response.error !== undefined) {
return reject(response.error);
}
resolve({
type: MessageType.CredentialCreationResponse,
result: response.result,
});
}
);
});
function initializeFido2ContentScript(isFido2FeatureEnabled: boolean) {
if (isFido2FeatureEnabled !== true) {
return;
}
if (message.type === MessageType.CredentialGetRequest) {
return new Promise((resolve, reject) => {
chrome.runtime.sendMessage(
{
command: "fido2GetCredentialRequest",
data: message.data,
requestId: message.metadata.requestId,
},
(response) => {
if (response.error !== undefined) {
return reject(response.error);
const s = document.createElement("script");
s.src = chrome.runtime.getURL("content/fido2/page-script.js");
(document.head || document.documentElement).appendChild(s);
const messenger = Messenger.forDOMCommunication(window);
messenger.handler = async (message, abortController) => {
const abortHandler = () =>
chrome.runtime.sendMessage({
command: "fido2AbortRequest",
abortedRequestId: message.metadata.requestId,
});
abortController.signal.addEventListener("abort", abortHandler);
if (message.type === MessageType.CredentialCreationRequest) {
return new Promise((resolve, reject) => {
chrome.runtime.sendMessage(
{
command: "fido2RegisterCredentialRequest",
data: message.data,
requestId: message.metadata.requestId,
},
(response) => {
if (response.error !== undefined) {
return reject(response.error);
}
resolve({
type: MessageType.CredentialCreationResponse,
result: response.result,
});
}
);
});
}
resolve({
type: MessageType.CredentialGetResponse,
result: response.result,
});
}
);
}).finally(() =>
abortController.signal.removeEventListener("abort", abortHandler)
) as Promise<Message>;
}
if (message.type === MessageType.CredentialGetRequest) {
return new Promise((resolve, reject) => {
chrome.runtime.sendMessage(
{
command: "fido2GetCredentialRequest",
data: message.data,
requestId: message.metadata.requestId,
},
(response) => {
if (response.error !== undefined) {
return reject(response.error);
}
return undefined;
};
resolve({
type: MessageType.CredentialGetResponse,
result: response.result,
});
}
);
}).finally(() =>
abortController.signal.removeEventListener("abort", abortHandler)
) as Promise<Message>;
}
return undefined;
};
}
checkFido2FeatureEnabled();

View File

@ -5454,8 +5454,8 @@
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
"exportingPersonalVaultDescription": {
"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.",
"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 attachments.",
"placeholders": {
"email": {
"content": "$1",

View File

@ -35,7 +35,7 @@ export class ExportScopeCalloutComponent implements OnInit {
}
: {
title: "exportingPersonalVaultTitle",
description: "exportingPersonalVaultDescription",
description: "exportingIndividualVaultDescription",
scopeIdentifier: await this.stateService.getEmail(),
};
this.show = true;

View File

@ -8,6 +8,7 @@ import { CardExport } from "./card.export";
import { FieldExport } from "./field.export";
import { IdentityExport } from "./identity.export";
import { LoginExport } from "./login.export";
import { PasswordHistoryExport } from "./password-history.export";
import { SecureNoteExport } from "./secure-note.export";
export class CipherExport {
@ -26,6 +27,10 @@ export class CipherExport {
req.card = null;
req.identity = null;
req.reprompt = CipherRepromptType.None;
req.passwordHistory = [];
req.creationDate = null;
req.revisionDate = null;
req.deletedDate = null;
return req;
}
@ -63,6 +68,13 @@ export class CipherExport {
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;
}
@ -96,6 +108,13 @@ export class CipherExport {
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;
}
@ -112,6 +131,10 @@ export class CipherExport {
card: CardExport;
identity: IdentityExport;
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
build(o: CipherView | CipherDomain) {
@ -152,5 +175,17 @@ export class CipherExport {
this.identity = new IdentityExport(o.identity);
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;
}
}

View File

@ -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;
}
}

View File

@ -11,6 +11,7 @@ export abstract class Fido2ClientService {
params: AssertCredentialParams,
abortController?: AbortController
) => Promise<AssertCredentialResult>;
isFido2FeatureEnabled: () => Promise<boolean>;
}
export interface CreateCredentialParams {

View File

@ -34,14 +34,15 @@ export class Fido2ClientService implements Fido2ClientServiceAbstraction {
private logService?: LogService
) {}
async isFido2FeatureEnabled(): Promise<boolean> {
return await this.configService.getFeatureFlagBool(FeatureFlag.Fido2VaultCredentials);
}
async createCredential(
params: CreateCredentialParams,
abortController = new AbortController()
): Promise<CreateCredentialResult> {
// debugger;
const enableFido2VaultCredentials = await this.configService.getFeatureFlagBool(
FeatureFlag.Fido2VaultCredentials
);
const enableFido2VaultCredentials = await this.isFido2FeatureEnabled();
if (!enableFido2VaultCredentials) {
this.logService?.warning(`[Fido2Client] Fido2VaultCredential is not enabled`);
@ -192,9 +193,7 @@ export class Fido2ClientService implements Fido2ClientServiceAbstraction {
params: AssertCredentialParams,
abortController = new AbortController()
): Promise<AssertCredentialResult> {
const enableFido2VaultCredentials = await this.configService.getFeatureFlagBool(
FeatureFlag.Fido2VaultCredentials
);
const enableFido2VaultCredentials = await this.isFido2FeatureEnabled();
if (!enableFido2VaultCredentials) {
this.logService?.warning(`[Fido2Client] Fido2VaultCredential is not enabled`);

View File

@ -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;
};

View File

@ -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;
};

View File

@ -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;
}

View File

@ -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 { 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";
@ -123,7 +134,7 @@ export class VaultExportService implements VaultExportServiceAbstraction {
}
});
const exportCiphers: any[] = [];
const exportCiphers: BitwardenCsvIndividualExportType[] = [];
decCiphers.forEach((c) => {
// only export logins and secure notes
if (c.type !== CipherType.Login && c.type !== CipherType.SecureNote) {
@ -133,7 +144,7 @@ export class VaultExportService implements VaultExportServiceAbstraction {
return;
}
const cipher: any = {};
const cipher = {} as BitwardenCsvIndividualExportType;
cipher.folder =
c.folderId != null && foldersMap.has(c.folderId) ? foldersMap.get(c.folderId).name : null;
cipher.favorite = c.favorite ? 1 : null;
@ -143,7 +154,7 @@ export class VaultExportService implements VaultExportServiceAbstraction {
return papa.unparse(exportCiphers);
} else {
const jsonDoc: any = {
const jsonDoc: BitwardenUnEncryptedIndividualJsonExport = {
encrypted: false,
folders: [],
items: [],
@ -193,7 +204,7 @@ export class VaultExportService implements VaultExportServiceAbstraction {
const encKeyValidation = await this.cryptoService.encrypt(Utils.newGuid());
const jsonDoc: any = {
const jsonDoc: BitwardenEncryptedIndividualJsonExport = {
encrypted: true,
encKeyValidation_DO_NOT_EDIT: encKeyValidation.encryptedString,
folders: [],
@ -269,14 +280,14 @@ export class VaultExportService implements VaultExportServiceAbstraction {
collectionsMap.set(c.id, c);
});
const exportCiphers: any[] = [];
const exportCiphers: BitwardenCsvOrgExportType[] = [];
decCiphers.forEach((c) => {
// only export logins and secure notes
if (c.type !== CipherType.Login && c.type !== CipherType.SecureNote) {
return;
}
const cipher: any = {};
const cipher = {} as BitwardenCsvOrgExportType;
cipher.collections = [];
if (c.collectionIds != null) {
cipher.collections = c.collectionIds
@ -289,7 +300,7 @@ export class VaultExportService implements VaultExportServiceAbstraction {
return papa.unparse(exportCiphers);
} else {
const jsonDoc: any = {
const jsonDoc: BitwardenUnEncryptedOrgJsonExport = {
encrypted: false,
collections: [],
items: [],
@ -317,20 +328,17 @@ export class VaultExportService implements VaultExportServiceAbstraction {
promises.push(
this.apiService.getCollections(organizationId).then((c) => {
const collectionPromises: any = [];
if (c != null && c.data != null && c.data.length > 0) {
c.data.forEach((r) => {
const collection = new Collection(new CollectionData(r as CollectionDetailsResponse));
collections.push(collection);
});
}
return Promise.all(collectionPromises);
})
);
promises.push(
this.apiService.getCiphersOrganization(organizationId).then((c) => {
const cipherPromises: any = [];
if (c != null && c.data != null && c.data.length > 0) {
c.data
.filter((item) => item.deletedDate === null)
@ -339,7 +347,6 @@ export class VaultExportService implements VaultExportServiceAbstraction {
ciphers.push(cipher);
});
}
return Promise.all(cipherPromises);
})
);
@ -348,7 +355,7 @@ export class VaultExportService implements VaultExportServiceAbstraction {
const orgKey = await this.cryptoService.getOrgKey(organizationId);
const encKeyValidation = await this.cryptoService.encrypt(Utils.newGuid(), orgKey);
const jsonDoc: any = {
const jsonDoc: BitwardenEncryptedOrgJsonExport = {
encrypted: true,
encKeyValidation_DO_NOT_EDIT: encKeyValidation.encryptedString,
collections: [],
@ -369,7 +376,7 @@ export class VaultExportService implements VaultExportServiceAbstraction {
return JSON.stringify(jsonDoc, null, " ");
}
private buildCommonCipher(cipher: any, c: CipherView) {
private buildCommonCipher(cipher: BitwardenCsvExportType, c: CipherView): BitwardenCsvExportType {
cipher.type = null;
cipher.name = c.name;
cipher.notes = c.notes;
@ -382,7 +389,7 @@ export class VaultExportService implements VaultExportServiceAbstraction {
cipher.login_totp = null;
if (c.fields) {
c.fields.forEach((f: any) => {
c.fields.forEach((f) => {
if (!cipher.fields) {
cipher.fields = "";
} else {

View File

@ -313,6 +313,9 @@ export abstract class BaseImporter {
if (cipher.fields != null && cipher.fields.length === 0) {
cipher.fields = null;
}
if (cipher.passwordHistory != null && cipher.passwordHistory.length === 0) {
cipher.passwordHistory = null;
}
}
protected processKvp(

View File

@ -6,13 +6,21 @@ import {
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
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 { BaseImporter } from "../base-importer";
import { Importer } from "../importer";
export class BitwardenJsonImporter extends BaseImporter implements Importer {
private results: any;
private result: ImportResult;
protected constructor(
@ -24,25 +32,27 @@ export class BitwardenJsonImporter extends BaseImporter implements Importer {
async parse(data: string): Promise<ImportResult> {
this.result = new ImportResult();
this.results = JSON.parse(data);
if (this.results == null || this.results.items == null) {
const results: BitwardenJsonExport = JSON.parse(data);
if (results == null || results.items == null) {
this.result.success = false;
return this.result;
}
if (this.results.encrypted) {
await this.parseEncrypted();
if (results.encrypted) {
await this.parseEncrypted(results as any);
} else {
this.parseDecrypted();
await this.parseDecrypted(results as any);
}
return this.result;
}
private async parseEncrypted() {
if (this.results.encKeyValidation_DO_NOT_EDIT != null) {
private async parseEncrypted(
results: BitwardenEncryptedIndividualJsonExport | BitwardenEncryptedOrgJsonExport
) {
if (results.encKeyValidation_DO_NOT_EDIT != null) {
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(
encKeyValidation,
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 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[]) {
for (const c of results.items) {
const cipher = CipherWithIdExport.toDomain(c);
// reset ids incase they were set for some reason
cipher.id = null;
@ -113,28 +104,14 @@ export class BitwardenJsonImporter extends BaseImporter implements Importer {
this.result.success = true;
}
private parseDecrypted() {
const groupingsMap = new Map<string, number>();
if (this.organization && this.results.collections != null) {
this.results.collections.forEach((c: CollectionWithIdExport) => {
const collection = CollectionWithIdExport.toView(c);
if (collection != null) {
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);
}
});
}
private async parseDecrypted(
results: BitwardenUnEncryptedIndividualJsonExport | BitwardenUnEncryptedOrgJsonExport
) {
const groupingsMap = this.organization
? await this.parseCollections(results as BitwardenUnEncryptedOrgJsonExport)
: await this.parseFolders(results as BitwardenUnEncryptedIndividualJsonExport);
this.results.items.forEach((c: CipherWithIdExport) => {
results.items.forEach((c) => {
const cipher = CipherWithIdExport.toView(c);
// reset ids incase they were set for some reason
cipher.id = null;
@ -168,4 +145,60 @@ export class BitwardenJsonImporter extends BaseImporter implements Importer {
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;
}
}

View File

@ -4,7 +4,7 @@ import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.se
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
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 { Importer } from "../importer";