mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-05-16 12:29:01 -04:00
337 lines
12 KiB
C#
337 lines
12 KiB
C#
//-----------------------------------------------------------------------
|
|
// <copyright file="ItemCsvService.cs" company="aliasvault">
|
|
// Copyright (c) aliasvault. All rights reserved.
|
|
// Licensed under the AGPLv3 license. See LICENSE.md file in the project root for full license information.
|
|
// </copyright>
|
|
//-----------------------------------------------------------------------
|
|
|
|
namespace AliasVault.ImportExport;
|
|
|
|
using AliasClientDb;
|
|
using AliasClientDb.Models;
|
|
using AliasVault.ImportExport.Importers;
|
|
using AliasVault.ImportExport.Models;
|
|
using CsvHelper;
|
|
using CsvHelper.Configuration;
|
|
using System.Globalization;
|
|
|
|
/// <summary>
|
|
/// Exports and imports Item objects to and from CSV files.
|
|
/// </summary>
|
|
public static class ItemCsvService
|
|
{
|
|
/// <summary>
|
|
/// Export list of items to CSV file.
|
|
/// </summary>
|
|
/// <param name="items">List of items to export.</param>
|
|
/// <returns>CSV file as byte array.</returns>
|
|
public static byte[] ExportItemsToCsv(List<Item> items)
|
|
{
|
|
var records = new List<ItemCsvRecord>();
|
|
|
|
foreach (var item in items)
|
|
{
|
|
var record = new ItemCsvRecord
|
|
{
|
|
ServiceName = item.Name ?? string.Empty,
|
|
FolderPath = BuildFolderPath(item.Folder),
|
|
ServiceUrl = GetFieldValue(item, FieldKey.LoginUrl),
|
|
Username = GetFieldValue(item, FieldKey.LoginUsername),
|
|
CurrentPassword = GetFieldValue(item, FieldKey.LoginPassword),
|
|
AliasEmail = GetFieldValue(item, FieldKey.LoginEmail),
|
|
TwoFactorSecret = item.TotpCodes.FirstOrDefault(t => !t.IsDeleted)?.SecretKey ?? string.Empty,
|
|
AliasGender = GetFieldValue(item, FieldKey.AliasGender),
|
|
AliasFirstName = GetFieldValue(item, FieldKey.AliasFirstName),
|
|
AliasLastName = GetFieldValue(item, FieldKey.AliasLastName),
|
|
AliasNickName = string.Empty, // NickName is no longer stored as a separate field
|
|
AliasBirthDate = ParseBirthDate(GetFieldValue(item, FieldKey.AliasBirthdate)),
|
|
Notes = GetFieldValue(item, FieldKey.NotesContent),
|
|
CreatedAt = item.CreatedAt,
|
|
UpdatedAt = item.UpdatedAt,
|
|
CardholderName = GetFieldValue(item, FieldKey.CardCardholderName),
|
|
CardNumber = GetFieldValue(item, FieldKey.CardNumber),
|
|
CardExpiryMonth = GetFieldValue(item, FieldKey.CardExpiryMonth),
|
|
CardExpiryYear = GetFieldValue(item, FieldKey.CardExpiryYear),
|
|
CardCvv = GetFieldValue(item, FieldKey.CardCvv),
|
|
CardPin = GetFieldValue(item, FieldKey.CardPin),
|
|
};
|
|
|
|
records.Add(record);
|
|
}
|
|
|
|
using var memoryStream = new MemoryStream();
|
|
using var writer = new StreamWriter(memoryStream);
|
|
using var csv = new CsvWriter(writer, new CsvConfiguration(CultureInfo.InvariantCulture));
|
|
|
|
csv.WriteRecords(records);
|
|
writer.Flush();
|
|
return memoryStream.ToArray();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Imports Item objects from a CSV file.
|
|
/// </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>> ImportItemsFromCsv(string fileContent)
|
|
{
|
|
using var reader = new StringReader(fileContent);
|
|
var config = new CsvConfiguration(CultureInfo.InvariantCulture)
|
|
{
|
|
// Allow missing headers - fields that don't exist will be skipped
|
|
HeaderValidated = null,
|
|
MissingFieldFound = null,
|
|
};
|
|
using var csv = new CsvReader(reader, config);
|
|
|
|
var records = new List<ItemCsvRecord>();
|
|
await foreach (var record in csv.GetRecordsAsync<ItemCsvRecord>())
|
|
{
|
|
records.Add(record);
|
|
}
|
|
|
|
if (records.Count == 0)
|
|
{
|
|
throw new InvalidOperationException("No records found in the CSV file.");
|
|
}
|
|
|
|
var credentials = new List<ImportedCredential>();
|
|
|
|
foreach (var record in records)
|
|
{
|
|
var credential = new ImportedCredential
|
|
{
|
|
ServiceName = record.ServiceName,
|
|
ServiceUrls = BaseImporter.ParseUrls(record.ServiceUrl),
|
|
Username = record.Username,
|
|
Password = record.CurrentPassword,
|
|
Email = record.AliasEmail,
|
|
Notes = record.Notes,
|
|
Alias = new ImportedAlias
|
|
{
|
|
Gender = record.AliasGender,
|
|
FirstName = record.AliasFirstName,
|
|
LastName = record.AliasLastName,
|
|
NickName = record.AliasNickName,
|
|
BirthDate = record.AliasBirthDate,
|
|
CreatedAt = record.CreatedAt,
|
|
UpdatedAt = record.UpdatedAt,
|
|
},
|
|
TwoFactorSecret = record.TwoFactorSecret,
|
|
CreatedAt = record.CreatedAt,
|
|
UpdatedAt = record.UpdatedAt,
|
|
FolderPath = string.IsNullOrWhiteSpace(record.FolderPath) ? null : record.FolderPath,
|
|
};
|
|
|
|
if (HasCardData(record))
|
|
{
|
|
credential.ItemType = ImportedItemType.Creditcard;
|
|
credential.Creditcard = new ImportedCreditcard
|
|
{
|
|
CardholderName = record.CardholderName,
|
|
Number = record.CardNumber,
|
|
ExpiryMonth = record.CardExpiryMonth,
|
|
ExpiryYear = record.CardExpiryYear,
|
|
Cvv = record.CardCvv,
|
|
Pin = record.CardPin,
|
|
};
|
|
}
|
|
|
|
credentials.Add(credential);
|
|
}
|
|
|
|
return credentials;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Builds the full hierarchical path for a folder by traversing parent folders.
|
|
/// </summary>
|
|
/// <param name="folder">The folder to build the path for.</param>
|
|
/// <returns>The full hierarchical path (e.g., "Work/Projects/Active"), or empty string if null.</returns>
|
|
private static string BuildFolderPath(Folder? folder)
|
|
{
|
|
if (folder == null)
|
|
{
|
|
return string.Empty;
|
|
}
|
|
|
|
var pathParts = new List<string>();
|
|
var currentFolder = folder;
|
|
|
|
// Traverse up the folder hierarchy
|
|
while (currentFolder != null)
|
|
{
|
|
pathParts.Insert(0, currentFolder.Name);
|
|
currentFolder = currentFolder.ParentFolder;
|
|
}
|
|
|
|
return string.Join("/", pathParts);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a field value from an item by field key.
|
|
/// </summary>
|
|
/// <param name="item">The item to get the field value from.</param>
|
|
/// <param name="fieldKey">The field key to look up.</param>
|
|
/// <returns>The field value, or empty string if not found.</returns>
|
|
private static string GetFieldValue(Item item, string fieldKey)
|
|
{
|
|
return item.FieldValues
|
|
.FirstOrDefault(fv => fv.FieldKey == fieldKey && !fv.IsDeleted)
|
|
?.Value ?? string.Empty;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if the CSV record has any credit card field populated.
|
|
/// </summary>
|
|
/// <param name="record">The CSV record to inspect.</param>
|
|
/// <returns>True if any card field has a non-empty value.</returns>
|
|
private static bool HasCardData(ItemCsvRecord record)
|
|
{
|
|
return !string.IsNullOrWhiteSpace(record.CardholderName)
|
|
|| !string.IsNullOrWhiteSpace(record.CardNumber)
|
|
|| !string.IsNullOrWhiteSpace(record.CardExpiryMonth)
|
|
|| !string.IsNullOrWhiteSpace(record.CardExpiryYear)
|
|
|| !string.IsNullOrWhiteSpace(record.CardCvv)
|
|
|| !string.IsNullOrWhiteSpace(record.CardPin);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parses a birth date string to a DateTime.
|
|
/// </summary>
|
|
/// <param name="birthDateStr">The birth date string in yyyy-MM-dd format.</param>
|
|
/// <returns>The parsed DateTime, or null if the string is empty or invalid.</returns>
|
|
private static DateTime? ParseBirthDate(string birthDateStr)
|
|
{
|
|
if (string.IsNullOrEmpty(birthDateStr))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
if (DateTime.TryParseExact(birthDateStr, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.None, out var date))
|
|
{
|
|
return date;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// CSV record for Item objects.
|
|
/// </summary>
|
|
public class ItemCsvRecord
|
|
{
|
|
/// <summary>
|
|
/// Gets or sets the service name.
|
|
/// </summary>
|
|
public string ServiceName { get; set; } = string.Empty;
|
|
|
|
/// <summary>
|
|
/// Gets or sets the folder path.
|
|
/// Added in version 1.7.0.
|
|
/// </summary>
|
|
public string FolderPath { get; set; } = string.Empty;
|
|
|
|
/// <summary>
|
|
/// Gets or sets the service URL.
|
|
/// </summary>
|
|
public string ServiceUrl { get; set; } = string.Empty;
|
|
|
|
/// <summary>
|
|
/// Gets or sets the username.
|
|
/// </summary>
|
|
public string Username { get; set; } = string.Empty;
|
|
|
|
/// <summary>
|
|
/// Gets or sets the current password.
|
|
/// </summary>
|
|
public string CurrentPassword { get; set; } = string.Empty;
|
|
|
|
/// <summary>
|
|
/// Gets or sets the alias email.
|
|
/// </summary>
|
|
public string AliasEmail { get; set; } = string.Empty;
|
|
|
|
/// <summary>
|
|
/// Gets or sets the two-factor secret.
|
|
/// </summary>
|
|
public string TwoFactorSecret { get; set; } = string.Empty;
|
|
|
|
/// <summary>
|
|
/// Gets or sets the alias gender.
|
|
/// </summary>
|
|
public string AliasGender { get; set; } = string.Empty;
|
|
|
|
/// <summary>
|
|
/// Gets or sets the alias first name.
|
|
/// </summary>
|
|
public string AliasFirstName { get; set; } = string.Empty;
|
|
|
|
/// <summary>
|
|
/// Gets or sets the alias last name.
|
|
/// </summary>
|
|
public string AliasLastName { get; set; } = string.Empty;
|
|
|
|
/// <summary>
|
|
/// Gets or sets the alias nickname (legacy field, no longer used).
|
|
/// </summary>
|
|
public string AliasNickName { get; set; } = string.Empty;
|
|
|
|
/// <summary>
|
|
/// Gets or sets the alias birth date.
|
|
/// </summary>
|
|
public DateTime? AliasBirthDate { get; set; } = null;
|
|
|
|
/// <summary>
|
|
/// Gets or sets the credit card cardholder name.
|
|
/// Empty for non-credit-card items.
|
|
/// </summary>
|
|
public string CardholderName { get; set; } = string.Empty;
|
|
|
|
/// <summary>
|
|
/// Gets or sets the credit card number.
|
|
/// Empty for non-credit-card items.
|
|
/// </summary>
|
|
public string CardNumber { get; set; } = string.Empty;
|
|
|
|
/// <summary>
|
|
/// Gets or sets the credit card expiry month (01-12).
|
|
/// Empty for non-credit-card items.
|
|
/// </summary>
|
|
public string CardExpiryMonth { get; set; } = string.Empty;
|
|
|
|
/// <summary>
|
|
/// Gets or sets the credit card expiry year (e.g., "2028").
|
|
/// Empty for non-credit-card items.
|
|
/// </summary>
|
|
public string CardExpiryYear { get; set; } = string.Empty;
|
|
|
|
/// <summary>
|
|
/// Gets or sets the credit card CVV.
|
|
/// Empty for non-credit-card items.
|
|
/// </summary>
|
|
public string CardCvv { get; set; } = string.Empty;
|
|
|
|
/// <summary>
|
|
/// Gets or sets the credit card PIN.
|
|
/// Empty for non-credit-card items.
|
|
/// </summary>
|
|
public string CardPin { get; set; } = string.Empty;
|
|
|
|
/// <summary>
|
|
/// Gets or sets the notes.
|
|
/// </summary>
|
|
public string Notes { get; set; } = string.Empty;
|
|
|
|
/// <summary>
|
|
/// Gets or sets the created timestamp.
|
|
/// </summary>
|
|
public DateTime CreatedAt { get; set; } = DateTime.MinValue;
|
|
|
|
/// <summary>
|
|
/// Gets or sets the updated timestamp.
|
|
/// </summary>
|
|
public DateTime UpdatedAt { get; set; } = DateTime.MinValue;
|
|
}
|