Skip to content

Commit 6d9340a

Browse files
committed
Restrict which SMTP account senders and recipients
1 parent d388b19 commit 6d9340a

19 files changed

Lines changed: 363 additions & 11 deletions

File tree

MustMail.App/Components/App.razor

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
<base href="/"/>
88
<ResourcePreloader/>
99
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" rel="stylesheet"/>
10-
<link rel="stylesheet" href="@Assets["_content/MudBlazor/MudBlazor.min.css"]"/>
10+
<link href="@Assets["_content/MudBlazor/MudBlazor.min.css"]" rel="stylesheet" />
11+
<link href="@Assets["_content/CodeBeam.MudBlazor.Extensions/MudExtensions.min.css"]" rel="stylesheet" />
12+
1113
<ImportMap/>
1214
<HeadOutlet @rendermode="new InteractiveServerRenderMode(prerender: false)"/>
1315
<!--suppress CssUnusedSymbol -->
@@ -35,6 +37,7 @@
3537
<ReconnectModal/>
3638
<script src="@Assets["_framework/blazor.web.js"]"></script>
3739
<script src="@Assets["_content/MudBlazor/MudBlazor.min.js"]"></script>
40+
<script src="@Assets["_content/CodeBeam.MudBlazor.Extensions/MudExtensions.min.js"]"></script>
3841
</body>
3942

4043
</html>

MustMail.App/Components/Pages/Admin.razor

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -817,7 +817,59 @@
817817
Margin="Margin.Dense" Label="Password" Required="true"></MudTextField>
818818
</EditTemplate>
819819
</PropertyColumn>
820-
<PropertyColumn Property="x => x.Description"/>
820+
<PropertyColumn Property="x => x.Description" />
821+
<PropertyColumn Property="x => x.AllowedSenders" Title="Allowed Senders">
822+
<CellTemplate>
823+
<MudChipSet T="string" CheckMark>
824+
@foreach (var allowedSender in context.Item.AllowedSenders)
825+
{
826+
<MudChip Text="@allowedSender.EmailAddress"> </MudChip>
827+
}
828+
829+
</MudChipSet>
830+
831+
</CellTemplate>
832+
<EditTemplate>
833+
<MudChipField T="string" Variant="Variant.Outlined" Label="Allowed Senders" ChipColor="Color.Secondary"
834+
Values="@(context.Item.AllowedSenders?.Select(x => x.EmailAddress).ToList() ?? [])"
835+
ValuesChanged="@(values =>
836+
{
837+
context.Item.AllowedSenders = values
838+
.Select(x => new SMTPAccountAllowedSender
839+
{
840+
EmailAddress = x,
841+
SMTPAccountId = context.Item.Id
842+
})
843+
.ToList();
844+
})" />
845+
</EditTemplate>
846+
</PropertyColumn>
847+
<PropertyColumn Property="x => x.AllowedRecipients" Title="Allowed Recipients">
848+
<CellTemplate>
849+
<MudChipSet T="string" CheckMark>
850+
@foreach (var allowedRecipient in context.Item.AllowedRecipients)
851+
{
852+
<MudChip Text="@allowedRecipient.EmailAddress"> </MudChip>
853+
}
854+
855+
</MudChipSet>
856+
857+
</CellTemplate>
858+
<EditTemplate>
859+
<MudChipField T="string" Variant="Variant.Outlined" Label="Allowed Recipients" ChipColor="Color.Secondary"
860+
Values="@(context.Item.AllowedRecipients?.Select(x => x.EmailAddress).ToList() ?? [])"
861+
ValuesChanged="@(values =>
862+
{
863+
context.Item.AllowedRecipients = values
864+
.Select(x => new SMTPAccountAllowedRecipient
865+
{
866+
EmailAddress = x,
867+
SMTPAccountId = context.Item.Id
868+
})
869+
.ToList();
870+
})" />
871+
</EditTemplate>
872+
</PropertyColumn>
821873
<TemplateColumn Title="Actions">
822874
<CellTemplate>
823875
<MudStack Row Justify="Justify.SpaceBetween">

MustMail.App/Components/Pages/Admin.razor.cs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ protected override async Task OnInitializedAsync()
3636
Config = Configuration.Get<Configuration>()!;
3737

3838
Users = await dbContext.User.ToListAsync();
39-
SMTPAccounts = await dbContext.SMTPAccount.ToListAsync();
39+
SMTPAccounts = await dbContext.SMTPAccount.Include(a => a.AllowedSenders).Include(a => a.AllowedRecipients).ToListAsync();
4040
}
4141

4242
// New SMTP account - start editing a new SMTP account in form modal
@@ -85,7 +85,7 @@ protected async Task<DataGridEditFormAction> SMTPAccountItemChanges(SMTPAccount
8585
}
8686

