Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโ€™ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PM-11882] Handled identity item and unsupported items during ProtonPass import. #10967

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 96 additions & 3 deletions libs/importer/spec/protonpass-json-importer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ describe("Protonpass Json Importer", () => {
// "My Secure Note" is assigned to folder "Personal"
expect(result.folderRelationships[1]).toEqual([1, 0]);
// "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 () => {
Expand All @@ -102,7 +102,7 @@ describe("Protonpass Json Importer", () => {
// "My Secure Note" is assigned to folder "Personal"
expect(result.collectionRelationships[1]).toEqual([1, 0]);
// "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 () => {
Expand All @@ -114,7 +114,7 @@ describe("Protonpass Json Importer", () => {
expect(cipher.name).not.toBe("My Deleted Note");
}

expect(ciphers.length).toBe(4);
expect(ciphers.length).toBe(5);
});

it("should set favorites", async () => {
Expand All @@ -126,4 +126,97 @@ describe("Protonpass Json Importer", () => {
expect(ciphers[1].favorite).toBe(false);
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 changes: 138 additions & 0 deletions libs/importer/spec/test-data/protonpass-json/protonpass.json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,144 @@ export const testData: ProtonPassJsonFile = {
modifyTime: 1689182908,
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: {
Expand Down
Original file line number Diff line number Diff line change
@@ -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",
});
});
});
Original file line number Diff line number Diff line change
@@ -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 };
}
Loading
Loading