mirror of
https://github.com/bitwarden/browser
synced 2025-12-13 06:43:35 +00:00
[PM-11882] Handled identity item and unsupported items during ProtonPass import. (#10967)
This commit is contained in:
@@ -85,7 +85,7 @@ describe("Protonpass Json Importer", () => {
|
|||||||
// "My Secure Note" is assigned to folder "Personal"
|
// "My Secure Note" is assigned to folder "Personal"
|
||||||
expect(result.folderRelationships[1]).toEqual([1, 0]);
|
expect(result.folderRelationships[1]).toEqual([1, 0]);
|
||||||
// "Other vault login" is assigned to folder "Test"
|
// "Other vault login" is assigned to folder "Test"
|
||||||
expect(result.folderRelationships[3]).toEqual([3, 1]);
|
expect(result.folderRelationships[4]).toEqual([4, 1]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should create collections if part of an organization", async () => {
|
it("should create collections if part of an organization", async () => {
|
||||||
@@ -102,7 +102,7 @@ describe("Protonpass Json Importer", () => {
|
|||||||
// "My Secure Note" is assigned to folder "Personal"
|
// "My Secure Note" is assigned to folder "Personal"
|
||||||
expect(result.collectionRelationships[1]).toEqual([1, 0]);
|
expect(result.collectionRelationships[1]).toEqual([1, 0]);
|
||||||
// "Other vault login" is assigned to folder "Test"
|
// "Other vault login" is assigned to folder "Test"
|
||||||
expect(result.collectionRelationships[3]).toEqual([3, 1]);
|
expect(result.collectionRelationships[4]).toEqual([4, 1]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not add deleted items", async () => {
|
it("should not add deleted items", async () => {
|
||||||
@@ -114,7 +114,7 @@ describe("Protonpass Json Importer", () => {
|
|||||||
expect(cipher.name).not.toBe("My Deleted Note");
|
expect(cipher.name).not.toBe("My Deleted Note");
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(ciphers.length).toBe(4);
|
expect(ciphers.length).toBe(5);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should set favorites", async () => {
|
it("should set favorites", async () => {
|
||||||
@@ -126,4 +126,97 @@ describe("Protonpass Json Importer", () => {
|
|||||||
expect(ciphers[1].favorite).toBe(false);
|
expect(ciphers[1].favorite).toBe(false);
|
||||||
expect(ciphers[2].favorite).toBe(true);
|
expect(ciphers[2].favorite).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should skip unsupported items", async () => {
|
||||||
|
const testDataJson = JSON.stringify(testData);
|
||||||
|
const result = await importer.parse(testDataJson);
|
||||||
|
expect(result != null).toBe(true);
|
||||||
|
|
||||||
|
const ciphers = result.ciphers;
|
||||||
|
expect(ciphers.length).toBe(5);
|
||||||
|
expect(ciphers[4].type).toEqual(CipherType.Login);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should parse identity data", async () => {
|
||||||
|
const testDataJson = JSON.stringify(testData);
|
||||||
|
const result = await importer.parse(testDataJson);
|
||||||
|
expect(result != null).toBe(true);
|
||||||
|
|
||||||
|
result.ciphers.shift();
|
||||||
|
result.ciphers.shift();
|
||||||
|
result.ciphers.shift();
|
||||||
|
|
||||||
|
const cipher = result.ciphers.shift();
|
||||||
|
expect(cipher.type).toEqual(CipherType.Identity);
|
||||||
|
expect(cipher.identity.firstName).toBe("Test");
|
||||||
|
expect(cipher.identity.middleName).toBe("1");
|
||||||
|
expect(cipher.identity.lastName).toBe("1");
|
||||||
|
expect(cipher.identity.email).toBe("test@gmail.com");
|
||||||
|
expect(cipher.identity.phone).toBe("7507951789");
|
||||||
|
expect(cipher.identity.company).toBe("Bitwarden");
|
||||||
|
expect(cipher.identity.ssn).toBe("98378264782");
|
||||||
|
expect(cipher.identity.passportNumber).toBe("7173716378612");
|
||||||
|
expect(cipher.identity.licenseNumber).toBe("21234");
|
||||||
|
expect(cipher.identity.address1).toBe("Bitwarden");
|
||||||
|
expect(cipher.identity.address2).toBe("23 Street");
|
||||||
|
expect(cipher.identity.address3).toBe("12th Foor Test County");
|
||||||
|
expect(cipher.identity.city).toBe("New York");
|
||||||
|
expect(cipher.identity.state).toBe("Test");
|
||||||
|
expect(cipher.identity.postalCode).toBe("4038456");
|
||||||
|
expect(cipher.identity.country).toBe("US");
|
||||||
|
|
||||||
|
expect(cipher.fields.length).toEqual(13);
|
||||||
|
|
||||||
|
expect(cipher.fields.at(0).name).toEqual("gender");
|
||||||
|
expect(cipher.fields.at(0).value).toEqual("Male");
|
||||||
|
expect(cipher.fields.at(0).type).toEqual(FieldType.Text);
|
||||||
|
|
||||||
|
expect(cipher.fields.at(1).name).toEqual("TestPersonal");
|
||||||
|
expect(cipher.fields.at(1).value).toEqual("Personal");
|
||||||
|
expect(cipher.fields.at(1).type).toEqual(FieldType.Text);
|
||||||
|
|
||||||
|
expect(cipher.fields.at(2).name).toEqual("TestAddress");
|
||||||
|
expect(cipher.fields.at(2).value).toEqual("Address");
|
||||||
|
expect(cipher.fields.at(2).type).toEqual(FieldType.Text);
|
||||||
|
|
||||||
|
expect(cipher.fields.at(3).name).toEqual("xHandle");
|
||||||
|
expect(cipher.fields.at(3).value).toEqual("@twiter");
|
||||||
|
expect(cipher.fields.at(3).type).toEqual(FieldType.Text);
|
||||||
|
|
||||||
|
expect(cipher.fields.at(4).name).toEqual("secondPhoneNumber");
|
||||||
|
expect(cipher.fields.at(4).value).toEqual("243538978");
|
||||||
|
expect(cipher.fields.at(4).type).toEqual(FieldType.Text);
|
||||||
|
|
||||||
|
expect(cipher.fields.at(5).name).toEqual("instagram");
|
||||||
|
expect(cipher.fields.at(5).value).toEqual("@insta");
|
||||||
|
expect(cipher.fields.at(5).type).toEqual(FieldType.Text);
|
||||||
|
|
||||||
|
expect(cipher.fields.at(6).name).toEqual("TestContact");
|
||||||
|
expect(cipher.fields.at(6).value).toEqual("Contact");
|
||||||
|
expect(cipher.fields.at(6).type).toEqual(FieldType.Hidden);
|
||||||
|
|
||||||
|
expect(cipher.fields.at(7).name).toEqual("jobTitle");
|
||||||
|
expect(cipher.fields.at(7).value).toEqual("Engineer");
|
||||||
|
expect(cipher.fields.at(7).type).toEqual(FieldType.Text);
|
||||||
|
|
||||||
|
expect(cipher.fields.at(8).name).toEqual("workPhoneNumber");
|
||||||
|
expect(cipher.fields.at(8).value).toEqual("78236476238746");
|
||||||
|
expect(cipher.fields.at(8).type).toEqual(FieldType.Text);
|
||||||
|
|
||||||
|
expect(cipher.fields.at(9).name).toEqual("TestWork");
|
||||||
|
expect(cipher.fields.at(9).value).toEqual("Work");
|
||||||
|
expect(cipher.fields.at(9).type).toEqual(FieldType.Hidden);
|
||||||
|
|
||||||
|
expect(cipher.fields.at(10).name).toEqual("TestSection");
|
||||||
|
expect(cipher.fields.at(10).value).toEqual("Section");
|
||||||
|
expect(cipher.fields.at(10).type).toEqual(FieldType.Text);
|
||||||
|
|
||||||
|
expect(cipher.fields.at(11).name).toEqual("TestSectionHidden");
|
||||||
|
expect(cipher.fields.at(11).value).toEqual("SectionHidden");
|
||||||
|
expect(cipher.fields.at(11).type).toEqual(FieldType.Hidden);
|
||||||
|
|
||||||
|
expect(cipher.fields.at(12).name).toEqual("TestExtra");
|
||||||
|
expect(cipher.fields.at(12).value).toEqual("Extra");
|
||||||
|
expect(cipher.fields.at(12).type).toEqual(FieldType.Text);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -138,6 +138,144 @@ export const testData: ProtonPassJsonFile = {
|
|||||||
modifyTime: 1689182908,
|
modifyTime: 1689182908,
|
||||||
pinned: false,
|
pinned: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
itemId:
|
||||||
|
"gliCOyyJOsoBf5QIijvCF4QsPij3q_MR4nCXZ2sXm7YCJCfHjrRD_p2XG9vLsaytErsQvMhcLISVS7q8-7SCkg==",
|
||||||
|
shareId:
|
||||||
|
"TpawpLbs1nuUlQUCtgKZgb3zgAvbrGrOaqOylKqVe_RLROEyUvMq8_ZEuGw73PGRUSr89iNtQ2NosuggP54nwA==",
|
||||||
|
data: {
|
||||||
|
metadata: {
|
||||||
|
name: "Identity",
|
||||||
|
note: "",
|
||||||
|
itemUuid: "c2e52768",
|
||||||
|
},
|
||||||
|
extraFields: [
|
||||||
|
{
|
||||||
|
fieldName: "TestExtra",
|
||||||
|
type: "text",
|
||||||
|
data: {
|
||||||
|
content: "Extra",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
type: "identity",
|
||||||
|
content: {
|
||||||
|
fullName: "Test 1",
|
||||||
|
email: "test@gmail.com",
|
||||||
|
phoneNumber: "7507951789",
|
||||||
|
firstName: "Test",
|
||||||
|
middleName: "1",
|
||||||
|
lastName: "Test",
|
||||||
|
birthdate: "",
|
||||||
|
gender: "Male",
|
||||||
|
extraPersonalDetails: [
|
||||||
|
{
|
||||||
|
fieldName: "TestPersonal",
|
||||||
|
type: "text",
|
||||||
|
data: {
|
||||||
|
content: "Personal",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
organization: "Bitwarden",
|
||||||
|
streetAddress: "23 Street",
|
||||||
|
zipOrPostalCode: "4038456",
|
||||||
|
city: "New York",
|
||||||
|
stateOrProvince: "Test",
|
||||||
|
countryOrRegion: "US",
|
||||||
|
floor: "12th Foor",
|
||||||
|
county: "Test County",
|
||||||
|
extraAddressDetails: [
|
||||||
|
{
|
||||||
|
fieldName: "TestAddress",
|
||||||
|
type: "text",
|
||||||
|
data: {
|
||||||
|
content: "Address",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
socialSecurityNumber: "98378264782",
|
||||||
|
passportNumber: "7173716378612",
|
||||||
|
licenseNumber: "21234",
|
||||||
|
website: "",
|
||||||
|
xHandle: "@twiter",
|
||||||
|
secondPhoneNumber: "243538978",
|
||||||
|
linkedin: "",
|
||||||
|
reddit: "",
|
||||||
|
facebook: "",
|
||||||
|
yahoo: "",
|
||||||
|
instagram: "@insta",
|
||||||
|
extraContactDetails: [
|
||||||
|
{
|
||||||
|
fieldName: "TestContact",
|
||||||
|
type: "hidden",
|
||||||
|
data: {
|
||||||
|
content: "Contact",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
company: "Bitwarden",
|
||||||
|
jobTitle: "Engineer",
|
||||||
|
personalWebsite: "",
|
||||||
|
workPhoneNumber: "78236476238746",
|
||||||
|
workEmail: "",
|
||||||
|
extraWorkDetails: [
|
||||||
|
{
|
||||||
|
fieldName: "TestWork",
|
||||||
|
type: "hidden",
|
||||||
|
data: {
|
||||||
|
content: "Work",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
extraSections: [
|
||||||
|
{
|
||||||
|
sectionName: "TestSection",
|
||||||
|
sectionFields: [
|
||||||
|
{
|
||||||
|
fieldName: "TestSection",
|
||||||
|
type: "text",
|
||||||
|
data: {
|
||||||
|
content: "Section",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: "TestSectionHidden",
|
||||||
|
type: "hidden",
|
||||||
|
data: {
|
||||||
|
content: "SectionHidden",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
state: 1,
|
||||||
|
aliasEmail: null,
|
||||||
|
contentFormatVersion: 6,
|
||||||
|
createTime: 1725707298,
|
||||||
|
modifyTime: 1725707298,
|
||||||
|
pinned: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
itemId:
|
||||||
|
"WTKLZtKfHIC3Gv7gRXUANifNjj0gN3P_52I4MznAzig9GSb_OgJ0qcZ8taOZyfsFTLOWBslXwI-HSMWXVmnKzQ==",
|
||||||
|
shareId:
|
||||||
|
"TpawpLbs1nuUlQUCtgKZgb3zgAvbrGrOaqOylKqVe_RLROEyUvMq8_ZEuGw73PGRUSr89iNtQ2NosuggP54nwA==",
|
||||||
|
data: {
|
||||||
|
metadata: { name: "Alias", note: "", itemUuid: "576f14fa" },
|
||||||
|
extraFields: [],
|
||||||
|
type: "alias",
|
||||||
|
content: {},
|
||||||
|
},
|
||||||
|
state: 1,
|
||||||
|
aliasEmail: "alias.removing005@passinbox.com",
|
||||||
|
contentFormatVersion: 6,
|
||||||
|
createTime: 1725708208,
|
||||||
|
modifyTime: 1725708208,
|
||||||
|
pinned: false,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
REDACTED_VAULT_ID_B: {
|
REDACTED_VAULT_ID_B: {
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import { processNames } from "./protonpass-import-utils";
|
||||||
|
|
||||||
|
describe("processNames", () => {
|
||||||
|
it("should use only fullName to map names if it contains at least three words, ignoring individual name fields", () => {
|
||||||
|
const result = processNames("Alice Beth Carter", "Kevin", "", "");
|
||||||
|
expect(result).toEqual({
|
||||||
|
mappedFirstName: "Alice",
|
||||||
|
mappedMiddleName: "Beth",
|
||||||
|
mappedLastName: "Carter",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should map extra words to the middle name if fullName contains more than three words", () => {
|
||||||
|
const result = processNames("Alice Beth Middle Carter", "", "", "");
|
||||||
|
expect(result).toEqual({
|
||||||
|
mappedFirstName: "Alice",
|
||||||
|
mappedMiddleName: "Beth Middle",
|
||||||
|
mappedLastName: "Carter",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should map names correctly even if fullName has words separated by more than one space", () => {
|
||||||
|
const result = processNames("Alice Carter", "", "", "");
|
||||||
|
expect(result).toEqual({
|
||||||
|
mappedFirstName: "Alice",
|
||||||
|
mappedMiddleName: "",
|
||||||
|
mappedLastName: "Carter",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle a single name in fullName and use middleName and lastName to populate rest of names", () => {
|
||||||
|
const result = processNames("Alice", "", "Beth", "Carter");
|
||||||
|
expect(result).toEqual({
|
||||||
|
mappedFirstName: "Alice",
|
||||||
|
mappedMiddleName: "Beth",
|
||||||
|
mappedLastName: "Carter",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should correctly map fullName when it only contains two words", () => {
|
||||||
|
const result = processNames("Alice Carter", "", "", "");
|
||||||
|
expect(result).toEqual({
|
||||||
|
mappedFirstName: "Alice",
|
||||||
|
mappedMiddleName: "",
|
||||||
|
mappedLastName: "Carter",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should map middle name from middleName if fullName only contains two words", () => {
|
||||||
|
const result = processNames("Alice Carter", "", "Beth", "");
|
||||||
|
expect(result).toEqual({
|
||||||
|
mappedFirstName: "Alice",
|
||||||
|
mappedMiddleName: "Beth",
|
||||||
|
mappedLastName: "Carter",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fall back to firstName, middleName, and lastName if fullName is empty", () => {
|
||||||
|
const result = processNames("", "Alice", "Beth", "Carter");
|
||||||
|
expect(result).toEqual({
|
||||||
|
mappedFirstName: "Alice",
|
||||||
|
mappedMiddleName: "Beth",
|
||||||
|
mappedLastName: "Carter",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
export function processNames(
|
||||||
|
fullname: string | null,
|
||||||
|
firstname: string | null,
|
||||||
|
middlename: string | null,
|
||||||
|
lastname: string | null,
|
||||||
|
) {
|
||||||
|
let mappedFirstName = firstname;
|
||||||
|
let mappedMiddleName = middlename;
|
||||||
|
let mappedLastName = lastname;
|
||||||
|
|
||||||
|
if (fullname) {
|
||||||
|
const parts = fullname.trim().split(/\s+/);
|
||||||
|
|
||||||
|
// Assign parts to first, middle, and last name based on the number of parts
|
||||||
|
mappedFirstName = parts[0] || firstname;
|
||||||
|
mappedLastName = parts.length > 1 ? parts[parts.length - 1] : lastname;
|
||||||
|
mappedMiddleName = parts.length > 2 ? parts.slice(1, -1).join(" ") : middlename;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { mappedFirstName, mappedMiddleName, mappedLastName };
|
||||||
|
}
|
||||||
@@ -1,24 +1,110 @@
|
|||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { FieldType, SecureNoteType, CipherType } from "@bitwarden/common/vault/enums";
|
import { FieldType, SecureNoteType, CipherType } from "@bitwarden/common/vault/enums";
|
||||||
import { CardView } from "@bitwarden/common/vault/models/view/card.view";
|
import { CardView } from "@bitwarden/common/vault/models/view/card.view";
|
||||||
|
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||||
|
import { IdentityView } from "@bitwarden/common/vault/models/view/identity.view";
|
||||||
import { SecureNoteView } from "@bitwarden/common/vault/models/view/secure-note.view";
|
import { SecureNoteView } from "@bitwarden/common/vault/models/view/secure-note.view";
|
||||||
|
|
||||||
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";
|
||||||
|
|
||||||
|
import { processNames } from "./protonpass-import-utils";
|
||||||
import {
|
import {
|
||||||
ProtonPassCreditCardItemContent,
|
ProtonPassCreditCardItemContent,
|
||||||
|
ProtonPassIdentityItemContent,
|
||||||
|
ProtonPassIdentityItemExtraSection,
|
||||||
|
ProtonPassItemExtraField,
|
||||||
ProtonPassItemState,
|
ProtonPassItemState,
|
||||||
ProtonPassJsonFile,
|
ProtonPassJsonFile,
|
||||||
ProtonPassLoginItemContent,
|
ProtonPassLoginItemContent,
|
||||||
} from "./types/protonpass-json-type";
|
} from "./types/protonpass-json-type";
|
||||||
|
|
||||||
export class ProtonPassJsonImporter extends BaseImporter implements Importer {
|
export class ProtonPassJsonImporter extends BaseImporter implements Importer {
|
||||||
|
private mappedIdentityItemKeys = [
|
||||||
|
"fullName",
|
||||||
|
"firstName",
|
||||||
|
"middleName",
|
||||||
|
"lastName",
|
||||||
|
"email",
|
||||||
|
"phoneNumber",
|
||||||
|
"company",
|
||||||
|
"socialSecurityNumber",
|
||||||
|
"passportNumber",
|
||||||
|
"licenseNumber",
|
||||||
|
"organization",
|
||||||
|
"streetAddress",
|
||||||
|
"floor",
|
||||||
|
"county",
|
||||||
|
"city",
|
||||||
|
"stateOrProvince",
|
||||||
|
"zipOrPostalCode",
|
||||||
|
"countryOrRegion",
|
||||||
|
];
|
||||||
|
|
||||||
|
private identityItemExtraFieldsKeys = [
|
||||||
|
"extraPersonalDetails",
|
||||||
|
"extraAddressDetails",
|
||||||
|
"extraContactDetails",
|
||||||
|
"extraWorkDetails",
|
||||||
|
"extraSections",
|
||||||
|
];
|
||||||
|
|
||||||
constructor(private i18nService: I18nService) {
|
constructor(private i18nService: I18nService) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private processIdentityItemUnmappedAndExtraFields(
|
||||||
|
cipher: CipherView,
|
||||||
|
identityItem: ProtonPassIdentityItemContent,
|
||||||
|
) {
|
||||||
|
Object.keys(identityItem).forEach((key) => {
|
||||||
|
if (
|
||||||
|
!this.mappedIdentityItemKeys.includes(key) &&
|
||||||
|
!this.identityItemExtraFieldsKeys.includes(key)
|
||||||
|
) {
|
||||||
|
this.processKvp(
|
||||||
|
cipher,
|
||||||
|
key,
|
||||||
|
identityItem[key as keyof ProtonPassIdentityItemContent] as string,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.identityItemExtraFieldsKeys.includes(key)) {
|
||||||
|
if (key !== "extraSections") {
|
||||||
|
const extraFields = identityItem[
|
||||||
|
key as keyof ProtonPassIdentityItemContent
|
||||||
|
] as ProtonPassItemExtraField[];
|
||||||
|
|
||||||
|
extraFields?.forEach((extraField) => {
|
||||||
|
this.processKvp(
|
||||||
|
cipher,
|
||||||
|
extraField.fieldName,
|
||||||
|
extraField.data.content,
|
||||||
|
extraField.type === "hidden" ? FieldType.Hidden : FieldType.Text,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const extraSections = identityItem[
|
||||||
|
key as keyof ProtonPassIdentityItemContent
|
||||||
|
] as ProtonPassIdentityItemExtraSection[];
|
||||||
|
|
||||||
|
extraSections?.forEach((extraSection) => {
|
||||||
|
extraSection.sectionFields?.forEach((extraField) => {
|
||||||
|
this.processKvp(
|
||||||
|
cipher,
|
||||||
|
extraField.fieldName,
|
||||||
|
extraField.data.content,
|
||||||
|
extraField.type === "hidden" ? FieldType.Hidden : FieldType.Text,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
parse(data: string): Promise<ImportResult> {
|
parse(data: string): Promise<ImportResult> {
|
||||||
const result = new ImportResult();
|
const result = new ImportResult();
|
||||||
const results: ProtonPassJsonFile = JSON.parse(data);
|
const results: ProtonPassJsonFile = JSON.parse(data);
|
||||||
@@ -38,7 +124,6 @@ export class ProtonPassJsonImporter extends BaseImporter implements Importer {
|
|||||||
if (item.state == ProtonPassItemState.TRASHED) {
|
if (item.state == ProtonPassItemState.TRASHED) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
this.processFolder(result, vault.name);
|
|
||||||
|
|
||||||
const cipher = this.initLoginCipher();
|
const cipher = this.initLoginCipher();
|
||||||
cipher.name = this.getValueOrDefault(item.data.metadata.name, "--");
|
cipher.name = this.getValueOrDefault(item.data.metadata.name, "--");
|
||||||
@@ -96,8 +181,55 @@ export class ProtonPassJsonImporter extends BaseImporter implements Importer {
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case "identity": {
|
||||||
|
const identityContent = item.data.content as ProtonPassIdentityItemContent;
|
||||||
|
cipher.type = CipherType.Identity;
|
||||||
|
cipher.identity = new IdentityView();
|
||||||
|
|
||||||
|
const { mappedFirstName, mappedMiddleName, mappedLastName } = processNames(
|
||||||
|
this.getValueOrDefault(identityContent.fullName),
|
||||||
|
this.getValueOrDefault(identityContent.firstName),
|
||||||
|
this.getValueOrDefault(identityContent.middleName),
|
||||||
|
this.getValueOrDefault(identityContent.lastName),
|
||||||
|
);
|
||||||
|
cipher.identity.firstName = mappedFirstName;
|
||||||
|
cipher.identity.middleName = mappedMiddleName;
|
||||||
|
cipher.identity.lastName = mappedLastName;
|
||||||
|
|
||||||
|
cipher.identity.email = this.getValueOrDefault(identityContent.email);
|
||||||
|
cipher.identity.phone = this.getValueOrDefault(identityContent.phoneNumber);
|
||||||
|
cipher.identity.company = this.getValueOrDefault(identityContent.company);
|
||||||
|
cipher.identity.ssn = this.getValueOrDefault(identityContent.socialSecurityNumber);
|
||||||
|
cipher.identity.passportNumber = this.getValueOrDefault(identityContent.passportNumber);
|
||||||
|
cipher.identity.licenseNumber = this.getValueOrDefault(identityContent.licenseNumber);
|
||||||
|
|
||||||
|
const address3 =
|
||||||
|
`${identityContent.floor ?? ""} ${identityContent.county ?? ""}`.trim();
|
||||||
|
cipher.identity.address1 = this.getValueOrDefault(identityContent.organization);
|
||||||
|
cipher.identity.address2 = this.getValueOrDefault(identityContent.streetAddress);
|
||||||
|
cipher.identity.address3 = this.getValueOrDefault(address3);
|
||||||
|
|
||||||
|
cipher.identity.city = this.getValueOrDefault(identityContent.city);
|
||||||
|
cipher.identity.state = this.getValueOrDefault(identityContent.stateOrProvince);
|
||||||
|
cipher.identity.postalCode = this.getValueOrDefault(identityContent.zipOrPostalCode);
|
||||||
|
cipher.identity.country = this.getValueOrDefault(identityContent.countryOrRegion);
|
||||||
|
this.processIdentityItemUnmappedAndExtraFields(cipher, identityContent);
|
||||||
|
|
||||||
|
for (const extraField of item.data.extraFields) {
|
||||||
|
this.processKvp(
|
||||||
|
cipher,
|
||||||
|
extraField.fieldName,
|
||||||
|
extraField.data.content,
|
||||||
|
extraField.type === "hidden" ? FieldType.Hidden : FieldType.Text,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.processFolder(result, vault.name);
|
||||||
this.cleanupCipher(cipher);
|
this.cleanupCipher(cipher);
|
||||||
result.ciphers.push(cipher);
|
result.ciphers.push(cipher);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,8 +36,11 @@ export type ProtonPassItemData = {
|
|||||||
metadata: ProtonPassItemMetadata;
|
metadata: ProtonPassItemMetadata;
|
||||||
extraFields: ProtonPassItemExtraField[];
|
extraFields: ProtonPassItemExtraField[];
|
||||||
platformSpecific?: any;
|
platformSpecific?: any;
|
||||||
type: "login" | "alias" | "creditCard" | "note";
|
type: "login" | "alias" | "creditCard" | "note" | "identity";
|
||||||
content: ProtonPassLoginItemContent | ProtonPassCreditCardItemContent;
|
content:
|
||||||
|
| ProtonPassLoginItemContent
|
||||||
|
| ProtonPassCreditCardItemContent
|
||||||
|
| ProtonPassIdentityItemContent;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ProtonPassItemMetadata = {
|
export type ProtonPassItemMetadata = {
|
||||||
@@ -74,3 +77,48 @@ export type ProtonPassCreditCardItemContent = {
|
|||||||
expirationDate?: string;
|
expirationDate?: string;
|
||||||
pin?: string;
|
pin?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ProtonPassIdentityItemExtraSection = {
|
||||||
|
sectionName?: string;
|
||||||
|
sectionFields?: ProtonPassItemExtraField[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ProtonPassIdentityItemContent = {
|
||||||
|
fullName?: string;
|
||||||
|
email?: string;
|
||||||
|
phoneNumber?: string;
|
||||||
|
firstName?: string;
|
||||||
|
middleName?: string;
|
||||||
|
lastName?: string;
|
||||||
|
birthdate?: string;
|
||||||
|
gender?: string;
|
||||||
|
extraPersonalDetails?: ProtonPassItemExtraField[];
|
||||||
|
organization?: string;
|
||||||
|
streetAddress?: string;
|
||||||
|
zipOrPostalCode?: string;
|
||||||
|
city?: string;
|
||||||
|
stateOrProvince?: string;
|
||||||
|
countryOrRegion?: string;
|
||||||
|
floor?: string;
|
||||||
|
county?: string;
|
||||||
|
extraAddressDetails?: ProtonPassItemExtraField[];
|
||||||
|
socialSecurityNumber?: string;
|
||||||
|
passportNumber?: string;
|
||||||
|
licenseNumber?: string;
|
||||||
|
website?: string;
|
||||||
|
xHandle?: string;
|
||||||
|
secondPhoneNumber?: string;
|
||||||
|
linkedin?: string;
|
||||||
|
reddit?: string;
|
||||||
|
facebook?: string;
|
||||||
|
yahoo?: string;
|
||||||
|
instagram?: string;
|
||||||
|
extraContactDetails?: ProtonPassItemExtraField[];
|
||||||
|
company?: string;
|
||||||
|
jobTitle?: string;
|
||||||
|
personalWebsite?: string;
|
||||||
|
workPhoneNumber?: string;
|
||||||
|
workEmail?: string;
|
||||||
|
extraWorkDetails?: ProtonPassItemExtraField[];
|
||||||
|
extraSections?: ProtonPassIdentityItemExtraSection[];
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user