mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-03-19 07:07:59 -04:00
Add dashlane importer and unittest (#811)
This commit is contained in:
committed by
Leendert de Borst
parent
89534bf78e
commit
901caa896b
@@ -87,7 +87,7 @@
|
||||
<p>@DuplicateCredentialsCount duplicate credential(s) were found and will not be imported.</p>
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
@if (ImportedCredentials.Count == 0)
|
||||
{
|
||||
<div class="p-4 mb-4 text-amber-700 bg-amber-100 rounded-lg dark:bg-amber-800/30 dark:text-amber-300" role="alert">
|
||||
@@ -369,7 +369,7 @@
|
||||
ImportError = null;
|
||||
ImportSuccessMessage = null;
|
||||
StateHasChanged();
|
||||
|
||||
|
||||
// Let UI update to start showing the loading indicator
|
||||
await Task.Delay(50);
|
||||
}
|
||||
@@ -479,6 +479,12 @@
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.File.Name.EndsWith(".zip"))
|
||||
{
|
||||
ImportError = $"Please unzip the {ServiceName} export file before importing, please read the instructions below for more information.";
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
IsImporting = true;
|
||||
@@ -500,10 +506,10 @@
|
||||
await Task.WhenAll(processingTask, delayTask);
|
||||
|
||||
ImportedCredentials = await processingTask;
|
||||
|
||||
|
||||
// Detect and remove duplicates before showing the preview
|
||||
await DetectAndRemoveDuplicates();
|
||||
|
||||
|
||||
CurrentStep = ImportStep.Preview;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -532,7 +538,7 @@
|
||||
)).ToList();
|
||||
|
||||
DuplicateCredentialsCount = duplicates.Count;
|
||||
|
||||
|
||||
// Remove duplicates from the import list
|
||||
ImportedCredentials = ImportedCredentials.Except(duplicates).ToList();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
@using AliasVault.ImportExport.Models
|
||||
@using AliasVault.ImportExport.Importers
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject GlobalNotificationService GlobalNotificationService
|
||||
@inject ILogger<ImportServiceBitwarden> Logger
|
||||
|
||||
<ImportServiceCard
|
||||
ServiceName="Dashlane"
|
||||
Description="Import passwords from your Dashlane account"
|
||||
LogoUrl="img/importers/dashlane.svg"
|
||||
ProcessFileCallback="ProcessFile">
|
||||
<p class="text-gray-700 dark:text-gray-300 mb-4">In order to import your Dashlane passwords, you need to export it as a CSV file. You can do this by logging into your Dashlane account, going to the 'Account' > 'Settings' menu and selecting 'Export to CSV'.</p>
|
||||
<p class="text-gray-700 dark:text-gray-300 mb-4">Note: the .zip file you download will contain a "credentials.csv" file. You need to unzip the archive first, and then upload the "credentials.csv" CSV file below.</p>
|
||||
</ImportServiceCard>
|
||||
|
||||
@code {
|
||||
private static async Task<List<ImportedCredential>> ProcessFile(string fileContents)
|
||||
{
|
||||
return await DashlaneImporter.ImportFromCsvAsync(fileContents);
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@
|
||||
<ImportService1Password />
|
||||
<ImportServiceBitwarden />
|
||||
<ImportServiceChrome />
|
||||
<ImportServiceDashlane />
|
||||
<ImportServiceFirefox />
|
||||
<ImportServiceKeePass />
|
||||
<ImportServiceKeePassXC />
|
||||
|
||||
5
src/AliasVault.Client/wwwroot/img/importers/dashlane.svg
Normal file
5
src/AliasVault.Client/wwwroot/img/importers/dashlane.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="512" cy="512" r="512" style="fill:#10353e"/>
|
||||
<path d="m544.7 458.9 53.8 19.8c8.7 3.1 19.3-1 19.3-7.5V334.9c0-3.1-2.6-6-6.8-7.5l-53.8-19.8c-8.7-3.1-19.3 1-19.3 7.5v136.4c0 3.1 2.6 5.9 6.8 7.4m0 244.8 53.8 19.8c8.7 3.1 19.3-1 19.3-7.5V579.7c0-3.1-2.6-6-6.8-7.5l-53.8-19.8c-8.7-3.1-19.3 1-19.3 7.5v136.4c0 3.1 2.6 6 6.8 7.4M445 413.3l53.8 19.8c8.7 3.1 19.3-1 19.3-7.5V277.3c0-3.1-2.6-6-6.8-7.5L457.5 250c-8.7-3.1-19.3 1-19.3 7.5v148.4c0 3.1 2.6 5.9 6.8 7.4m0 326.9 53.8 19.8c8.7 3.1 19.3-1 19.3-7.5v-148c0-3.1-2.6-6-6.8-7.5l-53.8-19.8c-8.7-3.1-19.3 1-19.3 7.5v148c0 3.1 2.6 6 6.8 7.5m-26.6-457.4c0-3.1-2.6-6-6.8-7.5l-53.8-19.8c-8.7-3.1-19.3 1-19.3 7.5v464.1c0 3.1 2.6 6 6.8 7.5l53.8 19.8c8.7 3.1 19.3-1 19.3-7.5V282.8zM710.7 406l-53.8-19.8c-8.7-3.1-19.3 1-19.3 7.5v224c0 3.1 2.6 6 6.8 7.5l53.8 19.8c8.7 3.1 19.3-1 19.3-7.5v-224c0-3.1-2.6-6-6.8-7.5" style="fill:#fff"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -69,6 +69,7 @@
|
||||
<EmbeddedResource Include="TestData\Exports\keepassxc.csv" />
|
||||
<EmbeddedResource Include="TestData\Exports\1password_8.csv" />
|
||||
<EmbeddedResource Include="TestData\Exports\protonpass.csv" />
|
||||
<EmbeddedResource Include="TestData\Exports\dashlane.csv" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
username,username2,username3,title,password,note,url,category,otpUrl
|
||||
Test username,,,Test,password123,,https://Test,,
|
||||
googleuser,,,Google,googlepassword,,https://www.google.com,,
|
||||
testusername,testusernamealternative,,Local,testpassword,testnote,https://www.testwebsite.local,,
|
||||
|
@@ -406,4 +406,52 @@ public class ImportExportTests
|
||||
Assert.That(testWithEmailCredential.Email, Is.EqualTo("testalias.gating981@passinbox.com"));
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test case for importing credentials from Dashlane CSV and ensuring all values are present.
|
||||
/// </summary>
|
||||
/// <returns>Async task.</returns>
|
||||
[Test]
|
||||
public async Task ImportCredentialsFromDashlaneCsv()
|
||||
{
|
||||
// Arrange
|
||||
var fileContent = await ResourceReaderUtility.ReadEmbeddedResourceStringAsync("AliasVault.UnitTests.TestData.Exports.dashlane.csv");
|
||||
|
||||
// Act
|
||||
var importedCredentials = await DashlaneImporter.ImportFromCsvAsync(fileContent);
|
||||
|
||||
// Assert
|
||||
Assert.That(importedCredentials, Has.Count.EqualTo(3));
|
||||
|
||||
// Test specific entries
|
||||
var testCredential = importedCredentials.First(c => c.ServiceName == "Test");
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(testCredential.ServiceName, Is.EqualTo("Test"));
|
||||
Assert.That(testCredential.ServiceUrl, Is.EqualTo("https://Test"));
|
||||
Assert.That(testCredential.Username, Is.EqualTo("Test username"));
|
||||
Assert.That(testCredential.Password, Is.EqualTo("password123"));
|
||||
Assert.That(testCredential.Notes, Is.Null);
|
||||
});
|
||||
|
||||
var googleCredential = importedCredentials.First(c => c.ServiceName == "Google");
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(googleCredential.ServiceName, Is.EqualTo("Google"));
|
||||
Assert.That(googleCredential.ServiceUrl, Is.EqualTo("https://www.google.com"));
|
||||
Assert.That(googleCredential.Username, Is.EqualTo("googleuser"));
|
||||
Assert.That(googleCredential.Password, Is.EqualTo("googlepassword"));
|
||||
Assert.That(googleCredential.Notes, Is.Null);
|
||||
});
|
||||
|
||||
var localCredential = importedCredentials.First(c => c.ServiceName == "Local");
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(localCredential.ServiceName, Is.EqualTo("Local"));
|
||||
Assert.That(localCredential.ServiceUrl, Is.EqualTo("https://www.testwebsite.local"));
|
||||
Assert.That(localCredential.Username, Is.EqualTo("testusername"));
|
||||
Assert.That(localCredential.Password, Is.EqualTo("testpassword"));
|
||||
Assert.That(localCredential.Notes, Is.EqualTo("testnote\nAlternative username 1: testusernamealternative"));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="DashlaneImporter.cs" company="lanedirt">
|
||||
// Copyright (c) lanedirt. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
namespace AliasVault.ImportExport.Importers;
|
||||
|
||||
using AliasVault.ImportExport.Models;
|
||||
using AliasVault.ImportExport.Models.Imports;
|
||||
using CsvHelper;
|
||||
using CsvHelper.Configuration;
|
||||
using System.Globalization;
|
||||
|
||||
/// <summary>
|
||||
/// Imports credentials from Dashlane.
|
||||
/// </summary>
|
||||
public static class DashlaneImporter
|
||||
{
|
||||
/// <summary>
|
||||
/// Imports Dashlane CSV file and converts contents to list of ImportedCredential model objects.
|
||||
/// </summary>
|
||||
/// <param name="fileContent">The content of the CSV file.</param>
|
||||
/// <returns>The imported list of ImportedCredential objects.</returns>
|
||||
public static async Task<List<ImportedCredential>> ImportFromCsvAsync(string fileContent)
|
||||
{
|
||||
using var reader = new StringReader(fileContent);
|
||||
using var csv = new CsvReader(reader, new CsvConfiguration(CultureInfo.InvariantCulture));
|
||||
|
||||
var credentials = new List<ImportedCredential>();
|
||||
await foreach (var record in csv.GetRecordsAsync<DashlaneCsvRecord>())
|
||||
{
|
||||
var credential = new ImportedCredential
|
||||
{
|
||||
ServiceName = record.Title,
|
||||
ServiceUrl = record.URL,
|
||||
Username = record.Username,
|
||||
Password = record.Password,
|
||||
TwoFactorSecret = record.OTPUrl,
|
||||
Notes = BuildNotes(record)
|
||||
};
|
||||
|
||||
credentials.Add(credential);
|
||||
}
|
||||
|
||||
if (credentials.Count == 0)
|
||||
{
|
||||
throw new InvalidOperationException("No records found in the CSV file.");
|
||||
}
|
||||
|
||||
return credentials;
|
||||
}
|
||||
|
||||
private static string? BuildNotes(DashlaneCsvRecord record)
|
||||
{
|
||||
var notes = new List<string>();
|
||||
|
||||
if (!string.IsNullOrEmpty(record.Note))
|
||||
{
|
||||
notes.Add(record.Note);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(record.Username2))
|
||||
{
|
||||
notes.Add($"Alternative username 1: {record.Username2}");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(record.Username3))
|
||||
{
|
||||
notes.Add($"Alternative username 2: {record.Username3}");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(record.Category))
|
||||
{
|
||||
notes.Add($"Category: {record.Category}");
|
||||
}
|
||||
|
||||
return notes.Count > 0 ? string.Join(Environment.NewLine, notes) : null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="DashlaneCsvRecord.cs" company="lanedirt">
|
||||
// Copyright (c) lanedirt. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
using AliasVault.ImportExport.Converters;
|
||||
using CsvHelper.Configuration.Attributes;
|
||||
|
||||
namespace AliasVault.ImportExport.Models.Imports;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a Dashlane CSV record that is being imported from a Dashlane CSV export file.
|
||||
/// </summary>
|
||||
public class DashlaneCsvRecord
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the primary username.
|
||||
/// </summary>
|
||||
[Name("username")]
|
||||
public string Username { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the second username.
|
||||
/// </summary>
|
||||
[Name("username2")]
|
||||
public string? Username2 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the third username.
|
||||
/// </summary>
|
||||
[Name("username3")]
|
||||
public string? Username3 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the title/service name.
|
||||
/// </summary>
|
||||
[Name("title")]
|
||||
public string Title { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the password.
|
||||
/// </summary>
|
||||
[Name("password")]
|
||||
public string? Password { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets any additional notes.
|
||||
/// </summary>
|
||||
[Name("note")]
|
||||
public string? Note { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the service URL.
|
||||
/// </summary>
|
||||
[Name("url")]
|
||||
public string? URL { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the category.
|
||||
/// </summary>
|
||||
[Name("category")]
|
||||
public string? Category { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the OTP URL.
|
||||
/// </summary>
|
||||
[Name("otpUrl")]
|
||||
public string? OTPUrl { get; set; }
|
||||
}
|
||||
Reference in New Issue
Block a user