1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-06 00:13:28 +00:00

[PM-18153] add support for importing some older / wonky card formats from msecure (#13328)

* add support for importing some older / wonky card formats from msecure

* slightly less fuzzy logic

---------

Co-authored-by: Daniel James Smith <2670567+djsmith85@users.noreply.github.com>
This commit is contained in:
Alex Rosenfeld
2025-03-20 13:53:17 -04:00
committed by GitHub
parent c999c19f07
commit bd0fedc5ce
2 changed files with 46 additions and 17 deletions

View File

@@ -8,6 +8,24 @@ describe("MSecureCsvImporter.parse", () => {
importer = new MSecureCsvImporter();
});
it("should correctly parse legacy formatted cards", async () => {
const mockCsvData =
`aWeirdOldStyleCard|1032,Credit Card,,Security code 1234,Card Number|12|5555 4444 3333 2222,Expiration Date|11|04/0029,Name on Card|9|Obi Wan Kenobi,Security Code|9|444,`.trim();
const result = await importer.parse(mockCsvData);
expect(result.success).toBe(true);
expect(result.ciphers.length).toBe(1);
const cipher = result.ciphers[0];
expect(cipher.name).toBe("aWeirdOldStyleCard");
expect(cipher.type).toBe(CipherType.Card);
expect(cipher.card.number).toBe("5555 4444 3333 2222");
expect(cipher.card.expiration).toBe("04 / 2029");
expect(cipher.card.code).toBe("444");
expect(cipher.card.cardholderName).toBe("Obi Wan Kenobi");
expect(cipher.notes).toBe("Security code 1234");
expect(cipher.card.brand).toBe("");
});
it("should correctly parse credit card entries as Secret Notes", async () => {
const mockCsvData =
`myCreditCard|155089404,Credit Card,,,Card Number|12|41111111111111111,Expiration Date|11|05/2026,Security Code|9|123,Name on Card|0|John Doe,PIN|9|1234,Issuing Bank|0|Visa,Phone Number|4|,Billing Address|0|,`.trim();

View File

@@ -43,23 +43,34 @@ export class MSecureCsvImporter extends BaseImporter implements Importer {
).split("/");
cipher.card.expMonth = month.trim();
cipher.card.expYear = year.trim();
cipher.card.code = this.getValueOrDefault(this.splitValueRetainingLastPart(value[6]));
cipher.card.cardholderName = this.getValueOrDefault(
this.splitValueRetainingLastPart(value[7]),
const securityCodeRegex = RegExp("^Security Code\\|\\d*\\|");
const securityCodeEntry = value.find((entry: string) => securityCodeRegex.test(entry));
cipher.card.code = this.getValueOrDefault(
this.splitValueRetainingLastPart(securityCodeEntry),
);
cipher.card.brand = this.getValueOrDefault(this.splitValueRetainingLastPart(value[9]));
cipher.notes =
this.getValueOrDefault(value[8].split("|")[0]) +
": " +
this.getValueOrDefault(this.splitValueRetainingLastPart(value[8]), "") +
"\n" +
this.getValueOrDefault(value[10].split("|")[0]) +
": " +
this.getValueOrDefault(this.splitValueRetainingLastPart(value[10]), "") +
"\n" +
this.getValueOrDefault(value[11].split("|")[0]) +
": " +
this.getValueOrDefault(this.splitValueRetainingLastPart(value[11]), "");
const cardNameRegex = RegExp("^Name on Card\\|\\d*\\|");
const nameOnCardEntry = value.find((entry: string) => entry.match(cardNameRegex));
cipher.card.cardholderName = this.getValueOrDefault(
this.splitValueRetainingLastPart(nameOnCardEntry),
);
cipher.card.brand = this.getValueOrDefault(this.splitValueRetainingLastPart(value[9]), "");
const noteRegex = RegExp("\\|\\d*\\|");
const rawNotes = value
.slice(2)
.filter((entry: string) => !this.isNullOrWhitespace(entry) && !noteRegex.test(entry));
const noteIndexes = [8, 10, 11];
const indexedNotes = noteIndexes
.filter((idx) => value[idx] && noteRegex.test(value[idx]))
.map((idx) => value[idx])
.map((val) => {
const key = val.split("|")[0];
const value = this.getValueOrDefault(this.splitValueRetainingLastPart(val), "");
return `${key}: ${value}`;
});
cipher.notes = [...rawNotes, ...indexedNotes].join("\n");
} else if (value.length > 3) {
cipher.type = CipherType.SecureNote;
cipher.secureNote = new SecureNoteView();
@@ -95,6 +106,6 @@ export class MSecureCsvImporter extends BaseImporter implements Importer {
// like "Password|8|myPassword", we want to keep the "myPassword" but also ensure that if
// the value contains any "|" it works fine
private splitValueRetainingLastPart(value: string) {
return value.split("|").slice(0, 2).concat(value.split("|").slice(2).join("|")).pop();
return value && value.split("|").slice(0, 2).concat(value.split("|").slice(2).join("|")).pop();
}
}