mirror of
https://github.com/bitwarden/browser
synced 2026-02-27 01:53:23 +00:00
Merge remote-tracking branch 'origin/main' into playwright
This commit is contained in:
@@ -195,6 +195,8 @@ export class ImportChromeComponent implements OnInit, OnDestroy {
|
||||
return "Brave";
|
||||
} else if (format === "vivaldicsv") {
|
||||
return "Vivaldi";
|
||||
} else if (format === "arccsv") {
|
||||
return "Arc";
|
||||
}
|
||||
return "Chrome";
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
<ng-container bitDialogFooter>
|
||||
<button bitButton buttonType="primary" type="submit">
|
||||
<span>{{ "importData" | i18n }}</span>
|
||||
<span>{{ "importVerb" | i18n }}</span>
|
||||
</button>
|
||||
<button bitButton bitDialogClose buttonType="secondary" type="button">
|
||||
<span>{{ "cancel" | i18n }}</span>
|
||||
|
||||
@@ -29,15 +29,15 @@ import { combineLatestWith, filter, map, switchMap, takeUntil } from "rxjs/opera
|
||||
|
||||
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import {
|
||||
CollectionService,
|
||||
CollectionTypes,
|
||||
CollectionView,
|
||||
} from "@bitwarden/admin-console/common";
|
||||
import { CollectionService } from "@bitwarden/admin-console/common";
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import {
|
||||
CollectionView,
|
||||
CollectionTypes,
|
||||
} from "@bitwarden/common/admin-console/models/collections";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
|
||||
@@ -7,8 +7,8 @@ import { safeProvider, SafeProvider } from "@bitwarden/angular/platform/utils/sa
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { KeyGenerationService } from "@bitwarden/common/key-management/crypto";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { PinServiceAbstraction } from "@bitwarden/common/key-management/pin/pin.service.abstraction";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
@@ -84,7 +84,7 @@ export const ImporterProviders: SafeProvider[] = [
|
||||
CollectionService,
|
||||
KeyService,
|
||||
EncryptService,
|
||||
PinServiceAbstraction,
|
||||
KeyGenerationService,
|
||||
AccountService,
|
||||
RestrictedItemTypesService,
|
||||
],
|
||||
|
||||
139
libs/importer/src/importers/arc-csv-importer.spec.ts
Normal file
139
libs/importer/src/importers/arc-csv-importer.spec.ts
Normal file
@@ -0,0 +1,139 @@
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { LoginUriView } from "@bitwarden/common/vault/models/view/login-uri.view";
|
||||
import { LoginView } from "@bitwarden/common/vault/models/view/login.view";
|
||||
|
||||
import { ArcCsvImporter } from "./arc-csv-importer";
|
||||
import { data as missingNameAndUrlData } from "./spec-data/arc-csv/missing-name-and-url-data.csv";
|
||||
import { data as missingNameWithUrlData } from "./spec-data/arc-csv/missing-name-with-url-data.csv";
|
||||
import { data as passwordWithNoteData } from "./spec-data/arc-csv/password-with-note-data.csv";
|
||||
import { data as simplePasswordData } from "./spec-data/arc-csv/simple-password-data.csv";
|
||||
import { data as subdomainData } from "./spec-data/arc-csv/subdomain-data.csv";
|
||||
import { data as urlWithWwwData } from "./spec-data/arc-csv/url-with-www-data.csv";
|
||||
|
||||
const CipherData = [
|
||||
{
|
||||
title: "should parse password",
|
||||
csv: simplePasswordData,
|
||||
expected: Object.assign(new CipherView(), {
|
||||
name: "example.com",
|
||||
login: Object.assign(new LoginView(), {
|
||||
username: "user@example.com",
|
||||
password: "password123",
|
||||
uris: [
|
||||
Object.assign(new LoginUriView(), {
|
||||
uri: "https://example.com/",
|
||||
}),
|
||||
],
|
||||
}),
|
||||
notes: null,
|
||||
type: 1,
|
||||
}),
|
||||
},
|
||||
{
|
||||
title: "should parse password with note",
|
||||
csv: passwordWithNoteData,
|
||||
expected: Object.assign(new CipherView(), {
|
||||
name: "example.com",
|
||||
login: Object.assign(new LoginView(), {
|
||||
username: "user@example.com",
|
||||
password: "password123",
|
||||
uris: [
|
||||
Object.assign(new LoginUriView(), {
|
||||
uri: "https://example.com/",
|
||||
}),
|
||||
],
|
||||
}),
|
||||
notes: "This is a test note",
|
||||
type: 1,
|
||||
}),
|
||||
},
|
||||
{
|
||||
title: "should strip www. prefix from name",
|
||||
csv: urlWithWwwData,
|
||||
expected: Object.assign(new CipherView(), {
|
||||
name: "example.com",
|
||||
login: Object.assign(new LoginView(), {
|
||||
username: "user@example.com",
|
||||
password: "password123",
|
||||
uris: [
|
||||
Object.assign(new LoginUriView(), {
|
||||
uri: "https://www.example.com/",
|
||||
}),
|
||||
],
|
||||
}),
|
||||
notes: null,
|
||||
type: 1,
|
||||
}),
|
||||
},
|
||||
{
|
||||
title: "should extract name from URL when name is missing",
|
||||
csv: missingNameWithUrlData,
|
||||
expected: Object.assign(new CipherView(), {
|
||||
name: "example.com",
|
||||
login: Object.assign(new LoginView(), {
|
||||
username: "user@example.com",
|
||||
password: "password123",
|
||||
uris: [
|
||||
Object.assign(new LoginUriView(), {
|
||||
uri: "https://example.com/login",
|
||||
}),
|
||||
],
|
||||
}),
|
||||
notes: null,
|
||||
type: 1,
|
||||
}),
|
||||
},
|
||||
{
|
||||
title: "should use -- as name when both name and URL are missing",
|
||||
csv: missingNameAndUrlData,
|
||||
expected: Object.assign(new CipherView(), {
|
||||
name: "--",
|
||||
login: Object.assign(new LoginView(), {
|
||||
username: null,
|
||||
password: "password123",
|
||||
uris: null,
|
||||
}),
|
||||
notes: null,
|
||||
type: 1,
|
||||
}),
|
||||
},
|
||||
{
|
||||
title: "should preserve subdomain in name",
|
||||
csv: subdomainData,
|
||||
expected: Object.assign(new CipherView(), {
|
||||
name: "login.example.com",
|
||||
login: Object.assign(new LoginView(), {
|
||||
username: "user@example.com",
|
||||
password: "password123",
|
||||
uris: [
|
||||
Object.assign(new LoginUriView(), {
|
||||
uri: "https://login.example.com/auth",
|
||||
}),
|
||||
],
|
||||
}),
|
||||
notes: null,
|
||||
type: 1,
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
||||
describe("Arc CSV Importer", () => {
|
||||
CipherData.forEach((data) => {
|
||||
it(data.title, async () => {
|
||||
jest.useFakeTimers().setSystemTime(data.expected.creationDate);
|
||||
const importer = new ArcCsvImporter();
|
||||
const result = await importer.parse(data.csv);
|
||||
expect(result != null).toBe(true);
|
||||
expect(result.ciphers.length).toBeGreaterThan(0);
|
||||
|
||||
const cipher = result.ciphers.shift();
|
||||
let property: keyof typeof data.expected;
|
||||
for (property in data.expected) {
|
||||
if (Object.prototype.hasOwnProperty.call(data.expected, property)) {
|
||||
expect(Object.prototype.hasOwnProperty.call(cipher, property)).toBe(true);
|
||||
expect(cipher[property]).toEqual(data.expected[property]);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
30
libs/importer/src/importers/arc-csv-importer.ts
Normal file
30
libs/importer/src/importers/arc-csv-importer.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { ImportResult } from "../models/import-result";
|
||||
|
||||
import { BaseImporter } from "./base-importer";
|
||||
import { Importer } from "./importer";
|
||||
|
||||
export class ArcCsvImporter extends BaseImporter implements Importer {
|
||||
parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
const results = this.parseCsv(data, true);
|
||||
if (results == null) {
|
||||
result.success = false;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
results.forEach((value) => {
|
||||
const cipher = this.initLoginCipher();
|
||||
const url = this.getValueOrDefault(value.url);
|
||||
cipher.name = this.getValueOrDefault(this.nameFromUrl(url) ?? "", "--");
|
||||
cipher.login.username = this.getValueOrDefault(value.username);
|
||||
cipher.login.password = this.getValueOrDefault(value.password);
|
||||
cipher.login.uris = this.makeUriArray(value.url);
|
||||
cipher.notes = this.getValueOrDefault(value.note);
|
||||
this.cleanupCipher(cipher);
|
||||
result.ciphers.push(cipher);
|
||||
});
|
||||
|
||||
result.success = true;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,7 @@
|
||||
// @ts-strict-ignore
|
||||
import * as papa from "papaparse";
|
||||
|
||||
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Collection, CollectionView } from "@bitwarden/admin-console/common";
|
||||
import { CollectionView, Collection } from "@bitwarden/common/admin-console/models/collections";
|
||||
import { normalizeExpiryYearFormat } from "@bitwarden/common/autofill/utils";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
@@ -319,8 +317,6 @@ export abstract class BaseImporter {
|
||||
}
|
||||
if (this.isNullOrWhitespace(cipher.notes)) {
|
||||
cipher.notes = null;
|
||||
} else {
|
||||
cipher.notes = cipher.notes.trim();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { filter, firstValueFrom } from "rxjs";
|
||||
|
||||
import { Collection } from "@bitwarden/common/admin-console/models/collections";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string";
|
||||
import {
|
||||
CipherWithIdExport,
|
||||
CollectionWithIdExport,
|
||||
FolderWithIdExport,
|
||||
} from "@bitwarden/common/models/export";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { OrgKey, UserKey } from "@bitwarden/common/types/key";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
|
||||
import { KeyService } from "@bitwarden/key-management";
|
||||
import { UserId } from "@bitwarden/user-core";
|
||||
import {
|
||||
BitwardenEncryptedIndividualJsonExport,
|
||||
BitwardenEncryptedJsonExport,
|
||||
BitwardenEncryptedOrgJsonExport,
|
||||
BitwardenJsonExport,
|
||||
BitwardenPasswordProtectedFileFormat,
|
||||
isOrgEncrypted,
|
||||
isPasswordProtected,
|
||||
isUnencrypted,
|
||||
} from "@bitwarden/vault-export-core";
|
||||
|
||||
import { ImportResult } from "../../models/import-result";
|
||||
import { Importer } from "../importer";
|
||||
|
||||
import { BitwardenJsonImporter } from "./bitwarden-json-importer";
|
||||
|
||||
export class BitwardenEncryptedJsonImporter extends BitwardenJsonImporter implements Importer {
|
||||
constructor(
|
||||
protected keyService: KeyService,
|
||||
protected encryptService: EncryptService,
|
||||
protected i18nService: I18nService,
|
||||
private cipherService: CipherService,
|
||||
private accountService: AccountService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async parse(data: string): Promise<ImportResult> {
|
||||
const results: BitwardenPasswordProtectedFileFormat | BitwardenJsonExport = JSON.parse(data);
|
||||
|
||||
if (isPasswordProtected(results)) {
|
||||
throw new Error(
|
||||
"Data is password-protected. Use BitwardenPasswordProtectedImporter instead.",
|
||||
);
|
||||
}
|
||||
|
||||
if (results == null || results.items == null) {
|
||||
const result = new ImportResult();
|
||||
result.success = false;
|
||||
return result;
|
||||
}
|
||||
|
||||
if (isUnencrypted(results)) {
|
||||
return super.parse(data);
|
||||
}
|
||||
|
||||
return await this.parseEncrypted(results);
|
||||
}
|
||||
|
||||
private async parseEncrypted(data: BitwardenEncryptedJsonExport): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
const account = await firstValueFrom(this.accountService.activeAccount$);
|
||||
|
||||
if (this.isNullOrWhitespace(data.encKeyValidation_DO_NOT_EDIT)) {
|
||||
result.success = false;
|
||||
result.errorMessage = this.i18nService.t("importEncKeyError");
|
||||
return result;
|
||||
}
|
||||
|
||||
const orgKeys = await firstValueFrom(this.keyService.orgKeys$(account.id));
|
||||
let keyForDecryption: OrgKey | UserKey | null | undefined = orgKeys?.[this.organizationId];
|
||||
if (!keyForDecryption) {
|
||||
keyForDecryption = await firstValueFrom(this.keyService.userKey$(account.id));
|
||||
}
|
||||
|
||||
if (!keyForDecryption) {
|
||||
result.success = false;
|
||||
result.errorMessage = this.i18nService.t("importEncKeyError");
|
||||
return result;
|
||||
}
|
||||
const encKeyValidation = new EncString(data.encKeyValidation_DO_NOT_EDIT);
|
||||
try {
|
||||
await this.encryptService.decryptString(encKeyValidation, keyForDecryption);
|
||||
} catch {
|
||||
result.success = false;
|
||||
result.errorMessage = this.i18nService.t("importEncKeyError");
|
||||
return result;
|
||||
}
|
||||
|
||||
let groupingsMap: Map<string, number> | null = null;
|
||||
if (isOrgEncrypted(data)) {
|
||||
groupingsMap = await this.parseEncryptedCollections(account.id, data, result);
|
||||
} else {
|
||||
groupingsMap = await this.parseEncryptedFolders(account.id, data, result);
|
||||
}
|
||||
|
||||
for (const c of data.items) {
|
||||
const cipher = CipherWithIdExport.toDomain(c);
|
||||
// reset ids in case they were set for some reason
|
||||
cipher.id = null;
|
||||
cipher.organizationId = this.organizationId;
|
||||
cipher.collectionIds = null;
|
||||
|
||||
// make sure password history is limited
|
||||
if (cipher.passwordHistory != null && cipher.passwordHistory.length > 5) {
|
||||
cipher.passwordHistory = cipher.passwordHistory.slice(0, 5);
|
||||
}
|
||||
|
||||
if (!this.organization && c.folderId != null && groupingsMap.has(c.folderId)) {
|
||||
result.folderRelationships.push([result.ciphers.length, groupingsMap.get(c.folderId)]);
|
||||
} else if (this.organization && c.collectionIds != null) {
|
||||
c.collectionIds.forEach((cId) => {
|
||||
if (groupingsMap.has(cId)) {
|
||||
result.collectionRelationships.push([result.ciphers.length, groupingsMap.get(cId)]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const view = await this.cipherService.decrypt(cipher, account.id);
|
||||
this.cleanupCipher(view);
|
||||
result.ciphers.push(view);
|
||||
}
|
||||
|
||||
result.success = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
private async parseEncryptedFolders(
|
||||
userId: UserId,
|
||||
data: BitwardenEncryptedIndividualJsonExport,
|
||||
importResult: ImportResult,
|
||||
): Promise<Map<string, number>> {
|
||||
const groupingsMap = new Map<string, number>();
|
||||
|
||||
if (data.folders == null) {
|
||||
return groupingsMap;
|
||||
}
|
||||
|
||||
const userKey = await firstValueFrom(this.keyService.userKey$(userId));
|
||||
|
||||
for (const f of data.folders) {
|
||||
let folderView: FolderView;
|
||||
const folder = FolderWithIdExport.toDomain(f);
|
||||
if (folder != null) {
|
||||
folderView = await folder.decrypt(userKey);
|
||||
}
|
||||
|
||||
if (folderView != null) {
|
||||
groupingsMap.set(f.id, importResult.folders.length);
|
||||
importResult.folders.push(folderView);
|
||||
}
|
||||
}
|
||||
return groupingsMap;
|
||||
}
|
||||
|
||||
private async parseEncryptedCollections(
|
||||
userId: UserId,
|
||||
data: BitwardenEncryptedOrgJsonExport,
|
||||
importResult: ImportResult,
|
||||
): Promise<Map<string, number>> {
|
||||
const groupingsMap = new Map<string, number>();
|
||||
if (data.collections == null) {
|
||||
return groupingsMap;
|
||||
}
|
||||
|
||||
const orgKeys = await firstValueFrom(
|
||||
this.keyService.orgKeys$(userId).pipe(filter((orgKeys) => orgKeys != null)),
|
||||
);
|
||||
|
||||
for (const c of data.collections) {
|
||||
const collection = CollectionWithIdExport.toDomain(
|
||||
c,
|
||||
new Collection({
|
||||
id: c.id,
|
||||
name: new EncString(c.name),
|
||||
organizationId: this.organizationId,
|
||||
}),
|
||||
);
|
||||
|
||||
const orgKey = orgKeys[c.organizationId];
|
||||
const collectionView = await collection.decrypt(orgKey, this.encryptService);
|
||||
|
||||
if (collectionView != null) {
|
||||
groupingsMap.set(c.id, importResult.collections.length);
|
||||
importResult.collections.push(collectionView);
|
||||
}
|
||||
}
|
||||
return groupingsMap;
|
||||
}
|
||||
}
|
||||
@@ -1,31 +1,17 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { filter, firstValueFrom } from "rxjs";
|
||||
|
||||
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Collection, CollectionView } from "@bitwarden/admin-console/common";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string";
|
||||
import {
|
||||
CipherWithIdExport,
|
||||
CollectionWithIdExport,
|
||||
FolderWithIdExport,
|
||||
} from "@bitwarden/common/models/export";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
|
||||
import { KeyService } from "@bitwarden/key-management";
|
||||
import {
|
||||
BitwardenEncryptedIndividualJsonExport,
|
||||
BitwardenEncryptedOrgJsonExport,
|
||||
BitwardenJsonExport,
|
||||
BitwardenUnEncryptedIndividualJsonExport,
|
||||
BitwardenUnEncryptedJsonExport,
|
||||
BitwardenUnEncryptedOrgJsonExport,
|
||||
isOrgUnEncrypted,
|
||||
isUnencrypted,
|
||||
} from "@bitwarden/vault-export-core";
|
||||
|
||||
import { ImportResult } from "../../models/import-result";
|
||||
@@ -33,104 +19,30 @@ import { BaseImporter } from "../base-importer";
|
||||
import { Importer } from "../importer";
|
||||
|
||||
export class BitwardenJsonImporter extends BaseImporter implements Importer {
|
||||
private result: ImportResult;
|
||||
|
||||
protected constructor(
|
||||
protected keyService: KeyService,
|
||||
protected encryptService: EncryptService,
|
||||
protected i18nService: I18nService,
|
||||
protected cipherService: CipherService,
|
||||
protected accountService: AccountService,
|
||||
) {
|
||||
protected constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
async parse(data: string): Promise<ImportResult> {
|
||||
this.result = new ImportResult();
|
||||
const results: BitwardenJsonExport = JSON.parse(data);
|
||||
if (results == null || results.items == null) {
|
||||
this.result.success = false;
|
||||
return this.result;
|
||||
const result = new ImportResult();
|
||||
result.success = false;
|
||||
return result;
|
||||
}
|
||||
|
||||
if (results.encrypted) {
|
||||
await this.parseEncrypted(results as any);
|
||||
} else {
|
||||
await this.parseDecrypted(results as any);
|
||||
if (!isUnencrypted(results)) {
|
||||
throw new Error("Data is encrypted. Use BitwardenEncryptedJsonImporter instead.");
|
||||
}
|
||||
|
||||
return this.result;
|
||||
return await this.parseDecrypted(results);
|
||||
}
|
||||
|
||||
private async parseEncrypted(
|
||||
results: BitwardenEncryptedIndividualJsonExport | BitwardenEncryptedOrgJsonExport,
|
||||
) {
|
||||
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
|
||||
private async parseDecrypted(results: BitwardenUnEncryptedJsonExport): Promise<ImportResult> {
|
||||
const importResult = new ImportResult();
|
||||
|
||||
if (results.encKeyValidation_DO_NOT_EDIT != null) {
|
||||
const orgKeys = await firstValueFrom(this.keyService.orgKeys$(userId));
|
||||
let keyForDecryption: SymmetricCryptoKey = orgKeys?.[this.organizationId];
|
||||
if (keyForDecryption == null) {
|
||||
keyForDecryption = await firstValueFrom(this.keyService.userKey$(userId));
|
||||
}
|
||||
const encKeyValidation = new EncString(results.encKeyValidation_DO_NOT_EDIT);
|
||||
try {
|
||||
await this.encryptService.decryptString(encKeyValidation, keyForDecryption);
|
||||
} catch {
|
||||
this.result.success = false;
|
||||
this.result.errorMessage = this.i18nService.t("importEncKeyError");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const groupingsMap = this.organization
|
||||
? await this.parseCollections(userId, results as BitwardenEncryptedOrgJsonExport)
|
||||
: await this.parseFolders(results as BitwardenEncryptedIndividualJsonExport);
|
||||
|
||||
for (const c of results.items) {
|
||||
const cipher = CipherWithIdExport.toDomain(c);
|
||||
// reset ids in case they were set for some reason
|
||||
cipher.id = null;
|
||||
cipher.organizationId = this.organizationId;
|
||||
cipher.collectionIds = null;
|
||||
|
||||
// make sure password history is limited
|
||||
if (cipher.passwordHistory != null && cipher.passwordHistory.length > 5) {
|
||||
cipher.passwordHistory = cipher.passwordHistory.slice(0, 5);
|
||||
}
|
||||
|
||||
if (!this.organization && c.folderId != null && groupingsMap.has(c.folderId)) {
|
||||
this.result.folderRelationships.push([
|
||||
this.result.ciphers.length,
|
||||
groupingsMap.get(c.folderId),
|
||||
]);
|
||||
} else if (this.organization && c.collectionIds != null) {
|
||||
c.collectionIds.forEach((cId) => {
|
||||
if (groupingsMap.has(cId)) {
|
||||
this.result.collectionRelationships.push([
|
||||
this.result.ciphers.length,
|
||||
groupingsMap.get(cId),
|
||||
]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const view = await this.cipherService.decrypt(cipher, userId);
|
||||
this.cleanupCipher(view);
|
||||
this.result.ciphers.push(view);
|
||||
}
|
||||
|
||||
this.result.success = true;
|
||||
}
|
||||
|
||||
private async parseDecrypted(
|
||||
results: BitwardenUnEncryptedIndividualJsonExport | BitwardenUnEncryptedOrgJsonExport,
|
||||
) {
|
||||
const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
|
||||
|
||||
const groupingsMap = this.organization
|
||||
? await this.parseCollections(userId, results as BitwardenUnEncryptedOrgJsonExport)
|
||||
: await this.parseFolders(results as BitwardenUnEncryptedIndividualJsonExport);
|
||||
const groupingsMap = isOrgUnEncrypted(results)
|
||||
? await this.parseCollections(results, importResult)
|
||||
: await this.parseFolders(results, importResult);
|
||||
|
||||
results.items.forEach((c) => {
|
||||
const cipher = CipherWithIdExport.toView(c);
|
||||
@@ -145,15 +57,15 @@ export class BitwardenJsonImporter extends BaseImporter implements Importer {
|
||||
}
|
||||
|
||||
if (!this.organization && c.folderId != null && groupingsMap.has(c.folderId)) {
|
||||
this.result.folderRelationships.push([
|
||||
this.result.ciphers.length,
|
||||
importResult.folderRelationships.push([
|
||||
importResult.ciphers.length,
|
||||
groupingsMap.get(c.folderId),
|
||||
]);
|
||||
} else if (this.organization && c.collectionIds != null) {
|
||||
c.collectionIds.forEach((cId) => {
|
||||
if (groupingsMap.has(cId)) {
|
||||
this.result.collectionRelationships.push([
|
||||
this.result.ciphers.length,
|
||||
importResult.collectionRelationships.push([
|
||||
importResult.ciphers.length,
|
||||
groupingsMap.get(cId),
|
||||
]);
|
||||
}
|
||||
@@ -161,76 +73,48 @@ export class BitwardenJsonImporter extends BaseImporter implements Importer {
|
||||
}
|
||||
|
||||
this.cleanupCipher(cipher);
|
||||
this.result.ciphers.push(cipher);
|
||||
importResult.ciphers.push(cipher);
|
||||
});
|
||||
|
||||
this.result.success = true;
|
||||
importResult.success = true;
|
||||
return importResult;
|
||||
}
|
||||
|
||||
private async parseFolders(
|
||||
data: BitwardenUnEncryptedIndividualJsonExport | BitwardenEncryptedIndividualJsonExport,
|
||||
): Promise<Map<string, number>> | null {
|
||||
data: BitwardenUnEncryptedIndividualJsonExport,
|
||||
importResult: ImportResult,
|
||||
): Promise<Map<string, number>> {
|
||||
const groupingsMap = new Map<string, number>();
|
||||
if (data.folders == null) {
|
||||
return null;
|
||||
return groupingsMap;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
const folderView = FolderWithIdExport.toView(f);
|
||||
if (folderView != null) {
|
||||
groupingsMap.set(f.id, this.result.folders.length);
|
||||
this.result.folders.push(folderView);
|
||||
groupingsMap.set(f.id, importResult.folders.length);
|
||||
importResult.folders.push(folderView);
|
||||
}
|
||||
}
|
||||
return groupingsMap;
|
||||
}
|
||||
|
||||
private async parseCollections(
|
||||
userId: UserId,
|
||||
data: BitwardenUnEncryptedOrgJsonExport | BitwardenEncryptedOrgJsonExport,
|
||||
): Promise<Map<string, number>> | null {
|
||||
data: BitwardenUnEncryptedOrgJsonExport,
|
||||
importResult: ImportResult,
|
||||
): Promise<Map<string, number>> {
|
||||
const groupingsMap = new Map<string, number>();
|
||||
if (data.collections == null) {
|
||||
return null;
|
||||
return groupingsMap;
|
||||
}
|
||||
|
||||
const orgKeys = await firstValueFrom(
|
||||
this.keyService.orgKeys$(userId).pipe(filter((orgKeys) => orgKeys != null)),
|
||||
);
|
||||
|
||||
const groupingsMap = new Map<string, number>();
|
||||
|
||||
for (const c of data.collections) {
|
||||
let collectionView: CollectionView;
|
||||
if (data.encrypted) {
|
||||
const collection = CollectionWithIdExport.toDomain(
|
||||
c,
|
||||
new Collection({
|
||||
id: c.id,
|
||||
name: new EncString(c.name),
|
||||
organizationId: this.organizationId,
|
||||
}),
|
||||
);
|
||||
|
||||
const orgKey = orgKeys[c.organizationId];
|
||||
collectionView = await collection.decrypt(orgKey, this.encryptService);
|
||||
} else {
|
||||
collectionView = CollectionWithIdExport.toView(c);
|
||||
collectionView.organizationId = null;
|
||||
}
|
||||
const collectionView = CollectionWithIdExport.toView(c);
|
||||
collectionView.organizationId = null;
|
||||
|
||||
if (collectionView != null) {
|
||||
groupingsMap.set(c.id, this.result.collections.length);
|
||||
this.result.collections.push(collectionView);
|
||||
groupingsMap.set(c.id, importResult.collections.length);
|
||||
importResult.collections.push(collectionView);
|
||||
}
|
||||
}
|
||||
return groupingsMap;
|
||||
|
||||
@@ -2,10 +2,11 @@ import { mock, MockProxy } from "jest-mock-extended";
|
||||
import { of } from "rxjs";
|
||||
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { KeyGenerationService } from "@bitwarden/common/key-management/crypto";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { PinServiceAbstraction } from "@bitwarden/common/key-management/pin/pin.service.abstraction";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { mockAccountInfoWith } from "@bitwarden/common/spec";
|
||||
import { emptyGuid, OrganizationId } from "@bitwarden/common/types/guid";
|
||||
import { OrgKey, UserKey } from "@bitwarden/common/types/key";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
@@ -15,6 +16,7 @@ import { UserId } from "@bitwarden/user-core";
|
||||
import { emptyAccountEncrypted } from "../spec-data/bitwarden-json/account-encrypted.json";
|
||||
import { emptyUnencryptedExport } from "../spec-data/bitwarden-json/unencrypted.json";
|
||||
|
||||
import { BitwardenEncryptedJsonImporter } from "./bitwarden-encrypted-json-importer";
|
||||
import { BitwardenJsonImporter } from "./bitwarden-json-importer";
|
||||
import { BitwardenPasswordProtectedImporter } from "./bitwarden-password-protected-importer";
|
||||
|
||||
@@ -24,7 +26,7 @@ describe("BitwardenPasswordProtectedImporter", () => {
|
||||
let encryptService: MockProxy<EncryptService>;
|
||||
let i18nService: MockProxy<I18nService>;
|
||||
let cipherService: MockProxy<CipherService>;
|
||||
let pinService: MockProxy<PinServiceAbstraction>;
|
||||
let keyGenerationService: MockProxy<KeyGenerationService>;
|
||||
let accountService: MockProxy<AccountService>;
|
||||
const password = Utils.newGuid();
|
||||
const promptForPassword_callback = async () => {
|
||||
@@ -36,14 +38,15 @@ describe("BitwardenPasswordProtectedImporter", () => {
|
||||
encryptService = mock<EncryptService>();
|
||||
i18nService = mock<I18nService>();
|
||||
cipherService = mock<CipherService>();
|
||||
pinService = mock<PinServiceAbstraction>();
|
||||
keyGenerationService = mock<KeyGenerationService>();
|
||||
accountService = mock<AccountService>();
|
||||
|
||||
accountService.activeAccount$ = of({
|
||||
id: emptyGuid as UserId,
|
||||
email: "test@example.com",
|
||||
emailVerified: true,
|
||||
name: "Test User",
|
||||
...mockAccountInfoWith({
|
||||
email: "test@example.com",
|
||||
name: "Test User",
|
||||
}),
|
||||
});
|
||||
|
||||
const mockOrgId = emptyGuid as OrganizationId;
|
||||
@@ -71,7 +74,7 @@ describe("BitwardenPasswordProtectedImporter", () => {
|
||||
encryptService,
|
||||
i18nService,
|
||||
cipherService,
|
||||
pinService,
|
||||
keyGenerationService,
|
||||
accountService,
|
||||
promptForPassword_callback,
|
||||
);
|
||||
@@ -90,30 +93,33 @@ describe("BitwardenPasswordProtectedImporter", () => {
|
||||
|
||||
describe("Account encrypted", () => {
|
||||
beforeAll(() => {
|
||||
jest.spyOn(BitwardenJsonImporter.prototype, "parse");
|
||||
jest.spyOn(BitwardenEncryptedJsonImporter.prototype, "parse");
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
accountService.activeAccount$ = of({
|
||||
id: emptyGuid as UserId,
|
||||
email: "test@example.com",
|
||||
emailVerified: true,
|
||||
name: "Test User",
|
||||
...mockAccountInfoWith({
|
||||
email: "test@example.com",
|
||||
name: "Test User",
|
||||
}),
|
||||
});
|
||||
importer = new BitwardenPasswordProtectedImporter(
|
||||
keyService,
|
||||
encryptService,
|
||||
i18nService,
|
||||
cipherService,
|
||||
pinService,
|
||||
keyGenerationService,
|
||||
accountService,
|
||||
promptForPassword_callback,
|
||||
);
|
||||
});
|
||||
|
||||
it("Should call BitwardenJsonImporter", async () => {
|
||||
expect((await importer.parse(emptyAccountEncrypted)).success).toEqual(true);
|
||||
expect(BitwardenJsonImporter.prototype.parse).toHaveBeenCalledWith(emptyAccountEncrypted);
|
||||
it("Should call BitwardenEncryptedJsonImporter", async () => {
|
||||
expect((await importer.parse(emptyAccountEncrypted)).success).toEqual(false);
|
||||
expect(BitwardenEncryptedJsonImporter.prototype.parse).toHaveBeenCalledWith(
|
||||
emptyAccountEncrypted,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { KeyGenerationService } from "@bitwarden/common/key-management/crypto";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string";
|
||||
import { PinServiceAbstraction } from "@bitwarden/common/key-management/pin/pin.service.abstraction";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
@@ -14,14 +14,21 @@ import {
|
||||
KeyService,
|
||||
KdfType,
|
||||
} from "@bitwarden/key-management";
|
||||
import { BitwardenPasswordProtectedFileFormat } from "@bitwarden/vault-export-core";
|
||||
import {
|
||||
BitwardenJsonExport,
|
||||
BitwardenPasswordProtectedFileFormat,
|
||||
isPasswordProtected,
|
||||
} from "@bitwarden/vault-export-core";
|
||||
|
||||
import { ImportResult } from "../../models/import-result";
|
||||
import { Importer } from "../importer";
|
||||
|
||||
import { BitwardenJsonImporter } from "./bitwarden-json-importer";
|
||||
import { BitwardenEncryptedJsonImporter } from "./bitwarden-encrypted-json-importer";
|
||||
|
||||
export class BitwardenPasswordProtectedImporter extends BitwardenJsonImporter implements Importer {
|
||||
export class BitwardenPasswordProtectedImporter
|
||||
extends BitwardenEncryptedJsonImporter
|
||||
implements Importer
|
||||
{
|
||||
private key: SymmetricCryptoKey;
|
||||
|
||||
constructor(
|
||||
@@ -29,7 +36,7 @@ export class BitwardenPasswordProtectedImporter extends BitwardenJsonImporter im
|
||||
encryptService: EncryptService,
|
||||
i18nService: I18nService,
|
||||
cipherService: CipherService,
|
||||
private pinService: PinServiceAbstraction,
|
||||
private keyGenerationService: KeyGenerationService,
|
||||
accountService: AccountService,
|
||||
private promptForPassword_callback: () => Promise<string>,
|
||||
) {
|
||||
@@ -38,20 +45,14 @@ export class BitwardenPasswordProtectedImporter extends BitwardenJsonImporter im
|
||||
|
||||
async parse(data: string): Promise<ImportResult> {
|
||||
const result = new ImportResult();
|
||||
const parsedData: BitwardenPasswordProtectedFileFormat = JSON.parse(data);
|
||||
const parsedData: BitwardenPasswordProtectedFileFormat | BitwardenJsonExport = JSON.parse(data);
|
||||
|
||||
if (!parsedData) {
|
||||
result.success = false;
|
||||
return result;
|
||||
}
|
||||
|
||||
// File is unencrypted
|
||||
if (!parsedData?.encrypted) {
|
||||
return await super.parse(data);
|
||||
}
|
||||
|
||||
// File is account-encrypted
|
||||
if (!parsedData?.passwordProtected) {
|
||||
if (!isPasswordProtected(parsedData)) {
|
||||
return await super.parse(data);
|
||||
}
|
||||
|
||||
@@ -86,7 +87,7 @@ export class BitwardenPasswordProtectedImporter extends BitwardenJsonImporter im
|
||||
? new PBKDF2KdfConfig(jdoc.kdfIterations)
|
||||
: new Argon2KdfConfig(jdoc.kdfIterations, jdoc.kdfMemory, jdoc.kdfParallelism);
|
||||
|
||||
this.key = await this.pinService.makePinKey(password, jdoc.salt, kdfConfig);
|
||||
this.key = await this.keyGenerationService.deriveVaultExportKey(password, jdoc.salt, kdfConfig);
|
||||
|
||||
const encKeyValidation = new EncString(jdoc.encKeyValidation_DO_NOT_EDIT);
|
||||
|
||||
|
||||
@@ -64,7 +64,10 @@ export class EnpassJsonImporter extends BaseImporter implements Importer {
|
||||
}
|
||||
}
|
||||
|
||||
cipher.notes += "\n" + this.getValueOrDefault(item.note, "");
|
||||
const note = this.getValueOrDefault(item.note, "");
|
||||
if (note) {
|
||||
cipher.notes = note.trimEnd();
|
||||
}
|
||||
this.convertToNoteIfNeeded(cipher);
|
||||
this.cleanupCipher(cipher);
|
||||
result.ciphers.push(cipher);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export { ArcCsvImporter } from "./arc-csv-importer";
|
||||
export { AscendoCsvImporter } from "./ascendo-csv-importer";
|
||||
export { AvastCsvImporter, AvastJsonImporter } from "./avast";
|
||||
export { AviraCsvImporter } from "./avira-csv-importer";
|
||||
|
||||
@@ -21,7 +21,7 @@ export class KeeperCsvImporter extends BaseImporter implements Importer {
|
||||
|
||||
const notes = this.getValueOrDefault(value[5]);
|
||||
if (notes) {
|
||||
cipher.notes = `${notes}\n`;
|
||||
cipher.notes = notes.trimEnd();
|
||||
}
|
||||
|
||||
cipher.name = this.getValueOrDefault(value[1], "--");
|
||||
|
||||
@@ -50,7 +50,7 @@ export class MykiCsvImporter extends BaseImporter implements Importer {
|
||||
results.forEach((value) => {
|
||||
const cipher = this.initLoginCipher();
|
||||
cipher.name = this.getValueOrDefault(value.nickname, "--");
|
||||
cipher.notes = this.getValueOrDefault(value.additionalInfo);
|
||||
cipher.notes = this.getValueOrDefault(value.additionalInfo, "").trimEnd();
|
||||
|
||||
if (value.url !== undefined) {
|
||||
// Accounts
|
||||
@@ -132,7 +132,7 @@ export class MykiCsvImporter extends BaseImporter implements Importer {
|
||||
cipher.secureNote = new SecureNoteView();
|
||||
cipher.type = CipherType.SecureNote;
|
||||
cipher.secureNote.type = SecureNoteType.Generic;
|
||||
cipher.notes = this.getValueOrDefault(value.content);
|
||||
cipher.notes = this.getValueOrDefault(value.content, "").trimEnd();
|
||||
|
||||
this.importUnmappedFields(cipher, value, _mappedUserNoteColumns);
|
||||
} else {
|
||||
|
||||
@@ -35,7 +35,7 @@ export class NetwrixPasswordSecureCsvImporter extends BaseImporter implements Im
|
||||
|
||||
const notes = this.getValueOrDefault(row.Informationen);
|
||||
if (notes) {
|
||||
cipher.notes = `${notes}\n`;
|
||||
cipher.notes = notes.trimEnd();
|
||||
}
|
||||
|
||||
cipher.name = this.getValueOrDefault(row.Beschreibung, "--");
|
||||
|
||||
@@ -97,7 +97,7 @@ export class OnePassword1PuxImporter extends BaseImporter implements Importer {
|
||||
this.processSections(category, item.details.sections, cipher);
|
||||
|
||||
if (!this.isNullOrWhitespace(item.details.notesPlain)) {
|
||||
cipher.notes = item.details.notesPlain.split(this.newLineRegex).join("\n") + "\n";
|
||||
cipher.notes = item.details.notesPlain.split(this.newLineRegex).join("\n").trimEnd();
|
||||
}
|
||||
|
||||
this.convertToNoteIfNeeded(cipher);
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { CollectionView } from "@bitwarden/admin-console/common";
|
||||
import { CollectionView } from "@bitwarden/common/admin-console/models/collections";
|
||||
|
||||
import { ImportResult } from "../models/import-result";
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { CollectionView } from "@bitwarden/admin-console/common";
|
||||
import { CollectionView } from "@bitwarden/common/admin-console/models/collections";
|
||||
|
||||
import { ImportResult } from "../models/import-result";
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { CollectionView } from "@bitwarden/admin-console/common";
|
||||
import { CollectionView } from "@bitwarden/common/admin-console/models/collections";
|
||||
import { OrganizationId } from "@bitwarden/common/types/guid";
|
||||
import { FieldType, SecureNoteType } from "@bitwarden/common/vault/enums";
|
||||
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
export const data = `name,url,username,password,note
|
||||
,,,password123,`;
|
||||
@@ -0,0 +1,2 @@
|
||||
export const data = `name,url,username,password,note
|
||||
,https://example.com/login,user@example.com,password123,`;
|
||||
@@ -0,0 +1,2 @@
|
||||
export const data = `name,url,username,password,note
|
||||
example.com,https://example.com/,user@example.com,password123,This is a test note`;
|
||||
@@ -0,0 +1,2 @@
|
||||
export const data = `name,url,username,password,note
|
||||
example.com,https://example.com/,user@example.com,password123,`;
|
||||
@@ -0,0 +1,2 @@
|
||||
export const data = `name,url,username,password,note
|
||||
login.example.com,https://login.example.com/auth,user@example.com,password123,`;
|
||||
@@ -0,0 +1,2 @@
|
||||
export const data = `name,url,username,password,note
|
||||
www.example.com,https://www.example.com/,user@example.com,password123,`;
|
||||
@@ -46,6 +46,7 @@ export const regularImportOptions = [
|
||||
{ id: "ascendocsv", name: "Ascendo DataVault (csv)" },
|
||||
{ id: "meldiumcsv", name: "Meldium (csv)" },
|
||||
{ id: "passkeepcsv", name: "PassKeep (csv)" },
|
||||
{ id: "arccsv", name: "Arc" },
|
||||
{ id: "edgecsv", name: "Edge" },
|
||||
{ id: "operacsv", name: "Opera" },
|
||||
{ id: "vivaldicsv", name: "Vivaldi" },
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { CollectionView } from "@bitwarden/admin-console/common";
|
||||
import { CollectionView } from "@bitwarden/common/admin-console/models/collections";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { CollectionView } from "@bitwarden/admin-console/common";
|
||||
import { CollectionView } from "@bitwarden/common/admin-console/models/collections";
|
||||
import { UserId } from "@bitwarden/user-core";
|
||||
|
||||
export abstract class ImportCollectionServiceAbstraction {
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
|
||||
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { CollectionView } from "@bitwarden/admin-console/common";
|
||||
import { CollectionView } from "@bitwarden/common/admin-console/models/collections";
|
||||
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
|
||||
|
||||
import { Importer } from "../importers/importer";
|
||||
|
||||
@@ -2,14 +2,14 @@ import { mock, MockProxy } from "jest-mock-extended";
|
||||
|
||||
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { CollectionService } from "@bitwarden/admin-console/common";
|
||||
import {
|
||||
CollectionService,
|
||||
CollectionTypes,
|
||||
CollectionView,
|
||||
} from "@bitwarden/admin-console/common";
|
||||
CollectionTypes,
|
||||
} from "@bitwarden/common/admin-console/models/collections";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { KeyGenerationService } from "@bitwarden/common/key-management/crypto";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { PinServiceAbstraction } from "@bitwarden/common/key-management/pin/pin.service.abstraction";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { CollectionId, OrganizationId } from "@bitwarden/common/types/guid";
|
||||
@@ -36,7 +36,7 @@ describe("ImportService", () => {
|
||||
let collectionService: MockProxy<CollectionService>;
|
||||
let keyService: MockProxy<KeyService>;
|
||||
let encryptService: MockProxy<EncryptService>;
|
||||
let pinService: MockProxy<PinServiceAbstraction>;
|
||||
let keyGenerationService: MockProxy<KeyGenerationService>;
|
||||
let accountService: MockProxy<AccountService>;
|
||||
let restrictedItemTypesService: MockProxy<RestrictedItemTypesService>;
|
||||
|
||||
@@ -48,7 +48,7 @@ describe("ImportService", () => {
|
||||
collectionService = mock<CollectionService>();
|
||||
keyService = mock<KeyService>();
|
||||
encryptService = mock<EncryptService>();
|
||||
pinService = mock<PinServiceAbstraction>();
|
||||
keyGenerationService = mock<KeyGenerationService>();
|
||||
restrictedItemTypesService = mock<RestrictedItemTypesService>();
|
||||
|
||||
importService = new ImportService(
|
||||
@@ -59,7 +59,7 @@ describe("ImportService", () => {
|
||||
collectionService,
|
||||
keyService,
|
||||
encryptService,
|
||||
pinService,
|
||||
keyGenerationService,
|
||||
accountService,
|
||||
restrictedItemTypesService,
|
||||
);
|
||||
|
||||
@@ -4,16 +4,15 @@ import { firstValueFrom, map } from "rxjs";
|
||||
|
||||
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { CollectionService, CollectionWithIdRequest } from "@bitwarden/admin-console/common";
|
||||
import {
|
||||
CollectionService,
|
||||
CollectionWithIdRequest,
|
||||
CollectionView,
|
||||
CollectionTypes,
|
||||
} from "@bitwarden/admin-console/common";
|
||||
} from "@bitwarden/common/admin-console/models/collections";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { KeyGenerationService } from "@bitwarden/common/key-management/crypto";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { PinServiceAbstraction } from "@bitwarden/common/key-management/pin/pin.service.abstraction";
|
||||
import { ImportCiphersRequest } from "@bitwarden/common/models/request/import-ciphers.request";
|
||||
import { ImportOrganizationCiphersRequest } from "@bitwarden/common/models/request/import-organization-ciphers.request";
|
||||
import { KvpRequest } from "@bitwarden/common/models/request/kvp.request";
|
||||
@@ -32,6 +31,7 @@ import { RestrictedItemTypesService } from "@bitwarden/common/vault/services/res
|
||||
import { KeyService } from "@bitwarden/key-management";
|
||||
|
||||
import {
|
||||
ArcCsvImporter,
|
||||
AscendoCsvImporter,
|
||||
AvastCsvImporter,
|
||||
AvastJsonImporter,
|
||||
@@ -119,7 +119,7 @@ export class ImportService implements ImportServiceAbstraction {
|
||||
private collectionService: CollectionService,
|
||||
private keyService: KeyService,
|
||||
private encryptService: EncryptService,
|
||||
private pinService: PinServiceAbstraction,
|
||||
private keyGenerationService: KeyGenerationService,
|
||||
private accountService: AccountService,
|
||||
private restrictedItemTypesService: RestrictedItemTypesService,
|
||||
) {}
|
||||
@@ -238,7 +238,7 @@ export class ImportService implements ImportServiceAbstraction {
|
||||
this.encryptService,
|
||||
this.i18nService,
|
||||
this.cipherService,
|
||||
this.pinService,
|
||||
this.keyGenerationService,
|
||||
this.accountService,
|
||||
promptForPassword_callback,
|
||||
);
|
||||
@@ -257,6 +257,8 @@ export class ImportService implements ImportServiceAbstraction {
|
||||
return new PadlockCsvImporter();
|
||||
case "keepass2xml":
|
||||
return new KeePass2XmlImporter();
|
||||
case "arccsv":
|
||||
return new ArcCsvImporter();
|
||||
case "edgecsv":
|
||||
case "chromecsv":
|
||||
case "operacsv":
|
||||
@@ -374,10 +376,13 @@ export class ImportService implements ImportServiceAbstraction {
|
||||
|
||||
private async handleIndividualImport(importResult: ImportResult, userId: UserId) {
|
||||
const request = new ImportCiphersRequest();
|
||||
for (let i = 0; i < importResult.ciphers.length; i++) {
|
||||
const c = await this.cipherService.encrypt(importResult.ciphers[i], userId);
|
||||
request.ciphers.push(new CipherRequest(c));
|
||||
|
||||
const encryptedCiphers = await this.cipherService.encryptMany(importResult.ciphers, userId);
|
||||
|
||||
for (const encryptedCipher of encryptedCiphers) {
|
||||
request.ciphers.push(new CipherRequest(encryptedCipher));
|
||||
}
|
||||
|
||||
const userKey = await firstValueFrom(this.keyService.userKey$(userId));
|
||||
|
||||
if (importResult.folders != null) {
|
||||
@@ -400,11 +405,18 @@ export class ImportService implements ImportServiceAbstraction {
|
||||
userId: UserId,
|
||||
) {
|
||||
const request = new ImportOrganizationCiphersRequest();
|
||||
for (let i = 0; i < importResult.ciphers.length; i++) {
|
||||
importResult.ciphers[i].organizationId = organizationId;
|
||||
const c = await this.cipherService.encrypt(importResult.ciphers[i], userId);
|
||||
request.ciphers.push(new CipherRequest(c));
|
||||
|
||||
// Set organization ID on all ciphers before batch encryption
|
||||
importResult.ciphers.forEach((cipher) => {
|
||||
cipher.organizationId = organizationId;
|
||||
});
|
||||
|
||||
const encryptedCiphers = await this.cipherService.encryptMany(importResult.ciphers, userId);
|
||||
|
||||
for (const encryptedCipher of encryptedCiphers) {
|
||||
request.ciphers.push(new CipherRequest(encryptedCipher));
|
||||
}
|
||||
|
||||
if (importResult.collections != null) {
|
||||
for (let i = 0; i < importResult.collections.length; i++) {
|
||||
importResult.collections[i].organizationId = organizationId;
|
||||
|
||||
Reference in New Issue
Block a user