mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-03-29 03:53:39 -04:00
352 lines
16 KiB
C#
352 lines
16 KiB
C#
//-----------------------------------------------------------------------
|
|
// <copyright file="SmtpServerTests.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.IntegrationTests.SmtpServer;
|
|
|
|
using System.Text;
|
|
using AliasServerDb;
|
|
using AliasVault.Cryptography.Server;
|
|
using MailKit.Net.Smtp;
|
|
using MailKit.Security;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.Hosting;
|
|
using MimeKit;
|
|
|
|
/// <summary>
|
|
/// SmtpServerTests class.
|
|
/// </summary>
|
|
[TestFixture]
|
|
public class SmtpServerTests
|
|
{
|
|
/// <summary>
|
|
/// Example public key for RSA encryption tests. This is a public key generated by the JSInterop on the client.
|
|
/// We use this here to also test the server-side decryption implementation, even though this isn't a real-world scenario.
|
|
/// </summary>
|
|
public const string PublicKey = "{\"alg\":\"RSA-OAEP-256\",\"e\":\"AQAB\",\"ext\":true,\"key_ops\":[\"encrypt\"],\"kty\":\"RSA\",\"n\":\"lW8fRfSvLQiK9uZgm_kFjHMY1SedAZlVvZ_8d_d5oqWezQhan8-Y10Qvx0NMe57sQB3ePnShJFNE33w83kgRNkOyxKJ2FOVKtRptd7CgwIt_l9TPjdrB0J0hFn9b1eit2vpQlOdP_Wa8WvW2eVdXYEMWuBU4-aj8vY2qzcmBc-HhJX-Me9oXhUscJxeqMP4_sNiN7D4I0enrmYicB3JQMhUIwMmNt-0srHTdSvHh_6vFZMqB9ohfh2D9Q0BzYcI8rGEy1RTYsmF1zYyoOOzeRGOcKCVNeLO9LZxfAdm1Eq5zv47uw543cxCZXIZPlXOVriMEtTRwaGzE_3RZmpGJqw\"}";
|
|
|
|
/// <summary>
|
|
/// Example private key for RSA encryption tests. This is a private key generated by the JSInterop on the client.
|
|
/// We use this here to also test the server-side decryption implementation, even though this isn't a real-world scenario.
|
|
/// </summary>
|
|
public const string PrivateKey = "{\"alg\":\"RSA-OAEP-256\",\"d\":\"KLByToUaseNym1oNkkrTRPQOHfREXywWWaTXhP8AwtXgEKomqv9G-c6aR-K-T6btY2P-oPj268I0rbnRhSEQdrsmUT5_cp8goYGJrx6MFwGlA32x6klXnus6GDsjkXJi7I5eJL17XV99CDOBtTagFxkNdaBpvClUcHTDvncQ5bGAIrNqS7KADoi-E19BxiW_GcSJiVT4H8kDHCkcgTjZx4rKJjTPqqJOLg_poDrvnTJbsjcXP80kQ1AAENRAvDGhSWzP0IYtP1DM_2FzM1s1b_SrUsS3KiO8drR2Kv-PSOvncpaNVnZGElGCraJ3B2Mm-dr3vFjkyWeWPceqyhtYoQ\",\"dp\":\"ttxRg6uB2YLWfkPKUkzAaBWniZDHM4silJX3IgexA5GJBd9GIhUiVEolc_MgmieQbZ10CC65wqcHVv82lgCeqxYHxHWLxxJCrOpvkFlYE8wr_WqOPQEzYKv3KsL6s6Fj7Pbv9WehWpXdlbJUm4Cy5cgUkdH6PXiwBSvfhCQGrYk\",\"dq\":\"YFqlDAVTfvTR2bMJulvWzd_at81CsEmR-lPo91h-3cLpxcLDOlrTP-d3Ass2I4r1PtBT1bKuuHeQ6fZmHH55a6m8XxPEs2BuIxlh9RiFfWbd66969UOnItuawf0rfGneKt1zl4st60T3KXd8-ECrLxdsvOYpOEuNzvIY_b3qitE\",\"e\":\"AQAB\",\"ext\":true,\"key_ops\":[\"decrypt\"],\"kty\":\"RSA\",\"n\":\"lW8fRfSvLQiK9uZgm_kFjHMY1SedAZlVvZ_8d_d5oqWezQhan8-Y10Qvx0NMe57sQB3ePnShJFNE33w83kgRNkOyxKJ2FOVKtRptd7CgwIt_l9TPjdrB0J0hFn9b1eit2vpQlOdP_Wa8WvW2eVdXYEMWuBU4-aj8vY2qzcmBc-HhJX-Me9oXhUscJxeqMP4_sNiN7D4I0enrmYicB3JQMhUIwMmNt-0srHTdSvHh_6vFZMqB9ohfh2D9Q0BzYcI8rGEy1RTYsmF1zYyoOOzeRGOcKCVNeLO9LZxfAdm1Eq5zv47uw543cxCZXIZPlXOVriMEtTRwaGzE_3RZmpGJqw\",\"p\":\"yUdbuDwmVwKhou5xXUxJfi1eOjN-5F88wtyR4LpgU2OvZ7m-er4hpXx5I2E-KTVX_iIp0Q9VDXhHH-WkN3qg20RXjRoxwgrggYbfdIYdrB-2kbMamq5cOf2XbXGEO8PoDXYoZprIB0EhrD4qVVykPUYg5El0hIKPdfs9LNoOEzs\",\"q\":\"vg93lGTurG0EY179tPr6Qe3ttKEN9zvQ97dZ9034DOWDoWLe-iMKG1-yKmkG4uwC8QqNnm1mPz7EqOuHPPGVTTib9NA4JdM27PUHSPKDUvp0cV4LhF6e-W7tMFk8WbJ2ACqkqhZHYgm-FDkZBCpnehNegTxipLluKa79G__ZHFE\",\"qi\":\"fnI3Wh5aYuxI0R18NTeFKjo1P7_Ck65Gc9O3CmeqiIe58EJaXQEcdwdSOG8aVmn03szXLHEnp7anNIH63f0ericbRYdCQVhcQpvsXzEM_sp4aYmwz45palrjlY4Jc6G6XQn3FwiqqRDvpnXdsunnQ62HHhxmslaEMYHQyLng2ss\"}";
|
|
|
|
/// <summary>
|
|
/// The test host instance.
|
|
/// </summary>
|
|
private IHost _testHost;
|
|
|
|
/// <summary>
|
|
/// The test host builder instance.
|
|
/// </summary>
|
|
private TestHostBuilder _testHostBuilder;
|
|
|
|
/// <summary>
|
|
/// Setup logic for every test.
|
|
/// </summary>
|
|
/// <returns>Task.</returns>
|
|
[SetUp]
|
|
public async Task Setup()
|
|
{
|
|
_testHostBuilder = new TestHostBuilder();
|
|
_testHost = _testHostBuilder.Build();
|
|
|
|
await _testHost.StartAsync();
|
|
|
|
// Create an AliasVault user, public key and an email claim.
|
|
var dbContext = _testHostBuilder.GetDbContext();
|
|
var user = new AliasVaultUser
|
|
{
|
|
UserName = "testuser",
|
|
Email = "testuser@example.tld",
|
|
};
|
|
dbContext.AliasVaultUsers.Add(user);
|
|
await dbContext.SaveChangesAsync();
|
|
|
|
// Create email claims.
|
|
var emailClaim = new UserEmailClaim
|
|
{
|
|
UserId = user.Id,
|
|
Address = "claimed@example.tld",
|
|
AddressLocal = "claimed",
|
|
AddressDomain = "example.tld",
|
|
};
|
|
dbContext.UserEmailClaims.Add(emailClaim);
|
|
|
|
var emailClaim2 = new UserEmailClaim
|
|
{
|
|
UserId = user.Id,
|
|
Address = "claimed.cc@example.tld",
|
|
AddressLocal = "claimed.cc",
|
|
AddressDomain = "example.tld",
|
|
};
|
|
dbContext.UserEmailClaims.Add(emailClaim2);
|
|
|
|
// Create disabled email claim.
|
|
var emailClaimDisabled = new UserEmailClaim
|
|
{
|
|
UserId = user.Id,
|
|
Address = "disabled@example.tld",
|
|
AddressLocal = "disabled",
|
|
AddressDomain = "example.tld",
|
|
Disabled = true,
|
|
};
|
|
dbContext.UserEmailClaims.Add(emailClaimDisabled);
|
|
|
|
// Create public key.
|
|
var encryptionKey = new UserEncryptionKey
|
|
{
|
|
UserId = user.Id,
|
|
PublicKey = PublicKey,
|
|
IsPrimary = true,
|
|
};
|
|
dbContext.UserEncryptionKeys.Add(encryptionKey);
|
|
|
|
await dbContext.SaveChangesAsync();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tear down logic for every test.
|
|
/// </summary>
|
|
/// <returns>Task.</returns>
|
|
[TearDown]
|
|
public async Task TearDown()
|
|
{
|
|
await _testHost.StopAsync();
|
|
_testHost.Dispose();
|
|
await _testHostBuilder.DisposeAsync();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tests sending a single email in plain format to the SMTP server with valid claim to check if it is processed correctly.
|
|
/// </summary>
|
|
/// <returns>Task.</returns>
|
|
[Test]
|
|
public async Task SingleEmailPlain()
|
|
{
|
|
// Email the SMTP server.
|
|
var message = new MimeMessage();
|
|
message.From.Add(new MailboxAddress("Test Sender", "sender@example.com"));
|
|
message.To.Add(new MailboxAddress("Test Recipient", "claimed@example.tld"));
|
|
message.Subject = "Test Email";
|
|
const string textBody = "This is a test email plain.";
|
|
message.Body = new BodyBuilder { TextBody = textBody }.ToMessageBody();
|
|
await SendMessageToSmtpServer(message);
|
|
|
|
// Check if the email is in the database.
|
|
var processedEmail = await _testHostBuilder.GetDbContext().Emails.FirstAsync();
|
|
|
|
// Test non-encrypted field.
|
|
Assert.That(processedEmail.To, Is.EqualTo("claimed@example.tld"));
|
|
|
|
// Decrypt the email and then check all individual fields.
|
|
processedEmail = EmailEncryption.DecryptEmail(processedEmail, PrivateKey);
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(processedEmail, Is.Not.Null);
|
|
Assert.That(processedEmail.From, Is.EqualTo("\"Test Sender\" <sender@example.com>"));
|
|
Assert.That(processedEmail.FromLocal, Is.EqualTo("sender"));
|
|
Assert.That(processedEmail.FromDomain, Is.EqualTo("example.com"));
|
|
Assert.That(processedEmail.MessagePreview, Is.EqualTo("This is a test email plain."));
|
|
Assert.That(processedEmail.MessagePlain, Is.EqualTo("This is a test email plain."));
|
|
Assert.That(processedEmail.MessageHtml, Is.Null);
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tests sending a single email in html format to the SMTP server to check if it is processed correctly.
|
|
/// </summary>
|
|
/// <returns>Task.</returns>
|
|
[Test]
|
|
public async Task SingleEmailHtml()
|
|
{
|
|
// Arrange
|
|
var message = new MimeMessage();
|
|
message.From.Add(new MailboxAddress("Test Sender", "sender@example.com"));
|
|
message.To.Add(new MailboxAddress("Test Recipient", "claimed@example.tld"));
|
|
message.Subject = "Test Email with HTML body.";
|
|
const string htmlBody = "<html><body><h1>This is a test email html.</h1></body></html>";
|
|
message.Body = new BodyBuilder { HtmlBody = htmlBody }.ToMessageBody();
|
|
await SendMessageToSmtpServer(message);
|
|
|
|
// Check if the email is in the database.
|
|
var processedEmail = await _testHostBuilder.GetDbContext().Emails.FirstAsync();
|
|
|
|
// Test non-encrypted field.
|
|
Assert.That(processedEmail.To, Is.EqualTo("claimed@example.tld"));
|
|
|
|
// Decrypt the email and then check all individual fields.
|
|
processedEmail = EmailEncryption.DecryptEmail(processedEmail, PrivateKey);
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(processedEmail, Is.Not.Null);
|
|
Assert.That(processedEmail.MessagePreview, Is.EqualTo("This is a test email html."));
|
|
Assert.That(processedEmail.MessagePlain, Is.Null);
|
|
Assert.That(processedEmail.MessageHtml, Is.EqualTo(htmlBody));
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tests sending a single email in multipart format to the SMTP server to check if it is processed correctly.
|
|
/// </summary>
|
|
/// <returns>Task.</returns>
|
|
[Test]
|
|
public async Task SingleEmailMultipart()
|
|
{
|
|
// Arrange
|
|
var message = new MimeMessage();
|
|
message.From.Add(new MailboxAddress("Test Sender", "sender@example.com"));
|
|
message.To.Add(new MailboxAddress("Test Recipient", "claimed@example.tld"));
|
|
message.Subject = "Test Email with multipart body.";
|
|
const string textBody = "This is a test email multipart.";
|
|
const string htmlBody = "<html><body><h1>This is a test email multipart.</h1></body></html>";
|
|
message.Body = new BodyBuilder { TextBody = textBody, HtmlBody = htmlBody }.ToMessageBody();
|
|
await SendMessageToSmtpServer(message);
|
|
|
|
// Check if the email is in the database.
|
|
var processedEmail = await _testHostBuilder.GetDbContext().Emails.FirstAsync();
|
|
|
|
// Test non-encrypted field.
|
|
Assert.That(processedEmail.To, Is.EqualTo("claimed@example.tld"));
|
|
|
|
// Decrypt the email and then check all individual fields.
|
|
processedEmail = EmailEncryption.DecryptEmail(processedEmail, PrivateKey);
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(processedEmail, Is.Not.Null);
|
|
Assert.That(processedEmail.MessagePreview, Is.EqualTo("This is a test email multipart."));
|
|
Assert.That(processedEmail.MessagePlain, Is.EqualTo("This is a test email multipart."));
|
|
Assert.That(processedEmail.MessageHtml, Is.EqualTo(htmlBody));
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tests sending a single email in plain format to the SMTP server to check if it is processed correctly.
|
|
/// </summary>
|
|
/// <returns>Task.</returns>
|
|
[Test]
|
|
public async Task MultipleRecipientsEmail()
|
|
{
|
|
// Send email to the SMTP server.
|
|
var message = new MimeMessage();
|
|
message.From.Add(new MailboxAddress("Test Sender", "sender@example.com"));
|
|
message.To.Add(new MailboxAddress("Test Recipient", "claimed@example.tld"));
|
|
message.Cc.Add(new MailboxAddress("Test Recipient 2", "claimed.cc@example.tld"));
|
|
message.Cc.Add(new MailboxAddress("Test Recipient 3 unknown domain", "recipient@unknowndomain.tld"));
|
|
|
|
message.Subject = "Test Email";
|
|
const string textBody = "This is a test email plain.";
|
|
message.Body = new BodyBuilder { TextBody = textBody }.ToMessageBody();
|
|
await SendMessageToSmtpServer(message);
|
|
|
|
// Check that two emails are in the database, one for each allowed recipient.
|
|
Assert.That(await _testHostBuilder.GetDbContext().Emails.CountAsync(), Is.EqualTo(2));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tests sending an email to an unknown recipient domain, we expect to get an error from the SMTP server.
|
|
/// </summary>
|
|
[Test]
|
|
public void SingleEmailUnknownRecipientDomain()
|
|
{
|
|
// Send email to the SMTP server.
|
|
var message = new MimeMessage();
|
|
message.From.Add(new MailboxAddress("Test Sender", "sender@example.com"));
|
|
message.To.Add(new MailboxAddress("Test Recipient", "recipient@unknowndomain.tld"));
|
|
message.Subject = "Test Email";
|
|
const string textBody = "This is a test email plain.";
|
|
message.Body = new BodyBuilder { TextBody = textBody }.ToMessageBody();
|
|
|
|
// Expect error from SmtpClient when sending email to unknown domain.
|
|
Assert.ThrowsAsync<SmtpCommandException>(async () => await SendMessageToSmtpServer(message));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tests sending an email to an existing but disabled email claim, we expect to get an error from the SMTP server.
|
|
/// </summary>
|
|
[Test]
|
|
public void SingleEmailDisabledUserClaim()
|
|
{
|
|
// Send email to the SMTP server.
|
|
var message = new MimeMessage();
|
|
message.From.Add(new MailboxAddress("Test Sender", "sender@example.com"));
|
|
message.To.Add(new MailboxAddress("Test Recipient", "disabled@example.tld"));
|
|
message.Subject = "Test Email";
|
|
const string textBody = "This is a test email plain.";
|
|
message.Body = new BodyBuilder { TextBody = textBody }.ToMessageBody();
|
|
|
|
// Expect error from SmtpClient when sending email to unknown domain.
|
|
Assert.ThrowsAsync<SmtpCommandException>(async () => await SendMessageToSmtpServer(message));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tests sending a single email to a known recipient domain but with no valid user claim. We expect
|
|
/// to get an error from the SMTP server.
|
|
/// </summary>
|
|
[Test]
|
|
public void SingleEmailNoUserClaim()
|
|
{
|
|
// Send email to the SMTP server.
|
|
var message = new MimeMessage();
|
|
message.From.Add(new MailboxAddress("Test Sender", "sender@example.com"));
|
|
message.To.Add(new MailboxAddress("Test Recipient", "not-claimed@example.tld"));
|
|
message.Subject = "Test Email";
|
|
const string textBody = "This is a test email plain.";
|
|
message.Body = new BodyBuilder { TextBody = textBody }.ToMessageBody();
|
|
|
|
// Expect error from SmtpClient when sending email to unknown domain.
|
|
Assert.ThrowsAsync<SmtpCommandException>(async () => await SendMessageToSmtpServer(message));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tests sending a single email in plain format to the SMTP server to check if it is processed correctly.
|
|
/// </summary>
|
|
/// <returns>Task.</returns>
|
|
[Test]
|
|
public async Task AttachmentEmail()
|
|
{
|
|
var message = new MimeMessage();
|
|
message.From.Add(new MailboxAddress("Test Sender", "sender@example.com"));
|
|
message.To.Add(new MailboxAddress("Test Recipient", "claimed@example.tld"));
|
|
message.Subject = "Test Email with attachment";
|
|
|
|
var bodyBuilder = new BodyBuilder();
|
|
bodyBuilder.TextBody = "This is a test email with attachment.";
|
|
|
|
// Add attachment using BodyBuilder
|
|
byte[] attachmentData = Encoding.UTF8.GetBytes("This is an attachment.");
|
|
bodyBuilder.Attachments.Add("attachment.txt", attachmentData, ContentType.Parse("text/plain"));
|
|
|
|
message.Body = bodyBuilder.ToMessageBody();
|
|
|
|
await SendMessageToSmtpServer(message);
|
|
|
|
// Check that attachment is in the database and the bytes are encrypted.
|
|
Assert.That(await _testHostBuilder.GetDbContext().EmailAttachments.CountAsync(), Is.EqualTo(1));
|
|
var attachment = await _testHostBuilder.GetDbContext().EmailAttachments.FirstAsync();
|
|
Assert.That(attachment.Bytes, Is.Not.EqualTo(attachmentData), "Email attachment bytes are not encrypted. Check email encryption logic.");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sends a message to the SMTP server.
|
|
/// </summary>
|
|
/// <param name="message">MimeMessage to send.</param>
|
|
private static async Task SendMessageToSmtpServer(MimeMessage message)
|
|
{
|
|
using var client = new SmtpClient();
|
|
|
|
await client.ConnectAsync("localhost", 2525, SecureSocketOptions.None);
|
|
try
|
|
{
|
|
await client.SendAsync(message);
|
|
}
|
|
finally
|
|
{
|
|
await client.DisconnectAsync(true);
|
|
}
|
|
}
|
|
}
|