mirror of
https://github.com/bitwarden/browser
synced 2026-01-28 15:23:53 +00:00
Merge branch 'main' into PM-29919-Add-dropdown-to-select-email-verification-and-emails-field-to-Send-when-creating-or-editing-a-Send
This commit is contained in:
@@ -24,12 +24,13 @@ pub fn get_supported_importers<T: InstalledBrowserRetriever>(
|
||||
let installed_browsers = T::get_installed_browsers().unwrap_or_default();
|
||||
|
||||
const IMPORTERS: &[(&str, &str)] = &[
|
||||
("arccsv", "Arc"),
|
||||
("bravecsv", "Brave"),
|
||||
("chromecsv", "Chrome"),
|
||||
("chromiumcsv", "Chromium"),
|
||||
("bravecsv", "Brave"),
|
||||
("edgecsv", "Microsoft Edge"),
|
||||
("operacsv", "Opera"),
|
||||
("vivaldicsv", "Vivaldi"),
|
||||
("edgecsv", "Microsoft Edge"),
|
||||
];
|
||||
|
||||
let supported: HashSet<&'static str> =
|
||||
@@ -91,6 +92,7 @@ mod tests {
|
||||
let map = get_supported_importers::<MockInstalledBrowserRetriever>();
|
||||
|
||||
let expected: HashSet<String> = HashSet::from([
|
||||
"arccsv".to_string(),
|
||||
"chromecsv".to_string(),
|
||||
"chromiumcsv".to_string(),
|
||||
"bravecsv".to_string(),
|
||||
@@ -113,6 +115,7 @@ mod tests {
|
||||
fn macos_specific_loaders_match_const_array() {
|
||||
let map = get_supported_importers::<MockInstalledBrowserRetriever>();
|
||||
let ids = [
|
||||
"arccsv",
|
||||
"chromecsv",
|
||||
"chromiumcsv",
|
||||
"bravecsv",
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
@@ -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" },
|
||||
|
||||
@@ -31,6 +31,7 @@ import { RestrictedItemTypesService } from "@bitwarden/common/vault/services/res
|
||||
import { KeyService } from "@bitwarden/key-management";
|
||||
|
||||
import {
|
||||
ArcCsvImporter,
|
||||
AscendoCsvImporter,
|
||||
AvastCsvImporter,
|
||||
AvastJsonImporter,
|
||||
@@ -256,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":
|
||||
|
||||
Reference in New Issue
Block a user