1
0
mirror of https://github.com/bitwarden/server.git synced 2025-04-04 20:50:21 -05:00

Merge branch 'main' into jmccannon/ac/pm-15420-managed-to-claimed

This commit is contained in:
jrmccannon 2025-04-04 08:49:23 -05:00
commit 963aa8b339
No known key found for this signature in database
GPG Key ID: CF03F3DB01CE96A6
13 changed files with 469 additions and 104 deletions

View File

@ -127,6 +127,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Infrastructure.Dapper.Test"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Events.IntegrationTest", "test\Events.IntegrationTest\Events.IntegrationTest.csproj", "{4F4C63A9-AEE2-48C4-AB86-A5BCD665E401}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Events.IntegrationTest", "test\Events.IntegrationTest\Events.IntegrationTest.csproj", "{4F4C63A9-AEE2-48C4-AB86-A5BCD665E401}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core.IntegrationTest", "test\Core.IntegrationTest\Core.IntegrationTest.csproj", "{3631BA42-6731-4118-A917-DAA43C5032B9}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -319,6 +321,10 @@ Global
{4F4C63A9-AEE2-48C4-AB86-A5BCD665E401}.Debug|Any CPU.Build.0 = Debug|Any CPU {4F4C63A9-AEE2-48C4-AB86-A5BCD665E401}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4F4C63A9-AEE2-48C4-AB86-A5BCD665E401}.Release|Any CPU.ActiveCfg = Release|Any CPU {4F4C63A9-AEE2-48C4-AB86-A5BCD665E401}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4F4C63A9-AEE2-48C4-AB86-A5BCD665E401}.Release|Any CPU.Build.0 = Release|Any CPU {4F4C63A9-AEE2-48C4-AB86-A5BCD665E401}.Release|Any CPU.Build.0 = Release|Any CPU
{3631BA42-6731-4118-A917-DAA43C5032B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3631BA42-6731-4118-A917-DAA43C5032B9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3631BA42-6731-4118-A917-DAA43C5032B9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3631BA42-6731-4118-A917-DAA43C5032B9}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -370,6 +376,7 @@ Global
{90D85D8F-5577-4570-A96E-5A2E185F0F6F} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84F} {90D85D8F-5577-4570-A96E-5A2E185F0F6F} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84F}
{4A725DB3-BE4F-4C23-9087-82D0610D67AF} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84F} {4A725DB3-BE4F-4C23-9087-82D0610D67AF} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84F}
{4F4C63A9-AEE2-48C4-AB86-A5BCD665E401} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84F} {4F4C63A9-AEE2-48C4-AB86-A5BCD665E401} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84F}
{3631BA42-6731-4118-A917-DAA43C5032B9} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84F}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {E01CBF68-2E20-425F-9EDB-E0A6510CA92F} SolutionGuid = {E01CBF68-2E20-425F-9EDB-E0A6510CA92F}

View File

@ -2,7 +2,6 @@
<PropertyGroup> <PropertyGroup>
<UserSecretsId>bitwarden-Billing</UserSecretsId> <UserSecretsId>bitwarden-Billing</UserSecretsId>
<MvcRazorCompileOnPublish>false</MvcRazorCompileOnPublish>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(RunConfiguration)' == 'Billing' " /> <PropertyGroup Condition=" '$(RunConfiguration)' == 'Billing' " />

View File

@ -87,8 +87,7 @@ public class Startup
// TODO: no longer be required - see PM-1880 // TODO: no longer be required - see PM-1880
services.AddScoped<IServiceAccountRepository, NoopServiceAccountRepository>(); services.AddScoped<IServiceAccountRepository, NoopServiceAccountRepository>();
// Mvc services.AddControllers(config =>
services.AddMvc(config =>
{ {
config.Filters.Add(new LoggingExceptionHandlerFilterAttribute()); config.Filters.Add(new LoggingExceptionHandlerFilterAttribute());
}); });

View File

@ -1,6 +0,0 @@
@{
ViewData["Title"] = "Index";
}
<h2>Index</h2>

View File

@ -1,21 +0,0 @@
@model LoginModel
@{
ViewData["Title"] = "Login";
}
<div class="row justify-content-md-center">
<div class="col-4">
<p>Please enter your email address below to log in.</p>
<form asp-action="" method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Email" class="sr-only">Email Address</label>
<input asp-for="Email" type="email" class="form-control" placeholder="ex. john@example.com"
required autofocus>
<span asp-validation-for="Email" class="invalid-feedback"></span>
<small class="form-text text-body-secondary">We'll email you a secure login link.</small>
</div>
<button class="btn btn-primary btn-block" type="submit">Continue</button>
</form>
</div>
</div>

