Add Dropbox Passwords import method (#1114)

This commit is contained in:
Leendert de Borst
2025-08-13 21:06:54 +02:00
committed by Leendert de Borst
parent 29d38759eb
commit 783b2d44ef
9 changed files with 217 additions and 1 deletions

View File

@@ -0,0 +1,25 @@
@using AliasVault.ImportExport.Models
@using AliasVault.ImportExport.Importers
@using Microsoft.Extensions.Localization
@inject NavigationManager NavigationManager
@inject GlobalNotificationService GlobalNotificationService
@inject IStringLocalizerFactory LocalizerFactory
@inject ILogger<ImportServiceDropbox> Logger
<ImportServiceCard
ServiceName="Dropbox Passwords"
Description="@Localizer["DropboxDescription"]"
LogoUrl="img/importers/dropbox.svg"
ProcessFileCallback="ProcessFile">
<p class="text-gray-700 dark:text-gray-300 mb-4">@Localizer["DropboxInstructionsPart1"]</p>
<p class="text-gray-700 dark:text-gray-300 mb-4">@Localizer["UploadFileInstructionCommon"]</p>
</ImportServiceCard>
@code {
private IStringLocalizer Localizer => LocalizerFactory.Create("Components.Main.Settings.ImportExport.ImportServices", "AliasVault.Client");
private static async Task<List<ImportedCredential>> ProcessFile(string fileContents)
{
return await DropboxImporter.ImportFromCsvAsync(fileContents);
}
}

View File

@@ -26,6 +26,7 @@
<ImportServiceBitwarden />
<ImportServiceChrome />
<ImportServiceDashlane />
<ImportServiceDropbox />
<ImportServiceFirefox />
<ImportServiceKeePass />
<ImportServiceKeePassXC />

View File

@@ -226,6 +226,15 @@
<value>If you have a CSV file back-up of your AliasVault database (from a different AliasVault server), you can import it here.</value>
<comment>AliasVault import instructions</comment>
</data>
<!-- Dropbox -->
<data name="DropboxDescription" xml:space="preserve">
<value>Import passwords from Dropbox Passwords</value>
<comment>Description for Dropbox import service</comment>
</data>
<data name="DropboxInstructionsPart1" xml:space="preserve">
<value>In order to import your Dropbox Passwords, you need to export them as a CSV file. You can do this by opening Dropbox Passwords, going to 'Account' > 'Export' (to .CSV).</value>
<comment>Dropbox export instructions part 1</comment>
</data>
<!-- Common text that can be reused -->
<data name="UploadFileInstructionCommon" xml:space="preserve">
<value>Once you have exported the file, you can upload it below.</value>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="43px" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 43 40" version="1.1" height="40px">
<path d="m12.5 0l-12.5 8.1 8.7 7 12.5-7.8-8.7-7.3zm-12.5 21.9l12.5 8.2 8.7-7.3-12.5-7.7-8.7 6.8zm21.2 0.9l8.8 7.3 12.4-8.1-8.6-6.9-12.6 7.7zm21.2-14.7l-12.4-8.1-8.8 7.3 12.6 7.8 8.6-7zm-21.1 16.3l-8.8 7.3-3.7-2.5v2.8l12.5 7.5 12.5-7.5v-2.8l-3.8 2.5-8.7-7.3z" fill="#007EE5"/>
</svg>

After

Width:  |  Height:  |  Size: 441 B

View File

@@ -62,13 +62,14 @@
<ItemGroup>
<EmbeddedResource Include="TestData\Exports\bitwarden.csv" />
<EmbeddedResource Include="TestData\Exports\chrome.csv" />
<EmbeddedResource Include="TestData\Exports\dashlane.csv" />
<EmbeddedResource Include="TestData\Exports\dropbox.csv" />
<EmbeddedResource Include="TestData\Exports\firefox.csv" />
<EmbeddedResource Include="TestData\Exports\strongbox.csv" />
<EmbeddedResource Include="TestData\Exports\keepass.csv" />
<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" />
<EmbeddedResource Include="TestData\Exports\lastpass.csv" />
</ItemGroup>

View File

@@ -0,0 +1,6 @@
Name,URL,Username,Password,Notes
Gmail,https://gmail.com,testuser@gmail.com,gmailpass123,Important email account
Facebook,https://facebook.com,fbuser,fbpass456,Social media account
GitHub,https://github.com,devuser,devpass789,Development platform
Secure Note,,,,Important information stored securely
Test Site,https://test.example.com,testlogin,testpassword,Test notes for example site
1 Name URL Username Password Notes
2 Gmail https://gmail.com testuser@gmail.com gmailpass123 Important email account
3 Facebook https://facebook.com fbuser fbpass456 Social media account
4 GitHub https://github.com devuser devpass789 Development platform
5 Secure Note Important information stored securely
6 Test Site https://test.example.com testlogin testpassword Test notes for example site

View File

@@ -592,6 +592,56 @@ public class ImportExportTests
});
}
/// <summary>
/// Test case for importing credentials from Dropbox CSV and ensuring all values are present.
/// </summary>
/// <returns>Async task.</returns>
[Test]
public async Task ImportCredentialsFromDropboxCsv()
{
// Arrange
var fileContent = await ResourceReaderUtility.ReadEmbeddedResourceStringAsync("AliasVault.UnitTests.TestData.Exports.dropbox.csv");
// Act
var importedCredentials = await DropboxImporter.ImportFromCsvAsync(fileContent);
// Assert
Assert.That(importedCredentials, Has.Count.EqualTo(5));
// Test Gmail credential
var gmailCredential = importedCredentials.First(c => c.ServiceName == "Gmail");
Assert.Multiple(() =>
{
Assert.That(gmailCredential.ServiceName, Is.EqualTo("Gmail"));
Assert.That(gmailCredential.ServiceUrl, Is.EqualTo("https://gmail.com"));
Assert.That(gmailCredential.Username, Is.EqualTo("testuser@gmail.com"));
Assert.That(gmailCredential.Password, Is.EqualTo("gmailpass123"));
Assert.That(gmailCredential.Notes, Is.EqualTo("Important email account"));
});
// Test GitHub credential
var githubCredential = importedCredentials.First(c => c.ServiceName == "GitHub");
Assert.Multiple(() =>
{
Assert.That(githubCredential.ServiceName, Is.EqualTo("GitHub"));
Assert.That(githubCredential.ServiceUrl, Is.EqualTo("https://github.com"));
Assert.That(githubCredential.Username, Is.EqualTo("devuser"));
Assert.That(githubCredential.Password, Is.EqualTo("devpass789"));
Assert.That(githubCredential.Notes, Is.EqualTo("Development platform"));
});
// Test Secure Note (no login credentials)
var secureNoteCredential = importedCredentials.First(c => c.ServiceName == "Secure Note");
Assert.Multiple(() =>
{
Assert.That(secureNoteCredential.ServiceName, Is.EqualTo("Secure Note"));
Assert.That(secureNoteCredential.ServiceUrl, Is.Null);
Assert.That(secureNoteCredential.Username, Is.Empty);
Assert.That(secureNoteCredential.Password, Is.Empty);
Assert.That(secureNoteCredential.Notes, Is.EqualTo("Important information stored securely"));
});
}
/// <summary>
/// Test case for getting the CSV template structure.
/// </summary>