8787
// Get item from database
88-
SMTPAccount? smtpAccount = await dbContext.SMTPAccount.FindAsync(item.Id);
88+
SMTPAccount? smtpAccount = await dbContext.SMTPAccount.Include(a => a.AllowedSenders).Include(a => a.AllowedRecipients).SingleAsync(a => a.Id == item.Id);
8989

9090
if (smtpAccount == null)
9191
return DataGridEditFormAction.Close;
@@ -97,6 +97,26 @@ protected async Task<DataGridEditFormAction> SMTPAccountItemChanges(SMTPAccount
9797
// Update values in DB
9898
dbContext.Entry(smtpAccount).CurrentValues.SetValues(item);
9999

100+
smtpAccount.AllowedSenders.Clear();
101+
102+
foreach (var sender in item.AllowedSenders)
103+
{
104+
smtpAccount.AllowedSenders.Add(new SMTPAccountAllowedSender
105+
{
106+
EmailAddress = sender.EmailAddress
107+
});
108+
}
109+
110+
smtpAccount.AllowedRecipients.Clear();
111+
112+
foreach (var recipient in item.AllowedRecipients)
113+
{
114+
smtpAccount.AllowedRecipients.Add(new SMTPAccountAllowedRecipient
115+
{
116+
EmailAddress = recipient.EmailAddress
117+
});
118+
}
119+
100120
_ = await dbContext.SaveChangesAsync();
101121

102122
_ = Snackbar.Add($"SMTP Account updated successfully!", Severity.Success);

MustMail.App/Components/_Imports.razor

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
@using Microsoft.JSInterop
99
@using MudBlazor
1010
@using MudBlazor.Services
11+
@using MudExtensions
1112
@using MustMail.App
1213
@using MustMail.App.Components
1314
@using MustMail.App.Components.Layout

MustMail.App/Models/SMTPAccount.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,7 @@ public class SMTPAccount
1414
public required string Password { get; set; }
1515
[MaxLength(255)]
1616
public required string Description { get; set; }
17+
public ICollection<SMTPAccountAllowedSender> AllowedSenders { get; set; } = [];
18+
public ICollection<SMTPAccountAllowedRecipient> AllowedRecipients { get; set; } = [];
1719

1820
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using System.ComponentModel.DataAnnotations;
2+
using System.ComponentModel.DataAnnotations.Schema;
3+
using System.Text.Json.Serialization;
4+
5+
namespace MustMail.App.Models
6+
{
7+
public class SMTPAccountAllowedRecipient
8+
{
9+
[Key]
10+
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
11+
public int Id { get; set; }
12+
13+
[MaxLength(255)]
14+
public required string EmailAddress { get; set; }
15+
16+
public int SMTPAccountId { get; set; }
17+
[JsonIgnore]
18+
public SMTPAccount SMTPAccount { get; set; } = null!;
19+
}
20+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using System.ComponentModel.DataAnnotations;
2+
using System.ComponentModel.DataAnnotations.Schema;
3+
using System.Text.Json.Serialization;
4+
5+
namespace MustMail.App.Models
6+
{
7+
public class SMTPAccountAllowedSender
8+
{
9+
[Key]
10+
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
11+
public int Id { get; set; }
12+
13+
[MaxLength(255)]
14+
public required string EmailAddress { get; set; }
15+
16+
public int SMTPAccountId { get; set; }
17+
[JsonIgnore]
18+
public SMTPAccount SMTPAccount { get; set; } = null!;
19+
}
20+
}

MustMail.App/MustMail.App.csproj

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313

1414
<ItemGroup>
15+
<PackageReference Include="CodeBeam.MudBlazor.Extensions" Version="9.1.0" />
1516
<PackageReference Include="dbup" Version="5.0.41" />
1617
<PackageReference Include="dbup-core" Version="6.1.1" />
1718
<PackageReference Include="dbup-mysql" Version="6.1.0" />
@@ -86,4 +87,8 @@
8687
</Content>
8788
</ItemGroup>
8889

90+
<ItemGroup>
91+
<Content Remove="..\MustMail.Migrations.Sqlite\Scripts\0001_Initial.sql" />
92+
</ItemGroup>
93+
8994
</Project>

MustMail.App/Program.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
using Microsoft.Graph;
1212
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
1313
using MudBlazor.Extensions;
14+
using MudExtensions.Services;
1415
using MudBlazor.Services;
1516
using MustMail.App;
1617
using MustMail.App.Auth;
@@ -482,6 +483,8 @@
482483
builder.Services.AddSingleton<RecipientResolver>();
483484
builder.Services.AddSingleton<SenderResolver>();
484485

486+
builder.Services.AddSingleton<SmtpAccountAuthorization>();
487+
485488
// Add attachment handler for extracting attachments from the message and then reattaching them using graph
486489
builder.Services.AddSingleton<AttachmentHandler>();
487490

@@ -516,6 +519,9 @@
516519
// Add MudBlazor services
517520
builder.Services.AddMudServices();
518521

522+
// Add MudBlazor Extensions services
523+
builder.Services.AddMudExtensions();
524+
519525
// Add services to the container.
520526
builder.Services.AddRazorComponents()
521527
.AddInteractiveServerComponents();

MustMail.App/Services/MailProcessing/MessageHandler.cs

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
namespace MustMail.App.Services.MailProcessing;
1313

14-
public partial class MessageHandler(ILogger<MessageHandler> logger, GraphServiceClient graphClient, IOptionsMonitor<Configuration> config, RecipientResolver recipientsResolver, SenderResolver senderResolver, AttachmentHandler attachmentHandler, MessageStorage messageStorage) : MessageStore
14+
public partial class MessageHandler(ILogger<MessageHandler> logger, GraphServiceClient graphClient, IOptionsMonitor<Configuration> config, RecipientResolver recipientsResolver, SenderResolver senderResolver, SmtpAccountAuthorization smtpAccountAuthorization, AttachmentHandler attachmentHandler, MessageStorage messageStorage) : MessageStore
1515
{
1616
public override async Task<SmtpResponse> SaveAsync(ISessionContext context, IMessageTransaction transaction, ReadOnlySequence<byte> buffer, CancellationToken cancellationToken)
1717
{
@@ -54,6 +54,13 @@ public override async Task<SmtpResponse> SaveAsync(ISessionContext context, IMes
5454
return SmtpResponse.NoValidRecipientsGiven;
5555
}
5656

57+
bool recipientsAllowed = await smtpAccountAuthorization.CheckRecipientIsAllowed(context.Authentication.User, recipients.All);
58+
if (!recipientsAllowed)
59+
{
60+
LogRecipientsNotAllowed(context.Authentication.User);
61+
return SmtpResponse.NoValidRecipientsGiven;
62+
}
63+
5764
// Get sender from message and SMTP transaction
5865
ResolvedSender sender = await senderResolver.ResolveSender(transaction, message);
5966

@@ -69,6 +76,13 @@ public override async Task<SmtpResponse> SaveAsync(ISessionContext context, IMes
6976
return SmtpResponse.SyntaxError;
7077
}
7178

79+
bool senderAllowed = await smtpAccountAuthorization.CheckSenderIsAllowed(context.Authentication.User, sender.Address);
80+
if (!senderAllowed)
81+
{
82+
LogSenderNotAllowed(context.Authentication.User, sender.Address);
83+
return SmtpResponse.MailboxNameNotAllowed;
84+
}
85+
7286
List<Attachment> attachments = [];
7387

7488
// If message contains attachments then extract them from the message
@@ -180,13 +194,19 @@ public override async Task<SmtpResponse> SaveAsync(ISessionContext context, IMes
180194
[LoggerMessage(EventId = 1104, Level = LogLevel.Warning, Message = "Message rejected: no valid recipients were found")]
181195
private partial void LogNoRecipients();
182196

183-
[LoggerMessage(EventId = 1105, Level = LogLevel.Debug, Message = "Sending email via Microsoft Graph: \n{Message}")]
197+
[LoggerMessage(EventId = 1105, Level = LogLevel.Warning, Message = "Message rejected: The SMTP account: {Account} is not allowed to send mail to one or more of the recipients")]
198+
private partial void LogRecipientsNotAllowed(string account);
199+
200+
[LoggerMessage(EventId = 1106, Level = LogLevel.Warning, Message = "Message rejected: The SMTP account: {Account} is not allowed to send mail from {Sender}")]
201+
private partial void LogSenderNotAllowed(string account, string sender);
202+
203+
[LoggerMessage(EventId = 1107, Level = LogLevel.Debug, Message = "Sending email via Microsoft Graph: \n{Message}")]
184204
private partial void LogGraphSendAttempt(string message);
185205

186-
[LoggerMessage(EventId = 1106, Level = LogLevel.Error, Message = "Failed to send email via Microsoft Graph for sender {Sender}")]
206+
[LoggerMessage(EventId = 1108, Level = LogLevel.Error, Message = "Failed to send email via Microsoft Graph for sender {Sender}")]
187207
private partial void LogGraphSendFailed(Exception exception, string sender);
188208

189-
[LoggerMessage(EventId = 1107, Level = LogLevel.Information, Message = "Email forwarded successfully. Subject: {Subject}, Sender: {Sender} as the User(UPN): {User}, Recipients; To: {To}, Cc: {Cc}, Bcc: {Bcc}")]
209+
[LoggerMessage(EventId = 1109, Level = LogLevel.Information, Message = "Email forwarded successfully. Subject: {Subject}, Sender: {Sender} as the User(UPN): {User}, Recipients; To: {To}, Cc: {Cc}, Bcc: {Bcc}")]
190210
private partial void LogEmailForwarded(string? subject, string sender, string user, IEnumerable<string> to, IEnumerable<string> cc, IEnumerable<string> bcc);
191211

192212

0 commit comments

Comments
 (0)