View File

@ -1,14 +0,0 @@
@{
ViewData["Title"] = "Error";
}
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
<h3>Development Mode</h3>
<p>
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
</p>
<p>
<strong>Development environment should not be enabled in deployed applications</strong>, as it can result in sensitive information from exceptions being displayed to end users. For local debugging, development environment can be enabled by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>, and restarting the application.
</p>

View File

@ -1,41 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] | Bitwarden Billing Portal</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"
integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet"
integrity="sha384-wvfXpqpZZVQGK6TAh5PVlGOfQNHSoD2xbE+QkPxCAFlNEevoEH3Sl0sibVcOQVnN" crossorigin="anonymous">
<link rel="stylesheet" href="~/styles/billing.css">
</head>
<body>
<nav class="navbar navbar-expand-md navbar-dark bg-dark mb-4">
<div class="container">
<a class="navbar-brand" href="#"><i class="fa fa-lg fa-fw fa-shield"></i> Billing</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarCollapse"
aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarCollapse">
<ul class="navbar-nav mr-auto">
<li class="nav-item active">
<a class="nav-link" href="#">Home <span class="sr-only">(current)</span></a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Link</a>
</li>
</ul>
</div>
</div>
</nav>
<main role="main" class="container">
@RenderBody()
</main>
@RenderSection("Scripts", required: false)
</body>
</html>

View File

@ -1,3 +0,0 @@
@using Bit.Billing
@using Bit.Billing.Models
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

View File

@ -1,3 +0,0 @@
@{
Layout = "_Layout";
}

View File

@ -1,6 +0,0 @@
.custom-select.input-validation-error ~ .invalid-feedback,
.custom-select.input-validation-error ~ .invalid-tooltip,
.form-control.input-validation-error ~ .invalid-feedback,
.form-control.input-validation-error ~ .invalid-tooltip {
display: block;
}

View File

@ -34,6 +34,9 @@ public class MailKitSmtpMailDeliveryService : IMailDeliveryService
} }
public async Task SendEmailAsync(Models.Mail.MailMessage message) public async Task SendEmailAsync(Models.Mail.MailMessage message)
=> await SendEmailAsync(message, CancellationToken.None);
public async Task SendEmailAsync(Models.Mail.MailMessage message, CancellationToken cancellationToken)
{ {
var mimeMessage = new MimeMessage(); var mimeMessage = new MimeMessage();
mimeMessage.From.Add(new MailboxAddress(_globalSettings.SiteName, _replyEmail)); mimeMessage.From.Add(new MailboxAddress(_globalSettings.SiteName, _replyEmail));
@ -76,25 +79,37 @@ public class MailKitSmtpMailDeliveryService : IMailDeliveryService
if (!_globalSettings.Mail.Smtp.StartTls && !_globalSettings.Mail.Smtp.Ssl && if (!_globalSettings.Mail.Smtp.StartTls && !_globalSettings.Mail.Smtp.Ssl &&
_globalSettings.Mail.Smtp.Port == 25) _globalSettings.Mail.Smtp.Port == 25)
{ {
await client.ConnectAsync(_globalSettings.Mail.Smtp.Host, _globalSettings.Mail.Smtp.Port, await client.ConnectAsync(
MailKit.Security.SecureSocketOptions.None); _globalSettings.Mail.Smtp.Host,
_globalSettings.Mail.Smtp.Port,
MailKit.Security.SecureSocketOptions.None,
cancellationToken
);
} }
else else
{ {
var useSsl = _globalSettings.Mail.Smtp.Port == 587 && !_globalSettings.Mail.Smtp.SslOverride ? var useSsl = _globalSettings.Mail.Smtp.Port == 587 && !_globalSettings.Mail.Smtp.SslOverride ?
false : _globalSettings.Mail.Smtp.Ssl; false : _globalSettings.Mail.Smtp.Ssl;
await client.ConnectAsync(_globalSettings.Mail.Smtp.Host, _globalSettings.Mail.Smtp.Port, useSsl); await client.ConnectAsync(
_globalSettings.Mail.Smtp.Host,
_globalSettings.Mail.Smtp.Port,
useSsl,
cancellationToken
);
} }
if (CoreHelpers.SettingHasValue(_globalSettings.Mail.Smtp.Username) && if (CoreHelpers.SettingHasValue(_globalSettings.Mail.Smtp.Username) &&
CoreHelpers.SettingHasValue(_globalSettings.Mail.Smtp.Password)) CoreHelpers.SettingHasValue(_globalSettings.Mail.Smtp.Password))
{ {
await client.AuthenticateAsync(_globalSettings.Mail.Smtp.Username, await client.AuthenticateAsync(
_globalSettings.Mail.Smtp.Password); _globalSettings.Mail.Smtp.Username,
_globalSettings.Mail.Smtp.Password,
cancellationToken
);
} }
await client.SendAsync(mimeMessage); await client.SendAsync(mimeMessage, cancellationToken);
await client.DisconnectAsync(true); await client.DisconnectAsync(true, cancellationToken);
} }
} }
} }