View File

@@ -0,0 +1,74 @@
//-----------------------------------------------------------------------
// <copyright file="DropboxImporter.cs" company="lanedirt">
// Copyright (c) lanedirt. All rights reserved.
// Licensed under the AGPLv3 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 Dropbox Passwords.
/// </summary>
public static class DropboxImporter
{
/// <summary>
/// Imports Dropbox 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<DropboxCsvRecord>())
{
// Skip empty records (records with no title)
if (string.IsNullOrWhiteSpace(record.Name))
{
continue;
}
var credential = new ImportedCredential
{
ServiceName = record.Name,
ServiceUrl = NormalizeUrl(record.Url),
Username = record.Username,
Password = record.Password,
Notes = record.Notes
};
credentials.Add(credential);
}
if (credentials.Count == 0)
{
throw new InvalidOperationException("No records found in the CSV file.");
}
return credentials;
}
/// <summary>
/// Normalizes URL values from Dropbox CSV format.
/// </summary>
/// <param name="url">The URL from the CSV record.</param>
/// <returns>The normalized URL or null if it's empty or invalid.</returns>
private static string? NormalizeUrl(string? url)
{
if (string.IsNullOrWhiteSpace(url))
{
return null;
}
return url;
}
}

View File

@@ -0,0 +1,46 @@
//-----------------------------------------------------------------------
// <copyright file="DropboxCsvRecord.cs" company="lanedirt">
// Copyright (c) lanedirt. All rights reserved.
// Licensed under the AGPLv3 license. See LICENSE.md file in the project root for full license information.
// </copyright>
//-----------------------------------------------------------------------
using CsvHelper.Configuration.Attributes;
namespace AliasVault.ImportExport.Models.Imports;
/// <summary>
/// Represents a Dropbox CSV record that is being imported from a Dropbox CSV export file.
/// </summary>
public class DropboxCsvRecord
{
/// <summary>
/// Gets or sets the title/service name (e.g., "Facebook", "Gmail").
/// </summary>
[Name("Name")]
public string Name { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the service URL.
/// </summary>
[Name("URL")]
public string? Url { get; set; }
/// <summary>
/// Gets or sets the username/login.
/// </summary>
[Name("Username")]
public string? Username { get; set; }
/// <summary>
/// Gets or sets the password.
/// </summary>
[Name("Password")]
public string? Password { get; set; }
/// <summary>
/// Gets or sets any additional notes.
/// </summary>
[Name("Notes")]
public string? Notes { get; set; }
}