diff --git a/libs/importer/src/components/import.component.html b/libs/importer/src/components/import.component.html index 59ab6739c06..f6d6603dca9 100644 --- a/libs/importer/src/components/import.component.html +++ b/libs/importer/src/components/import.component.html @@ -435,6 +435,12 @@ and select Export items → Enter your Master Password and select Continue. → Save the CSV file on your device. + + On the desktop application, go to Tools → Export → Enter your master password + → Select XML Format (*.xml) as Export format → Click on next → Choose which + entries should be included in the export → Click on next to export into the location + previously chosen. + { + it("should return error with invalid export version", async () => { + const importer = new PasswordDepot17XmlImporter(); + const result = await importer.parse(MacOS_WrongVersion); + expect(result.errorMessage).toBe( + "Unsupported export version detected - (only 17.0 is supported)", + ); + }); + + it("should not create a folder/collection if the group fingerprint is null", async () => { + const importer = new PasswordDepot17XmlImporter(); + const result = await importer.parse(MacOS_PasswordDepotXmlFile); + expect(result.folders.length).toBe(0); + }); + + it("should create folders and with correct assignments", async () => { + const importer = new PasswordDepot17XmlImporter(); + const result = await importer.parse(MacOS_MultipleFolders); + + // Expect 10 ciphers, 5 without a folder and 3 within 'folder macos' and 2 with 'folder 2' + expect(result.ciphers.length).toBe(10); + + expect(result.folders.length).toBe(2); + expect(result.folders[0].name).toBe("folder macos"); + expect(result.folders[1].name).toBe("folder 2"); + + // 3 items within 'folder macos' + expect(result.folderRelationships[0]).toEqual([5, 0]); + expect(result.folderRelationships[1]).toEqual([6, 0]); + expect(result.folderRelationships[2]).toEqual([7, 0]); + + //2 items with 'folder 2' + expect(result.folderRelationships[3]).toEqual([8, 1]); + expect(result.folderRelationships[4]).toEqual([9, 1]); + }); + + it("should parse custom fields from a MacOS exported file", async () => { + const importer = new PasswordDepot17XmlImporter(); + const result = await importer.parse(MacOS_PasswordDepotXmlFile); + + const cipher = result.ciphers.shift(); + expect(cipher.name).toBe("card 1"); + expect(cipher.notes).toBe("comment"); + + expect(cipher.card).not.toBeNull(); + + expect(cipher.card.cardholderName).toBe("some CC holder"); + expect(cipher.card.number).toBe("4242424242424242"); + expect(cipher.card.brand).toBe("Visa"); + expect(cipher.card.expMonth).toBe("8"); + expect(cipher.card.expYear).toBe("2028"); + expect(cipher.card.code).toBe("125"); + }); +}); diff --git a/libs/importer/src/importers/password-depot/password-depot-17-xml-importer.spec.ts b/libs/importer/src/importers/password-depot/password-depot-17-xml-importer.spec.ts new file mode 100644 index 00000000000..ea84603aef4 --- /dev/null +++ b/libs/importer/src/importers/password-depot/password-depot-17-xml-importer.spec.ts @@ -0,0 +1,496 @@ +// 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 { FieldType, SecureNoteType } from "@bitwarden/common/vault/enums"; +import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; +import { CipherType } from "@bitwarden/sdk-internal"; + +import { + EncryptedFileData, + InvalidRootNodeData, + InvalidVersionData, + CreditCardTestData, + MissingPasswordsNodeData, + PasswordTestData, + IdentityTestData, + RDPTestData, + SoftwareLicenseTestData, + TeamViewerTestData, + PuttyTestData, + BankingTestData, + InformationTestData, + CertificateTestData, + EncryptedFileTestData, + DocumentTestData, +} from "../spec-data/password-depot-xml"; + +import { PasswordDepot17XmlImporter } from "./password-depot-17-xml-importer"; + +describe("Password Depot 17 Xml Importer", () => { + it("should return error with missing root tag", async () => { + const importer = new PasswordDepot17XmlImporter(); + const result = await importer.parse(InvalidRootNodeData); + expect(result.errorMessage).toBe("Missing `passwordfile` node."); + }); + + it("should return error with invalid export version", async () => { + const importer = new PasswordDepot17XmlImporter(); + const result = await importer.parse(InvalidVersionData); + expect(result.errorMessage).toBe( + "Unsupported export version detected - (only 17.0 is supported)", + ); + }); + + it("should return error if file is marked as encrypted", async () => { + const importer = new PasswordDepot17XmlImporter(); + const result = await importer.parse(EncryptedFileData); + expect(result.errorMessage).toBe("Encrypted Password Depot files are not supported."); + }); + + it("should return error with missing passwords node tag", async () => { + const importer = new PasswordDepot17XmlImporter(); + const result = await importer.parse(MissingPasswordsNodeData); + expect(result.success).toBe(false); + expect(result.errorMessage).toBe("Missing `passwordfile > passwords` node."); + }); + + it("should parse groups nodes into folders", async () => { + const importer = new PasswordDepot17XmlImporter(); + const folder = new FolderView(); + folder.name = "tempDB"; + const actual = [folder]; + + const result = await importer.parse(PasswordTestData); + expect(result.folders).toEqual(actual); + }); + + it("should parse password type into logins", async () => { + const importer = new PasswordDepot17XmlImporter(); + const result = await importer.parse(PasswordTestData); + + const cipher = result.ciphers.shift(); + + expect(cipher.type).toEqual(CipherType.Login); + expect(cipher.name).toBe("password type"); + expect(cipher.notes).toBe("someComment"); + + expect(cipher.login).not.toBeNull(); + expect(cipher.login.username).toBe("someUser"); + expect(cipher.login.password).toBe("p6J<]fmjv!:H&iJ7/Mwt@3i8"); + expect(cipher.login.uri).toBe("http://example.com"); + }); + + it("should parse any unmapped fields as custom fields", async () => { + const importer = new PasswordDepot17XmlImporter(); + const result = await importer.parse(PasswordTestData); + + const cipher = result.ciphers.shift(); + + expect(cipher.type).toBe(CipherType.Login); + expect(cipher.name).toBe("password type"); + + expect(cipher.fields).not.toBeNull(); + + expect(cipher.fields[0].name).toBe("lastmodified"); + expect(cipher.fields[0].value).toBe("07.05.2025 13:37:56"); + expect(cipher.fields[0].type).toBe(FieldType.Text); + + expect(cipher.fields[1].name).toBe("expirydate"); + expect(cipher.fields[1].value).toBe("07.05.2025"); + expect(cipher.fields[0].type).toBe(FieldType.Text); + + expect(cipher.fields[2].name).toBe("importance"); + expect(cipher.fields[2].value).toBe("0"); + + let customField = cipher.fields.find((f) => f.name === "passwort"); + expect(customField).toBeDefined(); + expect(customField.value).toEqual("password"); + expect(customField.type).toEqual(FieldType.Hidden); + + customField = cipher.fields.find((f) => f.name === "memo"); + expect(customField).toBeDefined(); + expect(customField.value).toEqual("memo"); + expect(customField.type).toEqual(FieldType.Text); + + customField = cipher.fields.find((f) => f.name === "datum"); + expect(customField).toBeDefined(); + const expectedDate = new Date("2025-05-13T00:00:00Z"); + expect(customField.value).toEqual(expectedDate.toLocaleDateString()); + expect(customField.type).toEqual(FieldType.Text); + + customField = cipher.fields.find((f) => f.name === "nummer"); + expect(customField).toBeDefined(); + expect(customField.value).toEqual("1"); + expect(customField.type).toEqual(FieldType.Text); + + customField = cipher.fields.find((f) => f.name === "boolean"); + expect(customField).toBeDefined(); + expect(customField.value).toEqual("1"); + expect(customField.type).toEqual(FieldType.Boolean); + + customField = cipher.fields.find((f) => f.name === "decimal"); + expect(customField).toBeDefined(); + expect(customField.value).toEqual("1,01"); + expect(customField.type).toEqual(FieldType.Text); + + customField = cipher.fields.find((f) => f.name === "email"); + expect(customField).toBeDefined(); + expect(customField.value).toEqual("who@cares.com"); + expect(customField.type).toEqual(FieldType.Text); + + customField = cipher.fields.find((f) => f.name === "url"); + expect(customField).toBeDefined(); + expect(customField.value).toEqual("example.com"); + expect(customField.type).toEqual(FieldType.Text); + }); + + it("should parse credit cards", async () => { + const importer = new PasswordDepot17XmlImporter(); + const result = await importer.parse(CreditCardTestData); + + const cipher = result.ciphers.shift(); + expect(cipher.name).toBe("some CreditCard"); + expect(cipher.notes).toBe("someComment"); + + expect(cipher.card).not.toBeNull(); + + expect(cipher.card.cardholderName).toBe("some CC holder"); + expect(cipher.card.number).toBe("4222422242224222"); + expect(cipher.card.brand).toBe("Visa"); + expect(cipher.card.expMonth).toBe("5"); + expect(cipher.card.expYear).toBe("2026"); + expect(cipher.card.code).toBe("123"); + }); + + it("should parse identity type", async () => { + const importer = new PasswordDepot17XmlImporter(); + const result = await importer.parse(IdentityTestData); + + const cipher = result.ciphers.shift(); + expect(cipher.name).toBe("identity type"); + expect(cipher.notes).toBe("someNote"); + + expect(cipher.identity).not.toBeNull(); + + expect(cipher.identity.firstName).toBe("firstName"); + expect(cipher.identity.lastName).toBe("surName"); + expect(cipher.identity.email).toBe("email"); + expect(cipher.identity.company).toBe("someCompany"); + expect(cipher.identity.address1).toBe("someStreet"); + expect(cipher.identity.address2).toBe("address 2"); + expect(cipher.identity.city).toBe("town"); + expect(cipher.identity.state).toBe("state"); + expect(cipher.identity.postalCode).toBe("zipCode"); + expect(cipher.identity.country).toBe("country"); + expect(cipher.identity.phone).toBe("phoneNumber"); + }); + + it("should parse RDP type", async () => { + const importer = new PasswordDepot17XmlImporter(); + const result = await importer.parse(RDPTestData); + + const cipher = result.ciphers.shift(); + expect(cipher.type).toEqual(CipherType.Login); + expect(cipher.name).toBe("rdp type"); + expect(cipher.notes).toBe("someNote"); + + expect(cipher.login).not.toBeNull(); + expect(cipher.login.username).toBe("someUser"); + expect(cipher.login.password).toBe("somePassword"); + expect(cipher.login.uri).toBe("ms-rd:subscribe?url=https://contoso.com"); + }); + + it("should parse software license into secure notes", async () => { + const importer = new PasswordDepot17XmlImporter(); + const result = await importer.parse(SoftwareLicenseTestData); + + const cipher = result.ciphers.shift(); + + expect(cipher.type).toEqual(CipherType.SecureNote); + expect(cipher.name).toBe("software-license type"); + expect(cipher.notes).toBe("someComment"); + + expect(cipher.secureNote).not.toBeNull(); + expect(cipher.secureNote.type).toBe(SecureNoteType.Generic); + + let customField = cipher.fields.find((f) => f.name === "IDS_LicenseProduct"); + expect(customField).toBeDefined(); + expect(customField.value).toEqual("someProduct"); + + customField = cipher.fields.find((f) => f.name === "IDS_LicenseVersion"); + expect(customField).toBeDefined(); + expect(customField.value).toEqual("someVersion"); + + customField = cipher.fields.find((f) => f.name === "IDS_LicenseName"); + expect(customField).toBeDefined(); + expect(customField.value).toEqual("some User"); + + customField = cipher.fields.find((f) => f.name === "IDS_LicenseKey"); + expect(customField).toBeDefined(); + expect(customField.value).toEqual("license-key"); + + customField = cipher.fields.find((f) => f.name === "IDS_LicenseAdditionalKey"); + expect(customField).toBeDefined(); + expect(customField.value).toEqual("additional-license-key"); + + customField = cipher.fields.find((f) => f.name === "IDS_LicenseURL"); + expect(customField).toBeDefined(); + expect(customField.value).toEqual("example.com"); + + customField = cipher.fields.find((f) => f.name === "IDS_LicenseProtected"); + expect(customField).toBeDefined(); + expect(customField.value).toEqual("1"); + + customField = cipher.fields.find((f) => f.name === "IDS_LicenseUserName"); + expect(customField).toBeDefined(); + expect(customField.value).toEqual("someUserName"); + + customField = cipher.fields.find((f) => f.name === "IDS_LicensePassword"); + expect(customField).toBeDefined(); + expect(customField.value).toEqual("somePassword"); + + customField = cipher.fields.find((f) => f.name === "IDS_LicensePurchaseDate"); + expect(customField).toBeDefined(); + const expectedDate = new Date("2025-05-12T00:00:00Z"); + expect(customField.value).toEqual(expectedDate.toLocaleDateString()); + + customField = cipher.fields.find((f) => f.name === "IDS_LicenseOrderNumber"); + expect(customField).toBeDefined(); + expect(customField.value).toEqual("order number"); + + customField = cipher.fields.find((f) => f.name === "IDS_LicenseEmail"); + expect(customField).toBeDefined(); + expect(customField.value).toEqual("someEmail"); + + customField = cipher.fields.find((f) => f.name === "IDS_LicenseExpires"); + expect(customField).toBeDefined(); + expect(customField.value).toEqual("Nie"); + }); + + it("should parse team viewer into login type", async () => { + const importer = new PasswordDepot17XmlImporter(); + const result = await importer.parse(TeamViewerTestData); + + const cipher = result.ciphers.shift(); + + expect(cipher.type).toEqual(CipherType.Login); + expect(cipher.name).toBe("TeamViewer type"); + expect(cipher.notes).toBe("someNote"); + + expect(cipher.login).not.toBeNull(); + expect(cipher.login.password).toBe("somePassword"); + expect(cipher.login.username).toBe(""); + expect(cipher.login.uri).toBe("partnerId"); + + const customField = cipher.fields.find((f) => f.name === "IDS_TeamViewerMode"); + expect(customField).toBeDefined(); + expect(customField.value).toEqual("0"); + }); + + it("should parse putty into login type", async () => { + const importer = new PasswordDepot17XmlImporter(); + const result = await importer.parse(PuttyTestData); + + const cipher = result.ciphers.shift(); + + expect(cipher.type).toEqual(CipherType.Login); + expect(cipher.name).toBe("Putty type"); + expect(cipher.notes).toBe("someNote"); + + expect(cipher.login).not.toBeNull(); + expect(cipher.login.password).toBe("somePassword"); + expect(cipher.login.username).toBe("someUser"); + expect(cipher.login.uri).toBe("localhost"); + + let customField = cipher.fields.find((f) => f.name === "IDS_PuTTyProtocol"); + expect(customField).toBeDefined(); + expect(customField.value).toEqual("0"); + + customField = cipher.fields.find((f) => f.name === "IDS_PuTTyKeyFile"); + expect(customField).toBeDefined(); + expect(customField.value).toEqual("pathToKeyFile"); + + customField = cipher.fields.find((f) => f.name === "IDS_PuTTyKeyPassword"); + expect(customField).toBeDefined(); + expect(customField.value).toEqual("passwordForKeyFile"); + + customField = cipher.fields.find((f) => f.name === "IDS_PuTTyPort"); + expect(customField).toBeDefined(); + expect(customField.value).toEqual("8080"); + }); + + it("should parse banking item type into login type", async () => { + const importer = new PasswordDepot17XmlImporter(); + const result = await importer.parse(BankingTestData); + + const cipher = result.ciphers.shift(); + + expect(cipher.type).toEqual(CipherType.Login); + expect(cipher.name).toBe("banking type"); + expect(cipher.notes).toBe("someNote"); + + expect(cipher.login).not.toBeNull(); + expect(cipher.login.password).toBe("somePassword"); + expect(cipher.login.username).toBe("someUser"); + expect(cipher.login.uri).toBe("http://some-bank.com"); + + let customField = cipher.fields.find((f) => f.name === "IDS_ECHolder"); + expect(customField).toBeDefined(); + expect(customField.value).toEqual("account holder"); + + customField = cipher.fields.find((f) => f.name === "IDS_ECAccountNumber"); + expect(customField).toBeDefined(); + expect(customField.value).toEqual("1234567890"); + + customField = cipher.fields.find((f) => f.name === "IDS_ECBLZ"); + expect(customField).toBeDefined(); + expect(customField.value).toEqual("12345678"); + + customField = cipher.fields.find((f) => f.name === "IDS_ECBankName"); + expect(customField).toBeDefined(); + expect(customField.value).toEqual("someBank"); + + customField = cipher.fields.find((f) => f.name === "IDS_ECBIC"); + expect(customField).toBeDefined(); + expect(customField.value).toEqual("bic"); + + customField = cipher.fields.find((f) => f.name === "IDS_ECIBAN"); + expect(customField).toBeDefined(); + expect(customField.value).toEqual("iban"); + + customField = cipher.fields.find((f) => f.name === "IDS_ECCardNumber"); + expect(customField).toBeDefined(); + expect(customField.value).toEqual("12345678"); + + customField = cipher.fields.find((f) => f.name === "IDS_ECPhone"); + expect(customField).toBeDefined(); + expect(customField.value).toEqual("0049"); + + customField = cipher.fields.find((f) => f.name === "IDS_ECLegitimacyID"); + expect(customField).toBeDefined(); + expect(customField.value).toEqual("1234"); + + customField = cipher.fields.find((f) => f.name === "IDS_ECPIN"); + expect(customField).toBeDefined(); + expect(customField.value).toEqual("123"); + + customField = cipher.fields.find((f) => f.name === "tan_1_value"); + expect(customField).toBeDefined(); + expect(customField.value).toEqual("1234"); + + customField = cipher.fields.find((f) => f.name === "tan_1_used"); + expect(customField).toBeDefined(); + expect(customField.value).toEqual("12.05.2025 15:10:54"); + + // TAN entries + customField = cipher.fields.find((f) => f.name === "tan_1_amount"); + expect(customField).toBeDefined(); + expect(customField.value).toEqual(" 100,00"); + + customField = cipher.fields.find((f) => f.name === "tan_1_comment"); + expect(customField).toBeDefined(); + expect(customField.value).toEqual("some TAN note"); + + customField = cipher.fields.find((f) => f.name === "tan_1_ccode"); + expect(customField).toBeDefined(); + expect(customField.value).toEqual("123"); + + customField = cipher.fields.find((f) => f.name === "tan_2_value"); + expect(customField).toBeDefined(); + expect(customField.value).toEqual("4321"); + + customField = cipher.fields.find((f) => f.name === "tan_2_amount"); + expect(customField).toBeDefined(); + expect(customField.value).toEqual(" 0,00"); + }); + + it("should parse information into secure note type", async () => { + const importer = new PasswordDepot17XmlImporter(); + const result = await importer.parse(InformationTestData); + + const cipher = result.ciphers.shift(); + + expect(cipher.type).toEqual(CipherType.SecureNote); + expect(cipher.name).toBe("information type"); + expect(cipher.notes).toBe("some note content"); + }); + + it("should parse certificate into login type", async () => { + const importer = new PasswordDepot17XmlImporter(); + const result = await importer.parse(CertificateTestData); + + const cipher = result.ciphers.shift(); + + expect(cipher.type).toEqual(CipherType.Login); + expect(cipher.name).toBe("certificate type"); + expect(cipher.notes).toBe("someNote"); + + expect(cipher.login).not.toBeNull(); + expect(cipher.login.password).toBe("somePassword"); + }); + + it("should parse encrypted file into login type", async () => { + const importer = new PasswordDepot17XmlImporter(); + const result = await importer.parse(EncryptedFileTestData); + + const cipher = result.ciphers.shift(); + + expect(cipher.type).toEqual(CipherType.Login); + expect(cipher.name).toBe("encrypted file type"); + expect(cipher.notes).toBe("some comment"); + + expect(cipher.login).not.toBeNull(); + expect(cipher.login.password).toBe("somePassword"); + }); + + it("should parse document type into secure note", async () => { + const importer = new PasswordDepot17XmlImporter(); + const result = await importer.parse(DocumentTestData); + + const cipher = result.ciphers.shift(); + + expect(cipher.type).toEqual(CipherType.SecureNote); + expect(cipher.name).toBe("document type"); + expect(cipher.notes).toBe("document comment"); + + let customField = cipher.fields.find((f) => f.name === "IDS_DocumentSize"); + expect(customField).toBeDefined(); + expect(customField.value).toEqual("27071"); + + customField = cipher.fields.find((f) => f.name === "IDS_DocumentFolder"); + expect(customField).toBeDefined(); + expect(customField.value).toEqual("C:\\Users\\DJSMI\\Downloads\\"); + + customField = cipher.fields.find((f) => f.name === "IDS_DocumentFile"); + expect(customField).toBeDefined(); + expect(customField.value).toEqual("C:\\Users\\DJSMI\\Downloads\\some.pdf"); + }); + + it("should parse favourites and set them on the target item", async () => { + const importer = new PasswordDepot17XmlImporter(); + const result = await importer.parse(PasswordTestData); + + let cipher = result.ciphers.shift(); + expect(cipher.name).toBe("password type"); + expect(cipher.favorite).toBe(false); + + cipher = result.ciphers.shift(); + expect(cipher.name).toBe("password type (2)"); + expect(cipher.favorite).toBe(true); + + cipher = result.ciphers.shift(); + expect(cipher.name).toBe("password type (3)"); + expect(cipher.favorite).toBe(true); + }); + + it("should parse groups nodes into collections when importing into an organization", async () => { + const importer = new PasswordDepot17XmlImporter(); + importer.organizationId = "someOrgId"; + const collection = new CollectionView(); + collection.name = "tempDB"; + const actual = [collection]; + + const result = await importer.parse(PasswordTestData); + expect(result.collections).toEqual(actual); + }); +}); diff --git a/libs/importer/src/importers/password-depot/password-depot-17-xml-importer.ts b/libs/importer/src/importers/password-depot/password-depot-17-xml-importer.ts new file mode 100644 index 00000000000..ab1f6b4689f --- /dev/null +++ b/libs/importer/src/importers/password-depot/password-depot-17-xml-importer.ts @@ -0,0 +1,500 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { CipherType, FieldType, SecureNoteType } from "@bitwarden/common/vault/enums"; +import { CardView } from "@bitwarden/common/vault/models/view/card.view"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { FieldView } from "@bitwarden/common/vault/models/view/field.view"; +import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; +import { IdentityView } from "@bitwarden/common/vault/models/view/identity.view"; +import { LoginView } from "@bitwarden/common/vault/models/view/login.view"; +import { SecureNoteView } from "@bitwarden/common/vault/models/view/secure-note.view"; + +import { ImportResult } from "../../models/import-result"; +import { BaseImporter } from "../base-importer"; +import { Importer } from "../importer"; + +import { PasswordDepotItemType, PasswordDepotCustomFieldType } from "./types"; + +/** + * Importer for Password Depot 17 xml files. + * @see https://www.password-depot.de/ + * It provides methods to parse the XML data, extract relevant information, and create cipher objects + */ +export class PasswordDepot17XmlImporter extends BaseImporter implements Importer { + result = new ImportResult(); + + _favouritesLookupTable = new Set(); + + // Parse the XML data from the Password Depot export file and extracts the relevant information + parse(data: string): Promise { + const doc: XMLDocument = this.parseXml(data); + if (doc == null) { + this.result.success = false; + return Promise.resolve(this.result); + } + + // Check if the root node is present + const rootNode = doc.querySelector("passwordfile"); + if (rootNode == null) { + this.result.errorMessage = "Missing `passwordfile` node."; + this.result.success = false; + return Promise.resolve(this.result); + } + + // Check if the version is supported + const headerNode = this.querySelectorDirectChild(rootNode, "header"); + if (headerNode == null) { + this.result.success = false; + return Promise.resolve(this.result); + } + + let versionNode = this.querySelectorDirectChild(headerNode, "version"); + if (versionNode == null) { + // Adding a fallback for MacOS Password Depot 17.0 export files + // These files do not have a version node, but a dataformat node instead + versionNode = this.querySelectorDirectChild(headerNode, "dataformat"); + if (versionNode == null) { + this.result.success = false; + return Promise.resolve(this.result); + } + } + + const version = versionNode.textContent; + if (!version.startsWith("17")) { + this.result.errorMessage = "Unsupported export version detected - (only 17.0 is supported)"; + this.result.success = false; + return Promise.resolve(this.result); + } + + // Abort import if the file is encrypted + const encryptedNode = this.querySelectorDirectChild(headerNode, "encrypted"); + if (encryptedNode != null && encryptedNode.textContent == "True") { + this.result.errorMessage = "Encrypted Password Depot files are not supported."; + this.result.success = false; + return Promise.resolve(this.result); + } + + // Check if the passwords node is present + // This node contains all the password entries + const passwordsNode = rootNode.querySelector("passwords"); + if (passwordsNode == null) { + this.result.errorMessage = "Missing `passwordfile > passwords` node."; + this.result.success = false; + return Promise.resolve(this.result); + } + + this.buildFavouritesLookupTable(rootNode); + + this.querySelectorAllDirectChild(passwordsNode, "group").forEach((group) => { + this.traverse(group, ""); + }); + + if (this.organization) { + this.moveFoldersToCollections(this.result); + } + + this.result.success = true; + return Promise.resolve(this.result); + } + + // Traverses the XML tree and processes each node + // It starts from the root node and goes through each group and item + // This method is recursive and handles nested groups + private traverse(node: Element, groupPrefixName: string) { + const folderIndex = this.result.folders.length; + let groupName = groupPrefixName; + + if (groupName !== "") { + groupName += "/"; + } + + // Check if the group has a fingerprint attribute (GUID of a folder) + const groupFingerprint = node.attributes.getNamedItem("fingerprint"); + if (groupFingerprint?.textContent != "" && groupFingerprint.textContent != "null") { + const nameEl = node.attributes.getNamedItem("name"); + groupName += nameEl == null ? "-" : nameEl.textContent; + const folder = new FolderView(); + folder.name = groupName; + this.result.folders.push(folder); + } + + this.querySelectorAllDirectChild(node, "item").forEach((entry) => { + const cipherIndex = this.result.ciphers.length; + + const cipher = this.initLoginCipher(); + //Set default item type similar how we default to Login in other importers + let sourceType: PasswordDepotItemType = PasswordDepotItemType.Password; + + const entryFields = entry.children; + for (let i = 0; i < entryFields.length; i++) { + const entryField = entryFields[i]; + + // Skip processing historical entries + if (entryField.tagName === "hitems") { + continue; + } + + if (entryField.tagName === "description") { + cipher.name = entryField.textContent; + continue; + } + + if (entryField.tagName === "comment") { + cipher.notes = entryField.textContent; + continue; + } + + if (entryField.tagName === "type") { + sourceType = entryField.textContent as PasswordDepotItemType; + switch (sourceType) { + case PasswordDepotItemType.Password: + case PasswordDepotItemType.RDP: + case PasswordDepotItemType.Putty: + case PasswordDepotItemType.TeamViewer: + case PasswordDepotItemType.Banking: + case PasswordDepotItemType.Certificate: + case PasswordDepotItemType.EncryptedFile: + cipher.type = CipherType.Login; + cipher.login = new LoginView(); + break; + case PasswordDepotItemType.CreditCard: + cipher.type = CipherType.Card; + cipher.card = new CardView(); + break; + case PasswordDepotItemType.SoftwareLicense: + case PasswordDepotItemType.Information: + case PasswordDepotItemType.Document: + cipher.type = CipherType.SecureNote; + cipher.secureNote = new SecureNoteView(); + cipher.secureNote.type = SecureNoteType.Generic; + break; + case PasswordDepotItemType.Identity: + cipher.type = CipherType.Identity; + cipher.identity = new IdentityView(); + break; + } + continue; + } + + if ( + sourceType === PasswordDepotItemType.Password || + sourceType === PasswordDepotItemType.RDP || + sourceType === PasswordDepotItemType.Putty || + sourceType === PasswordDepotItemType.TeamViewer || + sourceType === PasswordDepotItemType.Banking || + sourceType === PasswordDepotItemType.Certificate || + sourceType === PasswordDepotItemType.EncryptedFile + ) { + if (this.parseLoginFields(entryField, cipher)) { + continue; + } + } + + // fingerprint is the GUID of the entry + // Base on the previously parsed favourites, we can identify an entry and set the favorite flag accordingly + if (entryField.tagName === "fingerprint") { + if (this._favouritesLookupTable.has(entryField.textContent)) { + cipher.favorite = true; + } + } + + if (entryField.tagName === "customfields") { + this.parseCustomFields(entryField, sourceType, cipher); + continue; + } + + if (sourceType === PasswordDepotItemType.Banking && entryField.tagName === "tans") { + this.querySelectorAllDirectChild(entryField, "tan").forEach((tanEntry) => { + this.parseBankingTANs(tanEntry, cipher); + }); + continue; + } + + this.processKvp(cipher, entryField.tagName, entryField.textContent, FieldType.Text); + } + + this.cleanupCipher(cipher); + this.result.ciphers.push(cipher); + + if (groupName !== "") { + this.result.folderRelationships.push([cipherIndex, folderIndex]); + } + }); + + this.querySelectorAllDirectChild(node, "group").forEach((group) => { + this.traverse(group, groupName); + }); + } + + // Parses custom fields and adds them to the cipher + // It iterates through all the custom fields and adds them to the cipher + private parseCustomFields( + entryField: Element, + sourceType: PasswordDepotItemType, + cipher: CipherView, + ) { + this.querySelectorAllDirectChild(entryField, "field").forEach((customField) => { + const customFieldObject = this.parseCustomField(customField); + if (customFieldObject == null) { + return; + } + + switch (sourceType) { + case PasswordDepotItemType.CreditCard: + if (this.parseCreditCardCustomFields(customFieldObject, cipher)) { + return; + } + break; + case PasswordDepotItemType.Identity: + if (this.parseIdentityCustomFields(customFieldObject, cipher)) { + return; + } + break; + case PasswordDepotItemType.Information: + if (this.parseInformationCustomFields(customFieldObject, cipher)) { + return; + } + break; + default: + // For other types, we will process the custom field as a regular key-value pair + break; + } + + this.processKvp( + cipher, + customFieldObject.name, + customFieldObject.value, + customFieldObject.type, + ); + }); + } + + // Parses login fields and adds them to the cipher + private parseLoginFields(entryField: Element, cipher: CipherView): boolean { + if (entryField.tagName === "username") { + cipher.login.username = entryField.textContent; + return true; + } + + if (entryField.tagName === "password") { + cipher.login.password = entryField.textContent; + return true; + } + + if (entryField.tagName === "url") { + cipher.login.uris = this.makeUriArray(entryField.textContent); + return true; + } + + return false; + } + + // Parses a custom field and adds it to the cipher + private parseCustomField(customField: Element): FieldView | null { + let key: string = undefined; + let value: string = undefined; + let sourceFieldType: PasswordDepotCustomFieldType = PasswordDepotCustomFieldType.Memo; + let visible: string = undefined; + // A custom field is represented by a element + // On exports from the Windows clients: It contains a , , and optionally a and element + // On exports from the MacOs clients the key-values are defined as xml attributes instead of child nodes + if (customField.hasAttributes()) { + key = customField.getAttribute("name"); + if (key == null) { + return null; + } + + value = customField.getAttribute("value"); + + const typeAttr = customField.getAttribute("type"); + sourceFieldType = + typeAttr != null + ? (typeAttr as PasswordDepotCustomFieldType) + : PasswordDepotCustomFieldType.Memo; + + visible = customField.getAttribute("visible"); + } else { + const keyEl = this.querySelectorDirectChild(customField, "name"); + key = keyEl != null ? keyEl.textContent : null; + + if (key == null) { + return null; + } + + const valueEl = this.querySelectorDirectChild(customField, "value"); + value = valueEl != null ? valueEl.textContent : null; + + const typeEl = this.querySelectorDirectChild(customField, "type"); + sourceFieldType = + typeEl != null + ? (typeEl.textContent as PasswordDepotCustomFieldType) + : PasswordDepotCustomFieldType.Memo; + + const visibleEl = this.querySelectorDirectChild(customField, "visible"); + visible = visibleEl != null ? visibleEl.textContent : null; + } + + if (sourceFieldType === PasswordDepotCustomFieldType.Date) { + if (!isNaN(value as unknown as number)) { + // Convert excel date format to JavaScript date + const numericValue = parseInt(value); + const secondsInDay = 86400; + const missingLeapYearDays = secondsInDay * 1000; + value = new Date((numericValue - (25567 + 2)) * missingLeapYearDays).toLocaleDateString(); + } + } + + if (sourceFieldType === PasswordDepotCustomFieldType.Password) { + return { name: key, value: value, type: FieldType.Hidden, linkedId: null } as FieldView; + } + + if (sourceFieldType === PasswordDepotCustomFieldType.Boolean) { + return { name: key, value: value, type: FieldType.Boolean, linkedId: null } as FieldView; + } + + if (visible == "0") { + return { name: key, value: value, type: FieldType.Hidden, linkedId: null } as FieldView; + } + + return { name: key, value: value, type: FieldType.Text, linkedId: null } as FieldView; + } + + // Parses credit card fields and adds them to the cipher + private parseCreditCardCustomFields(customField: FieldView, cipher: CipherView): boolean { + if (customField.name === "IDS_CardHolder") { + cipher.card.cardholderName = customField.value; + return true; + } + + if (customField.name === "IDS_CardNumber") { + cipher.card.number = customField.value; + cipher.card.brand = CardView.getCardBrandByPatterns(cipher.card.number); + return true; + } + + if (customField.name === "IDS_CardExpires") { + this.setCardExpiration(cipher, customField.value); + return true; + } + + if (customField.name === "IDS_CardCode") { + cipher.card.code = customField.value; + return true; + } + + return false; + } + + // Parses identity fields and adds them to the cipher + private parseIdentityCustomFields(customField: FieldView, cipher: CipherView): boolean { + if (customField.name === "IDS_IdentityName") { + this.processFullName(cipher, customField.value); + return true; + } + + if (customField.name === "IDS_IdentityEmail") { + cipher.identity.email = customField.value; + return true; + } + + if (customField.name === "IDS_IdentityFirstName") { + cipher.identity.firstName = customField.value; + return true; + } + + if (customField.name === "IDS_IdentityLastName") { + cipher.identity.lastName = customField.value; + return true; + } + + if (customField.name === "IDS_IdentityCompany") { + cipher.identity.company = customField.value; + return true; + } + + if (customField.name === "IDS_IdentityAddress1") { + cipher.identity.address1 = customField.value; + return true; + } + + if (customField.name === "IDS_IdentityAddress2") { + cipher.identity.address2 = customField.value; + return true; + } + + if (customField.name === "IDS_IdentityCity") { + cipher.identity.city = customField.value; + return true; + } + + if (customField.name === "IDS_IdentityState") { + cipher.identity.state = customField.value; + return true; + } + + if (customField.name === "IDS_IdentityZIP") { + cipher.identity.postalCode = customField.value; + return true; + } + + if (customField.name === "IDS_IdentityCountry") { + cipher.identity.country = customField.value; + return true; + } + + if (customField.name === "IDS_IdentityPhone") { + cipher.identity.phone = customField.value; + return true; + } + + return false; + } + + // Parses information custom fields and adds them to the cipher + private parseInformationCustomFields(customField: FieldView, cipher: CipherView): boolean { + if (customField.name === "IDS_InformationText") { + cipher.notes = customField.value; + return true; + } + + return false; + } + + // Parses TAN objects and adds them to the cipher + // It iterates through all the TAN fields and adds them to the cipher + private parseBankingTANs(TANsField: Element, cipher: CipherView) { + let tanNumber = "0"; + const entryFields = TANsField.children; + for (let i = 0; i < entryFields.length; i++) { + const entryField = entryFields[i]; + + if (entryField.tagName === "number") { + tanNumber = entryField.textContent; + continue; + } + + this.processKvp(cipher, `tan_${tanNumber}_${entryField.tagName}`, entryField.textContent); + } + } + + // Parses the favourites-node from the XML file, which contains a base64 encoded string + // The string contains the fingerprints/GUIDs of the favourited entries, separated by new lines + private buildFavouritesLookupTable(rootNode: Element): void { + const favouritesNode = this.querySelectorDirectChild(rootNode, "favorites"); + if (favouritesNode == null) { + return; + } + + const decodedBase64String = atob(favouritesNode.textContent); + if (decodedBase64String.indexOf("\r\n") > 0) { + decodedBase64String.split("\r\n").forEach((line) => { + this._favouritesLookupTable.add(line); + }); + return; + } + + decodedBase64String.split("\n").forEach((line) => { + this._favouritesLookupTable.add(line); + }); + } +} diff --git a/libs/importer/src/importers/password-depot/types/index.ts b/libs/importer/src/importers/password-depot/types/index.ts new file mode 100644 index 00000000000..709e5992ae8 --- /dev/null +++ b/libs/importer/src/importers/password-depot/types/index.ts @@ -0,0 +1,2 @@ +export * from "./password-depot-item-type"; +export * from "./password-depot-custom-field-type"; diff --git a/libs/importer/src/importers/password-depot/types/password-depot-custom-field-type.ts b/libs/importer/src/importers/password-depot/types/password-depot-custom-field-type.ts new file mode 100644 index 00000000000..166378b38b4 --- /dev/null +++ b/libs/importer/src/importers/password-depot/types/password-depot-custom-field-type.ts @@ -0,0 +1,15 @@ +/** This object represents the different custom field types in Password Depot */ +export const PasswordDepotCustomFieldType = Object.freeze({ + Password: "1", + Memo: "2", + Date: "3", + Number: "4", + Boolean: "5", + Decimal: "6", + Email: "7", + URL: "8", +} as const); + +/** This type represents the different custom field types in Password Depot */ +export type PasswordDepotCustomFieldType = + (typeof PasswordDepotCustomFieldType)[keyof typeof PasswordDepotCustomFieldType]; diff --git a/libs/importer/src/importers/password-depot/types/password-depot-item-type.ts b/libs/importer/src/importers/password-depot/types/password-depot-item-type.ts new file mode 100644 index 00000000000..04ec3a48c85 --- /dev/null +++ b/libs/importer/src/importers/password-depot/types/password-depot-item-type.ts @@ -0,0 +1,19 @@ +/** This object represents the different item types in Password Depot */ +export const PasswordDepotItemType = Object.freeze({ + Password: "0", + CreditCard: "1", + SoftwareLicense: "2", + Identity: "3", + Information: "4", + Banking: "5", + EncryptedFile: "6", + Document: "7", + RDP: "8", + Putty: "9", + TeamViewer: "10", + Certificate: "11", +} as const); + +/** This type represents the different item types in Password Depot */ +export type PasswordDepotItemType = + (typeof PasswordDepotItemType)[keyof typeof PasswordDepotItemType]; diff --git a/libs/importer/src/importers/spec-data/password-depot-xml/banking.xml.ts b/libs/importer/src/importers/spec-data/password-depot-xml/banking.xml.ts new file mode 100644 index 00000000000..3a2da1c27bf --- /dev/null +++ b/libs/importer/src/importers/spec-data/password-depot-xml/banking.xml.ts @@ -0,0 +1,283 @@ +export const BankingTestData = ` + +
+ Password Depot + 17.0.0 + + + CCDA8015-7F21-4344-BDF0-9EA6AF8AE31D + 12.05.2025 15:16:11 + False + 0 + 30.12.1899 00:00:00 + +
+ + + + banking type + 5 + somePassword + someUser + some-bank.com + someNote + 12.05.2025 15:11:29 + 02.05.2027 + 1 + DEB91652-52C6-402E-9D44-3557829BC6DF + + 127 + 0 + + + Allgemein + + 0 + + + 12.05.2025 15:09:17 + 12.05.2025 15:09:17 + 0 + 0 + 0 + 1 + + 0 + 2 + + DJSMI + + + 0 + + 0 + + 0 + + 161 + 0 + + + + IDS_ECHolder + account holder + -1 + 0 + 0 + + + IDS_ECAccountNumber + 1234567890 + -1 + 0 + 0 + + + IDS_ECBLZ + 12345678 + -1 + 0 + 0 + + + IDS_ECBankName + someBank + -1 + 0 + 0 + + + IDS_ECBIC + bic + -1 + 0 + 0 + + + IDS_ECIBAN + iban + -1 + 0 + 0 + + + IDS_ECCardNumber + 12345678 + -1 + 0 + 10 + + + IDS_ECPhone + 0049 + 0 + 0 + 0 + + + IDS_ECLegitimacyID + 1234 + -1 + 0 + 0 + + + IDS_ECPIN + 123 + 0 + 0 + 1 + + + + + 1 + 1234 + 12.05.2025 15:10:54 + 100,00 + some TAN note + 123 + + + 2 + 4321 + 0,00 + + + + + + + banking type + 5 + somePassword + someUser + some-bank.com + someNote + 12.05.2025 15:10:35 + 02.05.2027 + 1 + DEB91652-52C6-402E-9D44-3557829BC6DF + + 127 + 0 + + + Allgemein + + 0 + + + 12.05.2025 15:09:17 + 12.05.2025 15:09:17 + 0 + 0 + 0 + 1 + + 0 + 2 + + DJSMI + + + 0 + + 0 + + 0 + + 161 + 0 + + + + IDS_ECHolder + account holder + -1 + 0 + 0 + + + IDS_ECAccountNumber + 1234567890 + -1 + 0 + 0 + + + IDS_ECBLZ + 12345678 + -1 + 0 + 0 + + + IDS_ECBankName + someBank + -1 + 0 + 0 + + + IDS_ECBIC + bic + -1 + 0 + 0 + + + IDS_ECIBAN + iban + -1 + 0 + 0 + + + IDS_ECCardNumber + 12345678 + -1 + 0 + 10 + + + IDS_ECPhone + 0049 + 0 + 0 + 0 + + + IDS_ECLegitimacyID + 1234 + -1 + 0 + 0 + + + IDS_ECPIN + 123 + 0 + 0 + 1 + + + + + + + + + + + + + QUY3NEZGODYtRkUzOS00NTg0LThFOTYtRkU5NTBDMjg5REY4DQo= + + + + + + QWxsZ2VtZWluDQpIb21lIEJhbmtpbmcNCkludGVybmV0DQpQcml2YXQNCldpbmRvd3MNCg== + +
`; diff --git a/libs/importer/src/importers/spec-data/password-depot-xml/certificate.xml.ts b/libs/importer/src/importers/spec-data/password-depot-xml/certificate.xml.ts new file mode 100644 index 00000000000..eb8c463d57f --- /dev/null +++ b/libs/importer/src/importers/spec-data/password-depot-xml/certificate.xml.ts @@ -0,0 +1,76 @@ +export const CertificateTestData = ` + +
+ Password Depot + 17.0.0 + + + CCDA8015-7F21-4344-BDF0-9EA6AF8AE31D + 12.05.2025 15:16:11 + False + 0 + 30.12.1899 00:00:00 + +
+ + + + certificate type + 11 + somePassword + + + someNote + 12.05.2025 15:15:57 + 00.00.0000 + 1 + 21288702-B042-46D9-9DDF-B44A5CD04A72 + + 130 + 0 + + + Allgemein + + 0 + + + 12.05.2025 15:15:26 + 12.05.2025 15:15:26 + 0 + 0 + 0 + 1 + + 0 + 2 + someTag + DJSMI + + + 0 + + 0 + + 0 + + 161 + 0 + + + + + + + + + + QUY3NEZGODYtRkUzOS00NTg0LThFOTYtRkU5NTBDMjg5REY4DQo= + + + + + + QWxsZ2VtZWluDQpIb21lIEJhbmtpbmcNCkludGVybmV0DQpQcml2YXQNCldpbmRvd3MNCg== + +
`; diff --git a/libs/importer/src/importers/spec-data/password-depot-xml/credit-card.xml.ts b/libs/importer/src/importers/spec-data/password-depot-xml/credit-card.xml.ts new file mode 100644 index 00000000000..f0af49bbfae --- /dev/null +++ b/libs/importer/src/importers/spec-data/password-depot-xml/credit-card.xml.ts @@ -0,0 +1,148 @@ +export const CreditCardTestData = ` + +
+ Password Depot + 17.0.0 + + + CCDA8015-7F21-4344-BDF0-9EA6AF8AE31D + 12.05.2025 15:16:11 + False + 0 + 30.12.1899 00:00:00 + +
+ + + + some CreditCard + 1 + 4222422242224222 + some CC holder + + someComment + 08.05.2025 12:09:47 + 08.05.2026 + 1 + DD9B52F8-B2CE-42C2-A891-5E20DA23EA20 + + 126 + 0 + + + + + 0 + + + 08.05.2025 12:08:48 + 08.05.2025 12:08:48 + 0 + 0 + 0 + 1 + + 0 + 2 + + DJSMI + + + 0 + + 0 + + 0 + + 161 + 0 + + + + IDS_CardType + 0 + 0 + 0 + 4 + + + IDS_CardHolder + some CC holder + -1 + 0 + 0 + + + IDS_CardNumber + 4222422242224222 + -1 + 0 + 10 + + + IDS_CardExpires + 05/2026 + -1 + 0 + 3 + + + IDS_CardCode + 123 + -1 + 0 + 0 + + + IDS_CardPhone + + -1 + 0 + 0 + + + IDS_CardURL + + -1 + 0 + 8 + + + IDS_CardAdditionalCode + + -1 + 0 + 0 + + + IDS_CardAdditionalInfo + + -1 + 0 + 0 + + + IDS_CardPIN + 123 + -1 + 0 + 1 + + + + + + + + + + + QUY3NEZGODYtRkUzOS00NTg0LThFOTYtRkU5NTBDMjg5REY4DQo= + + + + + + QWxsZ2VtZWluDQpIb21lIEJhbmtpbmcNCkludGVybmV0DQpQcml2YXQNCldpbmRvd3MNCg== + +
`; diff --git a/libs/importer/src/importers/spec-data/password-depot-xml/document.xml.ts b/libs/importer/src/importers/spec-data/password-depot-xml/document.xml.ts new file mode 100644 index 00000000000..4f607c9b048 --- /dev/null +++ b/libs/importer/src/importers/spec-data/password-depot-xml/document.xml.ts @@ -0,0 +1,99 @@ +export const DocumentTestData = ` + +
+ Password Depot + 17.0.0 + + + CCDA8015-7F21-4344-BDF0-9EA6AF8AE31D + 12.05.2025 15:16:11 + False + 0 + 30.12.1899 00:00:00 + +
+ + + + document type + 7 + + + + document comment + 03.06.2025 17:45:30 + 00.00.0000 + 1 + 1B8E7F2C-9229-43C6-AB89-42101809C822 + + 133 + 0 + + + Allgemein + + 0 + + + 05.06.2025 21:49:49 + 30.12.1899 00:00:00 + 0 + 0 + 0 + 1 + + 0 + 2 + + DJSMI + + + 0 + + 0 + + 0 + + 161 + 0 + + + + IDS_DocumentSize + 27071 + 0 + 0 + 4 + + + IDS_DocumentFolder + C:\\Users\\DJSMI\\Downloads\\ + 0 + 0 + 0 + + + IDS_DocumentFile + C:\\Users\\DJSMI\\Downloads\\some.pdf + 0 + 0 + 0 + + + + + + + + + + + QUY3NEZGODYtRkUzOS00NTg0LThFOTYtRkU5NTBDMjg5REY4DQo= + + + + + + QWxsZ2VtZWluDQpIb21lIEJhbmtpbmcNCkludGVybmV0DQpQcml2YXQNCldpbmRvd3MNCg== + +
`; diff --git a/libs/importer/src/importers/spec-data/password-depot-xml/encrypted-file.xml.ts b/libs/importer/src/importers/spec-data/password-depot-xml/encrypted-file.xml.ts new file mode 100644 index 00000000000..2d2e929440a --- /dev/null +++ b/libs/importer/src/importers/spec-data/password-depot-xml/encrypted-file.xml.ts @@ -0,0 +1,76 @@ +export const EncryptedFileTestData = ` + +
+ Password Depot + 17.0.0 + + + CCDA8015-7F21-4344-BDF0-9EA6AF8AE31D + 12.05.2025 15:16:11 + False + 0 + 30.12.1899 00:00:00 + +
+ + + + encrypted file type + 6 + somePassword + + + some comment + 12.05.2025 15:15:17 + 00.00.0000 + 1 + E4CA245D-A326-4359-9488-CC207B33C6C0 + + 132 + 0 + + + Allgemein + + 0 + + + 12.05.2025 15:14:58 + 12.05.2025 15:14:58 + 0 + 0 + 0 + 1 + + 0 + 2 + + DJSMI + + + 0 + + 0 + + 0 + + 161 + 0 + + + + + + + + + + QUY3NEZGODYtRkUzOS00NTg0LThFOTYtRkU5NTBDMjg5REY4DQo= + + + + + + QWxsZ2VtZWluDQpIb21lIEJhbmtpbmcNCkludGVybmV0DQpQcml2YXQNCldpbmRvd3MNCg== + +
`; diff --git a/libs/importer/src/importers/spec-data/password-depot-xml/identity.xml.ts b/libs/importer/src/importers/spec-data/password-depot-xml/identity.xml.ts new file mode 100644 index 00000000000..dfa275aa778 --- /dev/null +++ b/libs/importer/src/importers/spec-data/password-depot-xml/identity.xml.ts @@ -0,0 +1,197 @@ +export const IdentityTestData = ` + +
+ Password Depot + 17.0.0 + + + CCDA8015-7F21-4344-BDF0-9EA6AF8AE31D + 12.05.2025 15:16:11 + False + 0 + 30.12.1899 00:00:00 + +
+ + + + identity type + 3 + + account-name/id + website + someNote + 12.05.2025 15:14:33 + 00.00.0000 + 1 + 0E6085E9-7560-4826-814E-EFE1724E1377 + + 129 + 0 + + + Allgemein + + 0 + + + 12.05.2025 15:12:52 + 12.05.2025 15:12:52 + 0 + 0 + 0 + 1 + + 0 + 2 + + DJSMI + + + 0 + + 0 + + 0 + + 161 + 0 + + + + IDS_IdentityName + account-name/id + -1 + 0 + 0 + + + IDS_IdentityEmail + email + -1 + 0 + 0 + + + IDS_IdentityFirstName + firstName + -1 + 0 + 0 + + + IDS_IdentityLastName + surName + -1 + 0 + 0 + + + IDS_IdentityCompany + someCompany + -1 + 0 + 0 + + + IDS_IdentityAddress1 + someStreet + -1 + 0 + 0 + + + IDS_IdentityAddress2 + address 2 + -1 + 0 + 0 + + + IDS_IdentityCity + town + -1 + 0 + 0 + + + IDS_IdentityState + state + -1 + 0 + 0 + + + IDS_IdentityZIP + zipCode + -1 + 0 + 0 + + + IDS_IdentityCountry + country + -1 + 0 + 0 + + + IDS_IdentityPhone + phoneNumber + -1 + 0 + 0 + + + IDS_IdentityWebsite + website + -1 + 0 + 8 + + + IDS_IdentityBirthDate + 45789 + -1 + 0 + 3 + + + IDS_IdentityMobile + mobileNumber + -1 + 0 + 0 + + + IDS_IdentityFax + faxNumber + -1 + 0 + 0 + + + IDS_IdentityHouseNumber + 123 + -1 + 0 + 0 + + + + + + + + + + + QUY3NEZGODYtRkUzOS00NTg0LThFOTYtRkU5NTBDMjg5REY4DQo= + + + + + + QWxsZ2VtZWluDQpIb21lIEJhbmtpbmcNCkludGVybmV0DQpQcml2YXQNCldpbmRvd3MNCg== + +
`; diff --git a/libs/importer/src/importers/spec-data/password-depot-xml/index.ts b/libs/importer/src/importers/spec-data/password-depot-xml/index.ts new file mode 100644 index 00000000000..6d5903ed471 --- /dev/null +++ b/libs/importer/src/importers/spec-data/password-depot-xml/index.ts @@ -0,0 +1,19 @@ +export { InvalidRootNodeData } from "./missing-root-node.xml"; +export { MissingPasswordsNodeData } from "./missing-passwords-node.xml"; +export { InvalidVersionData } from "./wrong-version.xml"; +export { EncryptedFileData } from "./noop-encrypted-file.xml"; +export { PasswordTestData } from "./password.xml"; +export { CreditCardTestData } from "./credit-card.xml"; +export { IdentityTestData } from "./identity.xml"; +export { RDPTestData } from "./rdp.xml"; +export { SoftwareLicenseTestData } from "./software-license.xml"; +export { TeamViewerTestData } from "./team-viewer.xml"; +export { PuttyTestData } from "./putty.xml"; +export { BankingTestData } from "./banking.xml"; +export { InformationTestData } from "./information.xml"; +export { CertificateTestData } from "./certificate.xml"; +export { EncryptedFileTestData } from "./encrypted-file.xml"; +export { DocumentTestData } from "./document.xml"; +export { MacOS_WrongVersion } from "./macos-wrong-version.xml"; +export { MacOS_PasswordDepotXmlFile } from "./macos-customfields.xml"; +export { MacOS_MultipleFolders } from "./macos-multiple-folders.xml"; diff --git a/libs/importer/src/importers/spec-data/password-depot-xml/information.xml.ts b/libs/importer/src/importers/spec-data/password-depot-xml/information.xml.ts new file mode 100644 index 00000000000..1f07882ea64 --- /dev/null +++ b/libs/importer/src/importers/spec-data/password-depot-xml/information.xml.ts @@ -0,0 +1,85 @@ +export const InformationTestData = ` + +
+ Password Depot + 17.0.0 + + + CCDA8015-7F21-4344-BDF0-9EA6AF8AE31D + 12.05.2025 15:16:11 + False + 0 + 30.12.1899 00:00:00 + +
+ + + + information type + 4 + + + + + 12.05.2025 15:14:54 + 00.00.0000 + 1 + 546AFAE7-6F64-4040-838B-AFE691580356 + + 131 + 0 + + + Allgemein + + 0 + + + 12.05.2025 15:14:39 + 12.05.2025 15:14:39 + 0 + 0 + 0 + 1 + + 0 + 2 + + DJSMI + + + 0 + + 0 + + 0 + + 161 + 0 + + + + IDS_InformationText + some note content + 0 + 0 + 2 + + + + + + + + + + + QUY3NEZGODYtRkUzOS00NTg0LThFOTYtRkU5NTBDMjg5REY4DQo= + + + + + + QWxsZ2VtZWluDQpIb21lIEJhbmtpbmcNCkludGVybmV0DQpQcml2YXQNCldpbmRvd3MNCg== + +
`; diff --git a/libs/importer/src/importers/spec-data/password-depot-xml/macos-customfields.xml.ts b/libs/importer/src/importers/spec-data/password-depot-xml/macos-customfields.xml.ts new file mode 100644 index 00000000000..d83eae6bb6d --- /dev/null +++ b/libs/importer/src/importers/spec-data/password-depot-xml/macos-customfields.xml.ts @@ -0,0 +1,42 @@ +export const MacOS_PasswordDepotXmlFile = ` + +
+ Password Depot + 0 + 0 + 0 + 1 + 1 + 17 + 30.12.1899 00:00:00 + 23.06.2025 16:30:50 + 25.06.2025 14:30:47 + 2C1A154A-3BB0-4871-9537-3634DE303F8E + 7 +
+ + + + card 1 + 1 + comment + 23.06.2025 16:14:33 + EBF4AC3D-86C9-49BE-826B-BAE5FF9E3575 + 23.06.2025 16:13:40 + 25.06.2025 14:17:10 + + + + + + + + + + + + + + + +
`; diff --git a/libs/importer/src/importers/spec-data/password-depot-xml/macos-multiple-folders.xml.ts b/libs/importer/src/importers/spec-data/password-depot-xml/macos-multiple-folders.xml.ts new file mode 100644 index 00000000000..174e9415fa1 --- /dev/null +++ b/libs/importer/src/importers/spec-data/password-depot-xml/macos-multiple-folders.xml.ts @@ -0,0 +1,215 @@ +export const MacOS_MultipleFolders = ` + +
+ Password Depot + 0 + 0 + 0 + 1 + 1 + 17 + 30.12.1899 00:00:00 + 27.06.2025 10:39:07 + 27.06.2025 10:39:27 + 7DCDD3FA-F512-4CD4-AEED-DE2A4C8375CF + 7 +
+ + + + remote desktop + 8 + pass + username + compjter + comment + 26.06.2025 16:04:57 + 81316050-9B9C-4D9B-9549-45B52A0BE6BB + + Private + 26.06.2025 16:04:32 + 27.06.2025 10:28:02 + tgmac + + + teamviewer + 10 + pass + partnerid + comment + 26.06.2025 16:05:28 + 26.06.2025 + 8AAACC16-4FD4-4E52-9C1F-6979B051B510 + Internet + 26.06.2025 16:05:03 + 27.06.2025 10:28:02 + tag + + + + + + ec card + 5 + pass + user + url + 26.06.2025 16:08:35 + B5C148A4-C408-427C-B69C-F88E7C529FA4 + + 26.06.2025 16:08:00 + 27.06.2025 10:28:02 + + + + + + + + + + + + + + + identity + 3 + 26.06.2025 16:09:50 + 87574AD4-8844-4A01-9381-AFF0907198A3 + 26.06.2025 16:09:19 + 27.06.2025 10:28:02 + + + + + + + + + + + + + + + + + + + + + + credit card + 1 + comment + 26.06.2025 19:07:38 + E98E3CBA-1578-48AD-8E41-CFD3280045BB + 26.06.2025 16:06:32 + 27.06.2025 10:28:02 + + + + + + + + + + + + + + + + password + passmac + usernam + comment + 26.06.2025 16:04:30 + DE8AD61B-8EC0-4E72-9BC8-971E80712B50 + + General + 26.06.2025 16:04:04 + 27.06.2025 10:28:02 + tagmac + + + informationb + 4 + 26.06.2025 16:10:01 + 7E9E6941-BB3B-47F2-9E43-33F900EBBF95 + Banking + 26.06.2025 16:09:53 + 27.06.2025 10:28:02 + + + + + + certificat + 11 + passsss + comment + 26.06.2025 16:10:28 + 26.06.2025 + 1F36748F-0374-445E-B020-282EAE26259F + Internet + 26.06.2025 16:10:10 + 27.06.2025 10:28:02 + tag + + + + + putty + 9 + pass + username + host + comment + 26.06.2025 16:06:08 + 26.06.2025 + 7947A949-98F0-4F26-BE12-5FFAFE7601C8 + + Banking + 26.06.2025 16:05:38 + 27.06.2025 10:28:02 + tag + + + + + + + + + soft license + 2 + 26.06.2025 16:09:02 + 2A5CF4C1-70D0-4F27-A1DE-4CFEF5FB71CF + 26.06.2025 16:08:43 + 27.06.2025 10:28:02 + + + + + + + + + + + + + + + + + + + +
`; diff --git a/libs/importer/src/importers/spec-data/password-depot-xml/macos-wrong-version.xml.ts b/libs/importer/src/importers/spec-data/password-depot-xml/macos-wrong-version.xml.ts new file mode 100644 index 00000000000..771bf813e48 --- /dev/null +++ b/libs/importer/src/importers/spec-data/password-depot-xml/macos-wrong-version.xml.ts @@ -0,0 +1,21 @@ +export const MacOS_WrongVersion = ` + +
+ Password Depot + 0 + 0 + 0 + 1 + 1 + 18 + 30.12.1899 00:00:00 + 23.06.2025 16:30:50 + 25.06.2025 14:30:47 + 2C1A154A-3BB0-4871-9537-3634DE303F8E + 7 +
+ + + + +
`; diff --git a/libs/importer/src/importers/spec-data/password-depot-xml/missing-passwords-node.xml.ts b/libs/importer/src/importers/spec-data/password-depot-xml/missing-passwords-node.xml.ts new file mode 100644 index 00000000000..d07beb8521c --- /dev/null +++ b/libs/importer/src/importers/spec-data/password-depot-xml/missing-passwords-node.xml.ts @@ -0,0 +1,25 @@ +export const MissingPasswordsNodeData = ` + +
+ Password Depot + 17.0.0 + + + CCDA8015-7F21-4344-BDF0-9EA6AF8AE31D + 12.05.2025 15:16:11 + False + 0 + 30.12.1899 00:00:00 + +
+ + + + + + + + + + +
`; diff --git a/libs/importer/src/importers/spec-data/password-depot-xml/missing-root-node.xml.ts b/libs/importer/src/importers/spec-data/password-depot-xml/missing-root-node.xml.ts new file mode 100644 index 00000000000..aca2a2f6fa1 --- /dev/null +++ b/libs/importer/src/importers/spec-data/password-depot-xml/missing-root-node.xml.ts @@ -0,0 +1,28 @@ +export const InvalidRootNodeData = ` + +
+ Password Depot + 17.0.0 + + + CCDA8015-7F21-4344-BDF0-9EA6AF8AE31D + 12.05.2025 15:16:11 + False + 0 + 30.12.1899 00:00:00 + +
+ + + + + + + + + + + + + +
`; diff --git a/libs/importer/src/importers/spec-data/password-depot-xml/noop-encrypted-file.xml.ts b/libs/importer/src/importers/spec-data/password-depot-xml/noop-encrypted-file.xml.ts new file mode 100644 index 00000000000..e8050726b25 --- /dev/null +++ b/libs/importer/src/importers/spec-data/password-depot-xml/noop-encrypted-file.xml.ts @@ -0,0 +1,27 @@ +export const EncryptedFileData = ` + +
+ Password Depot + 17.0.0 + + + CCDA8015-7F21-4344-BDF0-9EA6AF8AE31D + 12.05.2025 15:16:11 + True + 0 + 30.12.1899 00:00:00 + +
+ + + + + + + + + + + + +
`; diff --git a/libs/importer/src/importers/spec-data/password-depot-xml/password.xml.ts b/libs/importer/src/importers/spec-data/password-depot-xml/password.xml.ts new file mode 100644 index 00000000000..7d15fce3aa8 --- /dev/null +++ b/libs/importer/src/importers/spec-data/password-depot-xml/password.xml.ts @@ -0,0 +1,222 @@ +export const PasswordTestData = ` + +
+ Password Depot + 17.0.0 + + + CCDA8015-7F21-4344-BDF0-9EA6AF8AE31D + 12.05.2025 15:16:11 + False + 0 + 30.12.1899 00:00:00 + +
+ + + + password type + 0 + p6J<]fmjv!:H&iJ7/Mwt@3i8 + someUser + example.com + someComment + 07.05.2025 13:37:56 + 07.05.2025 + 0 + 27ACAC2D-8DDA-4088-8D3A-E6C5F40ED46E + + 0 + 0 + + + Allgemein + + 0 + + + 07.05.2025 13:36:48 + 07.05.2025 13:36:48 + 0 + 0 + 0 + 1 + + 0 + 2 + someTag + DJSMI + + + 0 + + 0 + + 0 + + 161 + 0 + + + + passwort + password + -1 + 0 + 1 + + + memo + memo + -1 + 0 + 2 + + + datum + 45790 + -1 + 0 + 3 + + + nummer + 1 + -1 + 0 + 4 + + + boolean + 1 + -1 + 0 + 5 + + + decimal + 1,01 + -1 + 0 + 6 + + + email + who@cares.com + -1 + 0 + 7 + + + url + example.com + -1 + 0 + 8 + + + + + password type (2) + 0 + p6J<]fmjv!:H&iJ7/Mwt@3i8 + someUser + + someComment + 07.05.2025 13:37:56 + 07.05.2025 + 0 + AF74FF86-FE39-4584-8E96-FE950C289DF8 + + 0 + 0 + + + Allgemein + + 0 + + + 07.05.2025 13:36:48 + 07.05.2025 13:36:48 + 0 + 0 + 0 + 1 + + 0 + 2 + someTag + DJSMI + + + 0 + + 0 + + 0 + + 161 + 0 + + + + password type (3) + 0 + p6J<]fmjv!:H&iJ7/Mwt@3i8 + someUser + + someComment + 07.05.2025 13:37:56 + 07.05.2025 + 0 + BF74FF86-FA39-4584-8E96-FA950C249DF8 + + 0 + 0 + + + Allgemein + + 0 + + + 07.05.2025 13:36:48 + 07.05.2025 13:36:48 + 0 + 0 + 0 + 1 + + 0 + 2 + someTag + DJSMI + + + 0 + + 0 + + 0 + + 161 + 0 + + + + + + + + + + QUY3NEZGODYtRkUzOS00NTg0LThFOTYtRkU5NTBDMjg5REY4CkJGNzRGRjg2LUZBMzktNDU4NC04RTk2LUZBOTUwQzI0OURGOA== + + + + + + QWxsZ2VtZWluDQpIb21lIEJhbmtpbmcNCkludGVybmV0DQpQcml2YXQNCldpbmRvd3MNCg== + +
`; diff --git a/libs/importer/src/importers/spec-data/password-depot-xml/putty.xml.ts b/libs/importer/src/importers/spec-data/password-depot-xml/putty.xml.ts new file mode 100644 index 00000000000..d878b04cd3c --- /dev/null +++ b/libs/importer/src/importers/spec-data/password-depot-xml/putty.xml.ts @@ -0,0 +1,106 @@ +export const PuttyTestData = ` + +
+ Password Depot + 17.0.0 + + + CCDA8015-7F21-4344-BDF0-9EA6AF8AE31D + 12.05.2025 15:16:11 + False + 0 + 30.12.1899 00:00:00 + +
+ + + + Putty type + 9 + somePassword + someUser + localhost + someNote + 12.05.2025 15:09:09 + 00.00.0000 + 1 + 32207D79-B70B-4987-BC73-3F7AD75D2C63 + + 125 + 0 + + cli command + Allgemein + + 0 + + + 12.05.2025 15:08:18 + 12.05.2025 15:08:18 + 0 + 0 + 0 + 1 + + 0 + 2 + someTag + DJSMI + + + 0 + + 0 + + 0 + + 161 + 0 + + + + IDS_PuTTyProtocol + 0 + 0 + 0 + 0 + + + IDS_PuTTyKeyFile + pathToKeyFile + -1 + 0 + 0 + + + IDS_PuTTyKeyPassword + passwordForKeyFile + -1 + 0 + 1 + + + IDS_PuTTyPort + 8080 + -1 + 0 + 4 + + + + + + + + + + + QUY3NEZGODYtRkUzOS00NTg0LThFOTYtRkU5NTBDMjg5REY4DQo= + + + + + + QWxsZ2VtZWluDQpIb21lIEJhbmtpbmcNCkludGVybmV0DQpQcml2YXQNCldpbmRvd3MNCg== + +
`; diff --git a/libs/importer/src/importers/spec-data/password-depot-xml/rdp.xml.ts b/libs/importer/src/importers/spec-data/password-depot-xml/rdp.xml.ts new file mode 100644 index 00000000000..fafc375f9a6 --- /dev/null +++ b/libs/importer/src/importers/spec-data/password-depot-xml/rdp.xml.ts @@ -0,0 +1,76 @@ +export const RDPTestData = ` + +
+ Password Depot + 17.0.0 + + + CCDA8015-7F21-4344-BDF0-9EA6AF8AE31D + 12.05.2025 15:16:11 + False + 0 + 30.12.1899 00:00:00 + +
+ + + + rdp type + 8 + somePassword + someUser + ms-rd:subscribe?url=https://contoso.com + someNote + 12.05.2025 15:07:33 + 12.05.2025 + 1 + 24CFF328-3036-48E3-99A3-85CD337725D3 + + 123 + 0 + + commandline command + Allgemein + + 0 + + + 12.05.2025 15:06:24 + 12.05.2025 15:06:24 + 0 + 0 + 0 + 1 + + 0 + 2 + sometag + DJSMI + + + 0 + + 0 + + 0 + + 161 + 0 + + + + + + + + + + QUY3NEZGODYtRkUzOS00NTg0LThFOTYtRkU5NTBDMjg5REY4DQo= + + + + + + QWxsZ2VtZWluDQpIb21lIEJhbmtpbmcNCkludGVybmV0DQpQcml2YXQNCldpbmRvd3MNCg== + +
`; diff --git a/libs/importer/src/importers/spec-data/password-depot-xml/software-license.xml.ts b/libs/importer/src/importers/spec-data/password-depot-xml/software-license.xml.ts new file mode 100644 index 00000000000..5ab9437c3d7 --- /dev/null +++ b/libs/importer/src/importers/spec-data/password-depot-xml/software-license.xml.ts @@ -0,0 +1,169 @@ +export const SoftwareLicenseTestData = ` + +
+ Password Depot + 17.0.0 + + + CCDA8015-7F21-4344-BDF0-9EA6AF8AE31D + 12.05.2025 15:16:11 + False + 0 + 30.12.1899 00:00:00 + +
+ + + + software-license type + 2 + somePassword + someUserName + example.com + someComment + 12.05.2025 15:12:48 + 00.00.0000 + 1 + 220206EB-BE82-4E78-8FFB-9316D854721F + + 128 + 0 + + + Allgemein + + 0 + + + 12.05.2025 15:11:33 + 12.05.2025 15:11:33 + 0 + 0 + 0 + 1 + + 0 + 2 + + DJSMI + + + 0 + + 0 + + 0 + + 161 + 0 + + + + IDS_LicenseProduct + someProduct + 0 + 0 + 0 + + + IDS_LicenseVersion + someVersion + 0 + 0 + 0 + + + IDS_LicenseName + some User + -1 + 0 + 0 + + + IDS_LicenseKey + license-key + -1 + 0 + 0 + + + IDS_LicenseAdditionalKey + additional-license-key + -1 + 0 + 0 + + + IDS_LicenseURL + example.com + -1 + 0 + 8 + + + IDS_LicenseProtected + 1 + 0 + 0 + 5 + + + IDS_LicenseUserName + someUserName + -1 + 0 + 0 + + + IDS_LicensePassword + somePassword + -1 + 0 + 1 + + + IDS_LicensePurchaseDate + 45789 + 0 + 0 + 3 + + + IDS_LicenseOrderNumber + order number + -1 + 0 + 0 + + + IDS_LicenseEmail + someEmail + -1 + 0 + 7 + + + IDS_LicenseExpires + Nie + 0 + 0 + 3 + + + + + + + + + + + QUY3NEZGODYtRkUzOS00NTg0LThFOTYtRkU5NTBDMjg5REY4DQo= + + + + + + QWxsZ2VtZWluDQpIb21lIEJhbmtpbmcNCkludGVybmV0DQpQcml2YXQNCldpbmRvd3MNCg== + +
`; diff --git a/libs/importer/src/importers/spec-data/password-depot-xml/team-viewer.xml.ts b/libs/importer/src/importers/spec-data/password-depot-xml/team-viewer.xml.ts new file mode 100644 index 00000000000..911c621c59a --- /dev/null +++ b/libs/importer/src/importers/spec-data/password-depot-xml/team-viewer.xml.ts @@ -0,0 +1,85 @@ +export const TeamViewerTestData = ` + +
+ Password Depot + 17.0.0 + + + CCDA8015-7F21-4344-BDF0-9EA6AF8AE31D + 12.05.2025 15:16:11 + False + 0 + 30.12.1899 00:00:00 + +
+ + + + TeamViewer type + 10 + somePassword + + partnerId + someNote + 12.05.2025 15:08:14 + 00.00.0000 + 1 + AE650032-5963-4D93-8E2E-69F216405C29 + + 124 + 0 + + + Allgemein + + 0 + + + 12.05.2025 15:07:41 + 12.05.2025 15:07:41 + 0 + 0 + 0 + 1 + + 0 + 2 + someTag + DJSMI + + + 0 + + 0 + + 0 + + 161 + 0 + + + + IDS_TeamViewerMode + 0 + 0 + 0 + 0 + + + + + + + + + + + QUY3NEZGODYtRkUzOS00NTg0LThFOTYtRkU5NTBDMjg5REY4DQo= + + + + + + QWxsZ2VtZWluDQpIb21lIEJhbmtpbmcNCkludGVybmV0DQpQcml2YXQNCldpbmRvd3MNCg== + +
`; diff --git a/libs/importer/src/importers/spec-data/password-depot-xml/wrong-version.xml.ts b/libs/importer/src/importers/spec-data/password-depot-xml/wrong-version.xml.ts new file mode 100644 index 00000000000..90b766ded1b --- /dev/null +++ b/libs/importer/src/importers/spec-data/password-depot-xml/wrong-version.xml.ts @@ -0,0 +1,28 @@ +export const InvalidVersionData = ` + +
+ Password Depot + 18.0.0 + + + CCDA8015-7F21-4344-BDF0-9EA6AF8AE31D + 12.05.2025 15:16:11 + False + 0 + 30.12.1899 00:00:00 + +
+ + + + + + + + + + + + + +
`; diff --git a/libs/importer/src/models/import-options.ts b/libs/importer/src/models/import-options.ts index a8c4b4e0a8a..205dbaf0198 100644 --- a/libs/importer/src/models/import-options.ts +++ b/libs/importer/src/models/import-options.ts @@ -73,6 +73,7 @@ export const regularImportOptions = [ { id: "passkyjson", name: "Passky (json)" }, { id: "passwordxpcsv", name: "Password XP (csv)" }, { id: "netwrixpasswordsecure", name: "Netwrix Password Secure (csv)" }, + { id: "passworddepot17xml", name: "Password Depot 17 (xml)" }, ] as const; export type ImportType = diff --git a/libs/importer/src/services/import.service.ts b/libs/importer/src/services/import.service.ts index 3789ee7536c..2b9d2e490f7 100644 --- a/libs/importer/src/services/import.service.ts +++ b/libs/importer/src/services/import.service.ts @@ -90,6 +90,7 @@ import { YotiCsvImporter, ZohoVaultCsvImporter, PasswordXPCsvImporter, + PasswordDepot17XmlImporter, } from "../importers"; import { Importer } from "../importers/importer"; import { @@ -348,6 +349,8 @@ export class ImportService implements ImportServiceAbstraction { return new PasswordXPCsvImporter(); case "netwrixpasswordsecure": return new NetwrixPasswordSecureCsvImporter(); + case "passworddepot17xml": + return new PasswordDepot17XmlImporter(); default: return null; }