View File

@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.0" />
<PackageReference Include="MartinCostello.Logging.XUnit" Version="0.5.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="Rnwood.SmtpServer" Version="3.1.0-ci0868" />
<PackageReference Include="xunit" Version="2.5.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Core\Core.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,410 @@
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using Bit.Core.Models.Mail;
using Bit.Core.Services;
using Bit.Core.Settings;
using MailKit.Security;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Rnwood.SmtpServer;
using Rnwood.SmtpServer.Extensions.Auth;
using Xunit.Abstractions;
namespace Bit.Core.IntegrationTest;
public class MailKitSmtpMailDeliveryServiceTests
{
private readonly X509Certificate2 _selfSignedCert;
public MailKitSmtpMailDeliveryServiceTests(ITestOutputHelper testOutputHelper)
{
ConfigureSmtpServerLogging(testOutputHelper);
_selfSignedCert = CreateSelfSignedCert("localhost");
}
private static X509Certificate2 CreateSelfSignedCert(string commonName)
{
using var rsa = RSA.Create(2048);
var certRequest = new CertificateRequest($"CN={commonName}", rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
return certRequest.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddYears(1));
}
private static async Task SaveCertAsync(string filePath, X509Certificate2 certificate)
{
await File.WriteAllBytesAsync(filePath, certificate.Export(X509ContentType.Cert));
}
private static void ConfigureSmtpServerLogging(ITestOutputHelper testOutputHelper)
{
// Unfortunately this package doesn't public expose its logging infrastructure
// so we use private reflection to try and access it.
try
{
var loggingType = typeof(DefaultServerBehaviour).Assembly.GetType("Rnwood.SmtpServer.Logging")
?? throw new Exception("No type found in RnWood.SmtpServer named 'Logging'");
var factoryProperty = loggingType.GetProperty("Factory")
?? throw new Exception($"No property named 'Factory' found on class {loggingType.FullName}");
var factoryPropertyGet = factoryProperty.GetMethod
?? throw new Exception($"{loggingType.FullName}.{factoryProperty.Name} does not have a get method.");
if (factoryPropertyGet.Invoke(null, null) is not ILoggerFactory loggerFactory)
{
throw new Exception($"{loggingType.FullName}.{factoryProperty.Name} is not of type 'ILoggerFactory'" +
$"instead it's type '{factoryProperty.PropertyType.FullName}'");
}
loggerFactory.AddXUnit(testOutputHelper);
}
catch (Exception ex)
{
testOutputHelper.WriteLine($"Failed to configure logging for RnWood.SmtpServer (logging will not be configured):\n{ex.Message}");
}
}
private static int RandomPort()
{
return Random.Shared.Next(50000, 60000);
}
private static GlobalSettings GetSettings(Action<GlobalSettings> configure)
{
var globalSettings = new GlobalSettings();
globalSettings.SiteName = "TestSiteName";
globalSettings.Mail.ReplyToEmail = "test@example.com";
globalSettings.Mail.Smtp.Host = "localhost";
// Set common defaults
configure(globalSettings);
return globalSettings;
}
[Fact]
public async Task SendEmailAsync_SmtpServerUsingSelfSignedCert_CertNotInTrustedRootStore_ThrowsException()
{
// If an SMTP server is using a self signed cert we currently require
// that the certificate for their SMTP server is installed in the root CA
// we are building the ability to do so without installing it, when we add that
// this test can be copied, and changed to utilize that new feature and instead of
// failing it should successfully send the email.
var port = RandomPort();
var behavior = new DefaultServerBehaviour(false, port, _selfSignedCert);
using var smtpServer = new SmtpServer(behavior);
smtpServer.Start();
var globalSettings = GetSettings(gs =>
{
gs.Mail.Smtp.Port = port;
gs.Mail.Smtp.Ssl = true;
});
var mailKitDeliveryService = new MailKitSmtpMailDeliveryService(
globalSettings,
NullLogger<MailKitSmtpMailDeliveryService>.Instance
);
await Assert.ThrowsAsync<SslHandshakeException>(
async () => await mailKitDeliveryService.SendEmailAsync(new MailMessage
{
Subject = "Test",
ToEmails = ["test@example.com"],
TextContent = "Hi",
}, new CancellationTokenSource(TimeSpan.FromSeconds(5)).Token)
);
}
[Fact(Skip = "Upcoming feature")]
public async Task SendEmailAsync_SmtpServerUsingSelfSignedCert_CertInCustomLocation_Works()
{
// If an SMTP server is using a self signed cert we will in the future
// allow a custom location for certificates to be stored and the certitifactes
// stored there will also be trusted.
var port = RandomPort();
var behavior = new DefaultServerBehaviour(false, port, _selfSignedCert);
using var smtpServer = new SmtpServer(behavior);
smtpServer.Start();
var globalSettings = GetSettings(gs =>
{
gs.Mail.Smtp.Port = port;
gs.Mail.Smtp.Ssl = true;
});
// TODO: Setup custom location and save self signed cert there.
// await SaveCertAsync("./my-location", _selfSignedCert);
var mailKitDeliveryService = new MailKitSmtpMailDeliveryService(
globalSettings,
NullLogger<MailKitSmtpMailDeliveryService>.Instance
);
var tcs = new TaskCompletionSource();
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
cts.Token.Register(() => _ = tcs.TrySetCanceled());
behavior.MessageReceivedEventHandler += (sender, args) =>
{
if (args.Message.Recipients.Contains("test1@example.com"))
{
tcs.SetResult();
}
return Task.CompletedTask;
};
await mailKitDeliveryService.SendEmailAsync(new MailMessage
{
Subject = "Test",
ToEmails = ["test1@example.com"],
TextContent = "Hi",
}, cts.Token);
// Wait for email
await tcs.Task;
}
[Fact(Skip = "Upcoming feature")]
public async Task SendEmailAsync_SmtpServerUsingSelfSignedCert_CertInCustomLocation_WithUnrelatedCerts_Works()
{
// If an SMTP server is using a self signed cert we will in the future
// allow a custom location for certificates to be stored and the certitifactes
// stored there will also be trusted.
var port = RandomPort();
var behavior = new DefaultServerBehaviour(false, port, _selfSignedCert);
using var smtpServer = new SmtpServer(behavior);
smtpServer.Start();
var globalSettings = GetSettings(gs =>
{
gs.Mail.Smtp.Port = port;
gs.Mail.Smtp.Ssl = true;
});
// TODO: Setup custom location and save self signed cert there
// along with another self signed cert that is not related to
// the SMTP server.
// await SaveCertAsync("./my-location", _selfSignedCert);
// await SaveCertAsync("./my-location", CreateSelfSignedCert("example.com"));
var mailKitDeliveryService = new MailKitSmtpMailDeliveryService(
globalSettings,
NullLogger<MailKitSmtpMailDeliveryService>.Instance
);
var tcs = new TaskCompletionSource();
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
cts.Token.Register(() => _ = tcs.TrySetCanceled());
behavior.MessageReceivedEventHandler += (sender, args) =>
{
if (args.Message.Recipients.Contains("test1@example.com"))
{
tcs.SetResult();
}
return Task.CompletedTask;
};
await mailKitDeliveryService.SendEmailAsync(new MailMessage
{
Subject = "Test",
ToEmails = ["test1@example.com"],
TextContent = "Hi",
}, cts.Token);
// Wait for email
await tcs.Task;
}
[Fact]
public async Task SendEmailAsync_Succeeds_WhenCertIsSelfSigned_ServerIsTrusted()
{
// When the setting `TrustServer = true` is set even if the cert is
// self signed and the cert is not trusted in anyway the connection should
// still go through.
var port = RandomPort();
var behavior = new DefaultServerBehaviour(false, port, _selfSignedCert);
using var smtpServer = new SmtpServer(behavior);
smtpServer.Start();
var globalSettings = GetSettings(gs =>
{
gs.Mail.Smtp.Port = port;
gs.Mail.Smtp.Ssl = true;
gs.Mail.Smtp.TrustServer = true;
});
var mailKitDeliveryService = new MailKitSmtpMailDeliveryService(
globalSettings,
NullLogger<MailKitSmtpMailDeliveryService>.Instance
);
var tcs = new TaskCompletionSource();
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
cts.Token.Register(() => _ = tcs.TrySetCanceled());
behavior.MessageReceivedEventHandler += (sender, args) =>
{
if (args.Message.Recipients.Contains("test1@example.com"))
{
tcs.SetResult();
}
return Task.CompletedTask;
};
await mailKitDeliveryService.SendEmailAsync(new MailMessage
{
Subject = "Test",
ToEmails = ["test1@example.com"],
TextContent = "Hi",
}, cts.Token);
// Wait for email
await tcs.Task;
}
[Fact]
public async Task SendEmailAsync_FailsConnectingWithTls_ServerDoesNotSupportTls()
{
// If the SMTP server is not setup to use TLS but our server is expecting it
// to, we should fail.
var port = RandomPort();
var behavior = new DefaultServerBehaviour(false, port);
using var smtpServer = new SmtpServer(behavior);
smtpServer.Start();
var globalSettings = GetSettings(gs =>
{
gs.Mail.Smtp.Port = port;
gs.Mail.Smtp.Ssl = true;
gs.Mail.Smtp.TrustServer = true;
});
var mailKitDeliveryService = new MailKitSmtpMailDeliveryService(
globalSettings,
NullLogger<MailKitSmtpMailDeliveryService>.Instance
);
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
await Assert.ThrowsAsync<SslHandshakeException>(
async () => await mailKitDeliveryService.SendEmailAsync(new MailMessage
{
Subject = "Test",
ToEmails = ["test1@example.com"],
TextContent = "Hi",
}, cts.Token)
);
}
[Fact(Skip = "Requires permission to privileged port")]
public async Task SendEmailAsync_Works_NoSsl()
{
// If the SMTP server isn't set up with any SSL/TLS and we dont' expect
// any, then the email should go through just fine. Just without encryption.
// This test has to use port 25
var port = 25;
var behavior = new DefaultServerBehaviour(false, port);
using var smtpServer = new SmtpServer(behavior);
smtpServer.Start();
var globalSettings = GetSettings(gs =>
{
gs.Mail.Smtp.Port = port;
gs.Mail.Smtp.Ssl = false;
gs.Mail.Smtp.StartTls = false;
});
var mailKitDeliveryService = new MailKitSmtpMailDeliveryService(
globalSettings,
NullLogger<MailKitSmtpMailDeliveryService>.Instance
);
var tcs = new TaskCompletionSource();
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
cts.Token.Register(() => _ = tcs.TrySetCanceled());
behavior.MessageReceivedEventHandler += (sender, args) =>
{
if (args.Message.Recipients.Contains("test1@example.com"))
{
tcs.SetResult();
}
return Task.CompletedTask;
};
await mailKitDeliveryService.SendEmailAsync(new MailMessage
{
Subject = "Test",
ToEmails = ["test1@example.com"],
TextContent = "Hi",
}, cts.Token);
// Wait for email
await tcs.Task;
}
[Fact]
public async Task SendEmailAsync_Succeeds_WhenServerNeedsToAuthenticate()
{
// When the setting `TrustServer = true` is set even if the cert is
// self signed and the cert is not trusted in anyway the connection should
// still go through.
var port = RandomPort();
var behavior = new DefaultServerBehaviour(false, port, _selfSignedCert);
behavior.AuthenticationCredentialsValidationRequiredEventHandler += (sender, args) =>
{
args.AuthenticationResult = AuthenticationResult.Failure;
if (args.Credentials is not UsernameAndPasswordAuthenticationCredentials usernameAndPasswordCreds)
{
return Task.CompletedTask;
}
if (usernameAndPasswordCreds.Username != "test" || usernameAndPasswordCreds.Password != "password")
{
return Task.CompletedTask;
}
args.AuthenticationResult = AuthenticationResult.Success;
return Task.CompletedTask;
};
using var smtpServer = new SmtpServer(behavior);
smtpServer.Start();
var globalSettings = GetSettings(gs =>
{
gs.Mail.Smtp.Port = port;
gs.Mail.Smtp.Ssl = true;
gs.Mail.Smtp.TrustServer = true;
gs.Mail.Smtp.Username = "test";
gs.Mail.Smtp.Password = "password";
});
var mailKitDeliveryService = new MailKitSmtpMailDeliveryService(
globalSettings,
NullLogger<MailKitSmtpMailDeliveryService>.Instance
);
var tcs = new TaskCompletionSource();
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
cts.Token.Register(() => _ = tcs.TrySetCanceled());
behavior.MessageReceivedEventHandler += (sender, args) =>
{
if (args.Message.Recipients.Contains("test1@example.com"))
{
tcs.SetResult();
}
return Task.CompletedTask;
};
await mailKitDeliveryService.SendEmailAsync(new MailMessage
{
Subject = "Test",
ToEmails = ["test1@example.com"],
TextContent = "Hi",
}, cts.Token);
// Wait for email
await tcs.Task;
}
}