From fd2d676c026bde70c27455e0b0c000fc2859c878 Mon Sep 17 00:00:00 2001 From: Gilles TOURREAU Date: Sun, 16 Nov 2025 11:19:41 +0100 Subject: [PATCH 01/15] Upgrade NuGet packages. --- Directory.Packages.props | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 57dedcc..f3bbb90 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -7,21 +7,21 @@ - - + + - - + + - + - + - + From afd0edf882a479c9846adba84f84ff7f33564dc1 Mon Sep 17 00:00:00 2001 From: Gilles TOURREAU Date: Sun, 16 Nov 2025 13:59:39 +0100 Subject: [PATCH 02/15] Uses the Emailing feature of PosInformatique.Foundations (fixes: #2). --- Directory.Packages.props | 7 +- src/Core/AppRegistrationSecretCheckResult.cs | 7 +- src/Core/AppRegistrationSecretManager.cs | 30 +- .../AppRegistrationSecretManagerOptions.cs | 2 - src/Core/Core.csproj | 14 +- src/Core/Emailing/EmailContact.cs | 23 -- src/Core/Emailing/EmailMessage.cs | 27 -- src/Core/Emailing/EmailTemplates.cs | 20 + src/Core/Emailing/Graph/GraphEmailProvider.cs | 54 --- src/Core/Emailing/IEmailGenerator.cs | 13 - src/Core/Emailing/IEmailProvider.cs | 13 - .../Emailing/ReportEmailTemplateBody.razor | 202 +++++++++++ .../Emailing/ReportEmailTemplateSubject.razor | 3 + src/Core/Emailing/ScribanEmailGenerator.cs | 39 -- src/Core/_Imports.razor | 2 + ...AppRegistrationSecretWatcherApplication.cs | 23 +- src/Functions/Functions.csproj | 1 + .../AppRegistrationSecretCheckResultTest.cs | 5 +- ...AppRegistrationSecretManagerOptionsTest.cs | 15 - .../AppRegistrationSecretManagerTest.cs | 146 ++++---- tests/Core.Tests/Emailing/EmailContactTest.cs | 24 -- tests/Core.Tests/Emailing/EmailMessageTest.cs | 29 -- .../Emailing/Graph/GraphEmailProviderTest.cs | 69 ---- ...ilGeneratorTest.GenerateAsync.verified.txt | 341 ------------------ .../Emailing/ScribanEmailGeneratorTest.cs | 69 ---- 25 files changed, 335 insertions(+), 843 deletions(-) delete mode 100644 src/Core/Emailing/EmailContact.cs delete mode 100644 src/Core/Emailing/EmailMessage.cs create mode 100644 src/Core/Emailing/EmailTemplates.cs delete mode 100644 src/Core/Emailing/Graph/GraphEmailProvider.cs delete mode 100644 src/Core/Emailing/IEmailGenerator.cs delete mode 100644 src/Core/Emailing/IEmailProvider.cs create mode 100644 src/Core/Emailing/ReportEmailTemplateBody.razor create mode 100644 src/Core/Emailing/ReportEmailTemplateSubject.razor delete mode 100644 src/Core/Emailing/ScribanEmailGenerator.cs create mode 100644 src/Core/_Imports.razor delete mode 100644 tests/Core.Tests/Emailing/EmailContactTest.cs delete mode 100644 tests/Core.Tests/Emailing/EmailMessageTest.cs delete mode 100644 tests/Core.Tests/Emailing/Graph/GraphEmailProviderTest.cs delete mode 100644 tests/Core.Tests/Emailing/ScribanEmailGeneratorTest.GenerateAsync.verified.txt delete mode 100644 tests/Core.Tests/Emailing/ScribanEmailGeneratorTest.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index f3bbb90..8664866 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,6 +1,8 @@  true + + 1.0.0-alpha.15 @@ -16,9 +18,10 @@ - + + + - diff --git a/src/Core/AppRegistrationSecretCheckResult.cs b/src/Core/AppRegistrationSecretCheckResult.cs index 96e0554..695c67c 100644 --- a/src/Core/AppRegistrationSecretCheckResult.cs +++ b/src/Core/AppRegistrationSecretCheckResult.cs @@ -8,11 +8,16 @@ namespace PosInformatique.Azure.Identity.AppRegistrationSecretWatcher { public class AppRegistrationSecretCheckResult { - public AppRegistrationSecretCheckResult(IReadOnlyList tenants) + public AppRegistrationSecretCheckResult(IReadOnlyList tenants, DateTime dateTime) { + Guard.IsUtc(dateTime, nameof(dateTime)); + + this.DateTime = dateTime; this.Tenants = new ReadOnlyCollection(tenants.ToArray()); } public ReadOnlyCollection Tenants { get; } + + public DateTime DateTime { get; } } } \ No newline at end of file diff --git a/src/Core/AppRegistrationSecretManager.cs b/src/Core/AppRegistrationSecretManager.cs index 354c118..a588a99 100644 --- a/src/Core/AppRegistrationSecretManager.cs +++ b/src/Core/AppRegistrationSecretManager.cs @@ -15,19 +15,16 @@ public class AppRegistrationSecretManager : IAppRegistrationSecretManager { private readonly IEntraIdClient entraIdApplicationClient; - private readonly IEmailProvider emailProvider; - - private readonly IEmailGenerator emailGenerator; + private readonly IEmailManager emailManager; private readonly AppRegistrationSecretManagerOptions options; private readonly TimeProvider timeProvider; - public AppRegistrationSecretManager(IEntraIdClient entraIdApplicationClient, IEmailProvider emailProvider, IEmailGenerator emailGenerator, TimeProvider timeProvider, IOptions options) + public AppRegistrationSecretManager(IEntraIdClient entraIdApplicationClient, IEmailManager emailManager, TimeProvider timeProvider, IOptions options) { this.entraIdApplicationClient = entraIdApplicationClient; - this.emailProvider = emailProvider; - this.emailGenerator = emailGenerator; + this.emailManager = emailManager; this.timeProvider = timeProvider; this.options = options.Value; } @@ -42,11 +39,8 @@ public async Task CheckAsync(AppRegistrationSe // Check the result var result = this.BuildResult(applicationsByTenant, now, parameters.ExpirationThreshold); - // Generate the e-mail - var emailContent = await this.emailGenerator.GenerateAsync(result, cancellationToken); - // Send e-mail - await this.SendEmailAsync(emailContent, now, cancellationToken); + await this.SendEmailAsync(result, cancellationToken); return result; } @@ -82,7 +76,7 @@ private AppRegistrationSecretCheckResult BuildResult(IReadOnlyList(); } - public EmailAddress EmailSender { get; set; } = default!; - public Collection EmailRecipients { get; } } } \ No newline at end of file diff --git a/src/Core/Core.csproj b/src/Core/Core.csproj index 1346af2..ce12f0f 100644 --- a/src/Core/Core.csproj +++ b/src/Core/Core.csproj @@ -1,21 +1,11 @@ - + - - - - - - - - - - PosInformatique.Azure.Identity.AppRegistrationSecretWatcher.Emailing.EmailTemplate.html - + diff --git a/src/Core/Emailing/EmailContact.cs b/src/Core/Emailing/EmailContact.cs deleted file mode 100644 index b378c1d..0000000 --- a/src/Core/Emailing/EmailContact.cs +++ /dev/null @@ -1,23 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) P.O.S Informatique. All rights reserved. -// -//----------------------------------------------------------------------- - -namespace PosInformatique.Foundations.Emailing -{ - using PosInformatique.Foundations.EmailAddresses; - - public class EmailContact - { - public EmailContact(EmailAddress email, string displayName) - { - this.Email = email; - this.DisplayName = displayName; - } - - public EmailAddress Email { get; } - - public string DisplayName { get; } - } -} \ No newline at end of file diff --git a/src/Core/Emailing/EmailMessage.cs b/src/Core/Emailing/EmailMessage.cs deleted file mode 100644 index af6c6ac..0000000 --- a/src/Core/Emailing/EmailMessage.cs +++ /dev/null @@ -1,27 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) P.O.S Informatique. All rights reserved. -// -//----------------------------------------------------------------------- - -namespace PosInformatique.Foundations.Emailing -{ - public sealed class EmailMessage - { - public EmailMessage(EmailContact from, EmailContact to, string subject, string htmlContent) - { - this.From = from; - this.To = to; - this.Subject = subject; - this.HtmlContent = htmlContent; - } - - public EmailContact From { get; } - - public EmailContact To { get; } - - public string Subject { get; } - - public string HtmlContent { get; } - } -} \ No newline at end of file diff --git a/src/Core/Emailing/EmailTemplates.cs b/src/Core/Emailing/EmailTemplates.cs new file mode 100644 index 0000000..311c282 --- /dev/null +++ b/src/Core/Emailing/EmailTemplates.cs @@ -0,0 +1,20 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) P.O.S Informatique. All rights reserved. +// +//----------------------------------------------------------------------- + +namespace PosInformatique.Azure.Identity.AppRegistrationSecretWatcher.Emailing +{ + using System.Diagnostics.CodeAnalysis; + using PosInformatique.Foundations.Emailing; + using PosInformatique.Foundations.Emailing.Templates.Razor; + + [ExcludeFromCodeCoverage] + public static class EmailTemplates + { + public static EmailTemplateIdentifier ReportIdentifier { get; } = EmailTemplateIdentifier.Create(); + + public static EmailTemplate Report { get; } = RazorEmailTemplate.Create(); + } +} \ No newline at end of file diff --git a/src/Core/Emailing/Graph/GraphEmailProvider.cs b/src/Core/Emailing/Graph/GraphEmailProvider.cs deleted file mode 100644 index 7f6bcb6..0000000 --- a/src/Core/Emailing/Graph/GraphEmailProvider.cs +++ /dev/null @@ -1,54 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) P.O.S Informatique. All rights reserved. -// -//----------------------------------------------------------------------- - -namespace PosInformatique.Foundations.Emailing.Graph -{ - using Microsoft.Graph; - using Microsoft.Graph.Models; - using Microsoft.Graph.Users.Item.SendMail; - - public sealed class GraphEmailProvider : IEmailProvider - { - private readonly GraphServiceClient serviceClient; - - public GraphEmailProvider(GraphServiceClient serviceClient) - { - this.serviceClient = serviceClient; - } - - public async Task SendAsync(EmailMessage message, CancellationToken cancellationToken = default) - { - var graphMessage = new Message() - { - Body = new ItemBody - { - ContentType = BodyType.Html, - Content = message.HtmlContent, - }, - Subject = message.Subject, - ToRecipients = new List - { - new() - { - EmailAddress = new EmailAddress - { - Address = message.To.Email.ToString(), - Name = message.To.DisplayName, - }, - }, - }, - }; - - var body = new SendMailPostRequestBody() - { - Message = graphMessage, - SaveToSentItems = false, - }; - - await this.serviceClient.Users[message.From.Email.ToString()].SendMail.PostAsync(body, cancellationToken: cancellationToken); - } - } -} \ No newline at end of file diff --git a/src/Core/Emailing/IEmailGenerator.cs b/src/Core/Emailing/IEmailGenerator.cs deleted file mode 100644 index 8372c9c..0000000 --- a/src/Core/Emailing/IEmailGenerator.cs +++ /dev/null @@ -1,13 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) P.O.S Informatique. All rights reserved. -// -//----------------------------------------------------------------------- - -namespace PosInformatique.Azure.Identity.AppRegistrationSecretWatcher.Emailing -{ - public interface IEmailGenerator - { - Task GenerateAsync(AppRegistrationSecretCheckResult result, CancellationToken cancellationToken = default); - } -} \ No newline at end of file diff --git a/src/Core/Emailing/IEmailProvider.cs b/src/Core/Emailing/IEmailProvider.cs deleted file mode 100644 index 301b294..0000000 --- a/src/Core/Emailing/IEmailProvider.cs +++ /dev/null @@ -1,13 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) P.O.S Informatique. All rights reserved. -// -//----------------------------------------------------------------------- - -namespace PosInformatique.Foundations.Emailing -{ - public interface IEmailProvider - { - Task SendAsync(EmailMessage message, CancellationToken cancellationToken = default); - } -} \ No newline at end of file diff --git a/src/Core/Emailing/ReportEmailTemplateBody.razor b/src/Core/Emailing/ReportEmailTemplateBody.razor new file mode 100644 index 0000000..4779fc6 --- /dev/null +++ b/src/Core/Emailing/ReportEmailTemplateBody.razor @@ -0,0 +1,202 @@ +@namespace PosInformatique.Azure.Identity.AppRegistrationSecretWatcher.Emailing +@inherits RazorEmailTemplateBody + + + + + + + +

Entra ID app registrations secret expiration report

+ + @foreach (var tenant in this.Model.Tenants) + { +
+

@tenant.DisplayName

+ +
+ @if (tenant.Applications.Count == 0) + { +
No application
+ } + else + { + foreach (var application in tenant.Applications) + { +
+

@application.DisplayName

+ +
+ @if (application.Secrets.Count == 0) + { +
No secret
+ } + else + { + foreach (var secret in application.Secrets) + { +
+

@secret.DisplayName

+
+
+
Status:
+
@secret.Status
+
+
+
Expiration date:
+
@secret.EndDate.ToString("d-MMM-yyyy")
+
+
+ @if (secret.DaysBeforeExpiration > 0) + { +
Days before expiration:
+
@secret.DaysBeforeExpiration days
+ } + else + { +
Expired since:
+
@(-secret.DaysBeforeExpiration) days
+ } +
+
+
+ } + } +
+
+ } + } +
+
+ } + + + +@code +{ + public static string StatusToCss(AppRegistrationSecretStatus status) + { + return status switch + { + AppRegistrationSecretStatus.Expired => "expired", + AppRegistrationSecretStatus.ExpiringSoon => "expiring-soon", + _ => "valid", + }; + } +} \ No newline at end of file diff --git a/src/Core/Emailing/ReportEmailTemplateSubject.razor b/src/Core/Emailing/ReportEmailTemplateSubject.razor new file mode 100644 index 0000000..adff9b1 --- /dev/null +++ b/src/Core/Emailing/ReportEmailTemplateSubject.razor @@ -0,0 +1,3 @@ +@namespace PosInformatique.Azure.Identity.AppRegistrationSecretWatcher.Emailing +@inherits RazorEmailTemplateSubject +Reminder: App Registration secrets expiring soon - [@this.Model.DateTime.ToString("d")] \ No newline at end of file diff --git a/src/Core/Emailing/ScribanEmailGenerator.cs b/src/Core/Emailing/ScribanEmailGenerator.cs deleted file mode 100644 index 42ae33e..0000000 --- a/src/Core/Emailing/ScribanEmailGenerator.cs +++ /dev/null @@ -1,39 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) P.O.S Informatique. All rights reserved. -// -//----------------------------------------------------------------------- - -namespace PosInformatique.Azure.Identity.AppRegistrationSecretWatcher.Emailing -{ - using Scriban; - using Scriban.Runtime; - - public class ScribanEmailGenerator : IEmailGenerator - { - public async Task GenerateAsync(AppRegistrationSecretCheckResult result, CancellationToken cancellationToken = default) - { - using var htmlTemplateStream = typeof(ScribanEmailGenerator).Assembly.GetManifestResourceStream("PosInformatique.Azure.Identity.AppRegistrationSecretWatcher.Emailing.EmailTemplate.html")!; - using var reader = new StreamReader(htmlTemplateStream); - - var htmlTemplate = await reader.ReadToEndAsync(cancellationToken); - - var scribanTemplate = Template.Parse(htmlTemplate); - - var scriptObject = new ScriptObject - { - { nameof(result.Tenants), result.Tenants }, - }; - - var context = new TemplateContext() - { - MemberRenamer = r => r.Name, - MemberFilter = null, - }; - - context.PushGlobal(scriptObject); - - return await scribanTemplate.RenderAsync(context); - } - } -} \ No newline at end of file diff --git a/src/Core/_Imports.razor b/src/Core/_Imports.razor new file mode 100644 index 0000000..cec21aa --- /dev/null +++ b/src/Core/_Imports.razor @@ -0,0 +1,2 @@ +@using PosInformatique.Foundations.Emailing.Templates.Razor +@using Microsoft.AspNetCore.Components.Web \ No newline at end of file diff --git a/src/Functions/AppRegistrationSecretWatcherApplication.cs b/src/Functions/AppRegistrationSecretWatcherApplication.cs index 8775b53..7ee644d 100644 --- a/src/Functions/AppRegistrationSecretWatcherApplication.cs +++ b/src/Functions/AppRegistrationSecretWatcherApplication.cs @@ -1,4 +1,4 @@ -//----------------------------------------------------------------------- +//----------------------------------------------------------------------- // // Copyright (c) P.O.S Informatique. All rights reserved. // @@ -12,12 +12,9 @@ namespace PosInformatique.Azure.Identity.AppRegistrationSecretWatcher.Functions using Microsoft.Azure.Functions.Worker.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; - using Microsoft.Graph; using PosInformatique.Azure.Identity.AppRegistrationSecretWatcher.Emailing; using PosInformatique.Azure.Identity.AppRegistrationSecretWatcher.EntraId; using PosInformatique.Foundations.EmailAddresses; - using PosInformatique.Foundations.Emailing; - using PosInformatique.Foundations.Emailing.Graph; public static class AppRegistrationSecretWatcherApplication { @@ -96,16 +93,18 @@ public static async Task Main(string[] args) .ConfigureFunctionsApplicationInsights(); // Emailing - builder.Services.AddSingleton(); - builder.Services.AddSingleton(); + builder.Services.AddEmailing(opt => + { + opt.SenderEmailAddress = emailSender; + + opt.RegisterTemplate(EmailTemplates.ReportIdentifier, EmailTemplates.Report); + }) + .UseGraph(new DefaultAzureCredential()) + .UseRazorEmailTemplates(); // Graph API builder.Services.AddSingleton(); builder.Services.AddSingleton(); - builder.Services.AddSingleton(sp => - { - return new GraphServiceClient(new DefaultAzureCredential()); - }); builder.Services.Configure(opt => { @@ -114,12 +113,10 @@ public static async Task Main(string[] args) }); // App registrations secret manager - builder.Services.AddSingleton(); + builder.Services.AddScoped(); builder.Services.Configure(opt => { - opt.EmailSender = emailSender; - foreach (var recipientEmailAddress in recipientEmailAddresses) { opt.EmailRecipients.Add(recipientEmailAddress); diff --git a/src/Functions/Functions.csproj b/src/Functions/Functions.csproj index 772275c..8e0cf1a 100644 --- a/src/Functions/Functions.csproj +++ b/src/Functions/Functions.csproj @@ -13,6 +13,7 @@ +
diff --git a/tests/Core.Tests/AppRegistrationSecretCheckResultTest.cs b/tests/Core.Tests/AppRegistrationSecretCheckResultTest.cs index bd140b4..cfe68bd 100644 --- a/tests/Core.Tests/AppRegistrationSecretCheckResultTest.cs +++ b/tests/Core.Tests/AppRegistrationSecretCheckResultTest.cs @@ -17,8 +17,11 @@ public void Constructor() new AppRegistrationSecretCheckResultTenant(default, default, []), }; - var tenant = new AppRegistrationSecretCheckResult(tenants); + var dateTime = new DateTime(2024, 1, 2, 3, 4, 5, 6, 7, DateTimeKind.Utc); + var tenant = new AppRegistrationSecretCheckResult(tenants, dateTime); + + tenant.DateTime.Should().Be(dateTime); tenant.Tenants.Should().Equal(tenants); } } diff --git a/tests/Core.Tests/AppRegistrationSecretManagerOptionsTest.cs b/tests/Core.Tests/AppRegistrationSecretManagerOptionsTest.cs index 6e51917..a1b75bb 100644 --- a/tests/Core.Tests/AppRegistrationSecretManagerOptionsTest.cs +++ b/tests/Core.Tests/AppRegistrationSecretManagerOptionsTest.cs @@ -6,8 +6,6 @@ namespace PosInformatique.Azure.Identity.AppRegistrationSecretWatcher.Tests { - using PosInformatique.Foundations.EmailAddresses; - public class AppRegistrationSecretManagerOptionsTest { [Fact] @@ -15,20 +13,7 @@ public void Constructor() { var options = new AppRegistrationSecretManagerOptions(); - options.EmailSender.Should().BeNull(); options.EmailRecipients.Should().BeEmpty(); } - - [Fact] - public void EmailSender_ValueChanged() - { - var options = new AppRegistrationSecretManagerOptions(); - - var sender = EmailAddress.Parse("email@domain.com"); - - options.EmailSender = sender; - - options.EmailSender.Should().Be(sender); - } } } \ No newline at end of file diff --git a/tests/Core.Tests/AppRegistrationSecretManagerTest.cs b/tests/Core.Tests/AppRegistrationSecretManagerTest.cs index dc321a1..8058762 100644 --- a/tests/Core.Tests/AppRegistrationSecretManagerTest.cs +++ b/tests/Core.Tests/AppRegistrationSecretManagerTest.cs @@ -65,81 +65,29 @@ public async Task CheckAsync() ]) ])); - var emailProvider = new Mock(MockBehavior.Strict); - emailProvider.Setup(ep => ep.SendAsync(It.Is(m => m.To.Email.ToString() == "email1@domain.com"), cancellationToken)) - .Callback((EmailMessage m, CancellationToken _) => - { - m.HtmlContent.Should().Be("The content"); - m.From.DisplayName.Should().BeEmpty(); - m.From.Email.Should().Be(EmailAddress.Parse("sender@domain.com")); - m.Subject.Should().Be($"Reminder: App Registration secrets expiring soon - [{new DateTime(2025, 6, 15):d}]"); - m.To.DisplayName.Should().BeEmpty(); - }) - .Returns(Task.CompletedTask); - emailProvider.Setup(ep => ep.SendAsync(It.Is(m => m.To.Email.ToString() == "email2@domain.com"), cancellationToken)) - .Callback((EmailMessage m, CancellationToken _) => - { - m.HtmlContent.Should().Be("The content"); - m.From.DisplayName.Should().BeEmpty(); - m.From.Email.Should().Be(EmailAddress.Parse("sender@domain.com")); - m.Subject.Should().Be($"Reminder: App Registration secrets expiring soon - [{new DateTime(2025, 6, 15):d}]"); - m.To.DisplayName.Should().BeEmpty(); - }) - .Returns(Task.CompletedTask); + var email = new Email(EmailTemplates.Report); - var emailGenerator = new Mock(MockBehavior.Strict); - emailGenerator.Setup(g => g.GenerateAsync(It.IsAny(), cancellationToken)) - .Callback((AppRegistrationSecretCheckResult r, CancellationToken _) => + var emailManager = new Mock(MockBehavior.Strict); + emailManager.Setup(em => em.Create(EmailTemplates.ReportIdentifier)) + .Returns(email); + + emailManager.Setup(g => g.SendAsync(It.IsAny>(), cancellationToken)) + .Callback((Email e, CancellationToken _) => { - r.Tenants.Should().HaveCount(2); - - r.Tenants[0].DisplayName.Should().Be("The tenant display name 1"); - r.Tenants[0].Id.Should().Be("The tenant ID 1"); - r.Tenants[0].Applications.Should().HaveCount(2); - r.Tenants[0].Applications[0].DisplayName.Should().Be("App 1-1"); - r.Tenants[0].Applications[0].Id.Should().Be("1-1"); - r.Tenants[0].Applications[0].Secrets.Should().HaveCount(2); - r.Tenants[0].Applications[0].Secrets[0].DaysBeforeExpiration.Should().Be(60); - r.Tenants[0].Applications[0].Secrets[0].DisplayName.Should().Be("Secret 1-1-1"); - r.Tenants[0].Applications[0].Secrets[0].EndDate.Should().Be(now.AddDays(60).AddHours(8)).And.BeIn(DateTimeKind.Local); - r.Tenants[0].Applications[0].Secrets[0].Status.Should().Be(AppRegistrationSecretStatus.Valid); - r.Tenants[0].Applications[0].Secrets[1].DaysBeforeExpiration.Should().Be(10); - r.Tenants[0].Applications[0].Secrets[1].DisplayName.Should().Be("Secret 1-1-2"); - r.Tenants[0].Applications[0].Secrets[1].EndDate.Should().Be(now.AddDays(10).AddHours(8)).And.BeIn(DateTimeKind.Local); - r.Tenants[0].Applications[0].Secrets[1].Status.Should().Be(AppRegistrationSecretStatus.ExpiringSoon); - r.Tenants[0].Applications[1].DisplayName.Should().Be("App 1-2"); - r.Tenants[0].Applications[1].Id.Should().Be("1-2"); - r.Tenants[0].Applications[1].Secrets.Should().HaveCount(2); - r.Tenants[0].Applications[1].Secrets[0].DaysBeforeExpiration.Should().Be(30); - r.Tenants[0].Applications[1].Secrets[0].DisplayName.Should().Be("Secret 1-2-1"); - r.Tenants[0].Applications[1].Secrets[0].EndDate.Should().Be(now.AddDays(30).AddHours(8)).And.BeIn(DateTimeKind.Local); - r.Tenants[0].Applications[1].Secrets[0].Status.Should().Be(AppRegistrationSecretStatus.ExpiringSoon); - r.Tenants[0].Applications[1].Secrets[1].DaysBeforeExpiration.Should().Be(120); - r.Tenants[0].Applications[1].Secrets[1].DisplayName.Should().Be("Secret 1-2-2"); - r.Tenants[0].Applications[1].Secrets[1].EndDate.Should().Be(now.AddDays(120).AddHours(8)).And.BeIn(DateTimeKind.Local); - r.Tenants[0].Applications[1].Secrets[1].Status.Should().Be(AppRegistrationSecretStatus.Valid); - - r.Tenants[1].DisplayName.Should().Be("The tenant display name 2"); - r.Tenants[1].Id.Should().Be("The tenant ID 2"); - r.Tenants[1].Applications.Should().HaveCount(2); - r.Tenants[1].Applications[0].DisplayName.Should().Be("App 2-1"); - r.Tenants[1].Applications[0].Id.Should().Be("2-1"); - r.Tenants[1].Applications[0].Secrets.Should().HaveCount(1); - r.Tenants[1].Applications[0].Secrets[0].DaysBeforeExpiration.Should().Be(-100); - r.Tenants[1].Applications[0].Secrets[0].DisplayName.Should().Be("Secret 2-1-1"); - r.Tenants[1].Applications[0].Secrets[0].EndDate.Should().Be(now.AddDays(-100).AddHours(8)).And.BeIn(DateTimeKind.Local); - r.Tenants[1].Applications[0].Secrets[0].Status.Should().Be(AppRegistrationSecretStatus.Expired); - r.Tenants[1].Applications[1].DisplayName.Should().Be("App 2-2"); - r.Tenants[1].Applications[1].Id.Should().Be("2-2"); - r.Tenants[1].Applications[1].Secrets.Should().HaveCount(1); - r.Tenants[1].Applications[1].Secrets[0].DaysBeforeExpiration.Should().Be(300); - r.Tenants[1].Applications[1].Secrets[0].DisplayName.Should().Be("Secret 2-2-1"); - r.Tenants[1].Applications[1].Secrets[0].EndDate.Should().Be(now.AddDays(300).AddHours(8)).And.BeIn(DateTimeKind.Local); - r.Tenants[1].Applications[1].Secrets[0].Status.Should().Be(AppRegistrationSecretStatus.Valid); - - expectedResult = r; + e.Should().BeSameAs(email); + + expectedResult = email.Recipients[0].Model; + + e.Recipients.Should().HaveCount(2); + + e.Recipients[0].Address.Should().Be(EmailAddress.Parse("email1@domain.com")); + e.Recipients[0].DisplayName.Should().BeEmpty(); + + e.Recipients[1].Address.Should().Be(EmailAddress.Parse("email2@domain.com")); + e.Recipients[1].DisplayName.Should().BeEmpty(); + e.Recipients[1].Model.Should().BeSameAs(expectedResult); }) - .ReturnsAsync("The content"); + .Returns(Task.CompletedTask); var timeProvider = new Mock(MockBehavior.Strict); timeProvider.Setup(tp => tp.GetUtcNow()) @@ -154,7 +102,6 @@ public async Task CheckAsync() EmailAddress.Parse("email1@domain.com"), EmailAddress.Parse("email2@domain.com"), }, - EmailSender = EmailAddress.Parse("sender@domain.com"), }); var parameters = new AppRegistrationSecretCheckParameters() @@ -167,14 +114,61 @@ public async Task CheckAsync() }, }; - var manager = new AppRegistrationSecretManager(entraIdClient.Object, emailProvider.Object, emailGenerator.Object, timeProvider.Object, options); + var manager = new AppRegistrationSecretManager(entraIdClient.Object, emailManager.Object, timeProvider.Object, options); var result = await manager.CheckAsync(parameters, cancellationToken); result.Should().BeSameAs(expectedResult); - emailGenerator.VerifyAll(); - emailProvider.VerifyAll(); + result.DateTime.Should().Be(now).And.BeIn(DateTimeKind.Utc); + + result.Tenants.Should().HaveCount(2); + + result.Tenants[0].DisplayName.Should().Be("The tenant display name 1"); + result.Tenants[0].Id.Should().Be("The tenant ID 1"); + result.Tenants[0].Applications.Should().HaveCount(2); + result.Tenants[0].Applications[0].DisplayName.Should().Be("App 1-1"); + result.Tenants[0].Applications[0].Id.Should().Be("1-1"); + result.Tenants[0].Applications[0].Secrets.Should().HaveCount(2); + result.Tenants[0].Applications[0].Secrets[0].DaysBeforeExpiration.Should().Be(60); + result.Tenants[0].Applications[0].Secrets[0].DisplayName.Should().Be("Secret 1-1-1"); + result.Tenants[0].Applications[0].Secrets[0].EndDate.Should().Be(now.AddDays(60).AddHours(8)).And.BeIn(DateTimeKind.Local); + result.Tenants[0].Applications[0].Secrets[0].Status.Should().Be(AppRegistrationSecretStatus.Valid); + result.Tenants[0].Applications[0].Secrets[1].DaysBeforeExpiration.Should().Be(10); + result.Tenants[0].Applications[0].Secrets[1].DisplayName.Should().Be("Secret 1-1-2"); + result.Tenants[0].Applications[0].Secrets[1].EndDate.Should().Be(now.AddDays(10).AddHours(8)).And.BeIn(DateTimeKind.Local); + result.Tenants[0].Applications[0].Secrets[1].Status.Should().Be(AppRegistrationSecretStatus.ExpiringSoon); + result.Tenants[0].Applications[1].DisplayName.Should().Be("App 1-2"); + result.Tenants[0].Applications[1].Id.Should().Be("1-2"); + result.Tenants[0].Applications[1].Secrets.Should().HaveCount(2); + result.Tenants[0].Applications[1].Secrets[0].DaysBeforeExpiration.Should().Be(30); + result.Tenants[0].Applications[1].Secrets[0].DisplayName.Should().Be("Secret 1-2-1"); + result.Tenants[0].Applications[1].Secrets[0].EndDate.Should().Be(now.AddDays(30).AddHours(8)).And.BeIn(DateTimeKind.Local); + result.Tenants[0].Applications[1].Secrets[0].Status.Should().Be(AppRegistrationSecretStatus.ExpiringSoon); + result.Tenants[0].Applications[1].Secrets[1].DaysBeforeExpiration.Should().Be(120); + result.Tenants[0].Applications[1].Secrets[1].DisplayName.Should().Be("Secret 1-2-2"); + result.Tenants[0].Applications[1].Secrets[1].EndDate.Should().Be(now.AddDays(120).AddHours(8)).And.BeIn(DateTimeKind.Local); + result.Tenants[0].Applications[1].Secrets[1].Status.Should().Be(AppRegistrationSecretStatus.Valid); + + result.Tenants[1].DisplayName.Should().Be("The tenant display name 2"); + result.Tenants[1].Id.Should().Be("The tenant ID 2"); + result.Tenants[1].Applications.Should().HaveCount(2); + result.Tenants[1].Applications[0].DisplayName.Should().Be("App 2-1"); + result.Tenants[1].Applications[0].Id.Should().Be("2-1"); + result.Tenants[1].Applications[0].Secrets.Should().HaveCount(1); + result.Tenants[1].Applications[0].Secrets[0].DaysBeforeExpiration.Should().Be(-100); + result.Tenants[1].Applications[0].Secrets[0].DisplayName.Should().Be("Secret 2-1-1"); + result.Tenants[1].Applications[0].Secrets[0].EndDate.Should().Be(now.AddDays(-100).AddHours(8)).And.BeIn(DateTimeKind.Local); + result.Tenants[1].Applications[0].Secrets[0].Status.Should().Be(AppRegistrationSecretStatus.Expired); + result.Tenants[1].Applications[1].DisplayName.Should().Be("App 2-2"); + result.Tenants[1].Applications[1].Id.Should().Be("2-2"); + result.Tenants[1].Applications[1].Secrets.Should().HaveCount(1); + result.Tenants[1].Applications[1].Secrets[0].DaysBeforeExpiration.Should().Be(300); + result.Tenants[1].Applications[1].Secrets[0].DisplayName.Should().Be("Secret 2-2-1"); + result.Tenants[1].Applications[1].Secrets[0].EndDate.Should().Be(now.AddDays(300).AddHours(8)).And.BeIn(DateTimeKind.Local); + result.Tenants[1].Applications[1].Secrets[0].Status.Should().Be(AppRegistrationSecretStatus.Valid); + + emailManager.VerifyAll(); entraIdClient.VerifyAll(); timeProvider.VerifyAll(); } diff --git a/tests/Core.Tests/Emailing/EmailContactTest.cs b/tests/Core.Tests/Emailing/EmailContactTest.cs deleted file mode 100644 index a8a99f0..0000000 --- a/tests/Core.Tests/Emailing/EmailContactTest.cs +++ /dev/null @@ -1,24 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) P.O.S Informatique. All rights reserved. -// -//----------------------------------------------------------------------- - -namespace PosInformatique.Foundations.Emailing.Tests -{ - using PosInformatique.Foundations.EmailAddresses; - - public class EmailContactTest - { - [Fact] - public void Constructor() - { - var emailAddress = EmailAddress.Parse("user@domain.com"); - - var contact = new EmailContact(emailAddress, "The display name"); - - contact.Email.Should().BeSameAs(emailAddress); - contact.DisplayName.Should().Be("The display name"); - } - } -} \ No newline at end of file diff --git a/tests/Core.Tests/Emailing/EmailMessageTest.cs b/tests/Core.Tests/Emailing/EmailMessageTest.cs deleted file mode 100644 index 42443db..0000000 --- a/tests/Core.Tests/Emailing/EmailMessageTest.cs +++ /dev/null @@ -1,29 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) P.O.S Informatique. All rights reserved. -// -//----------------------------------------------------------------------- - -namespace PosInformatique.Foundations.Emailing.Tests -{ - public class EmailMessageTest - { - [Fact] - public void Constructor() - { - var from = new EmailContact(default, default); - var to = new EmailContact(default, default); - - var emailMessage = new EmailMessage( - from, - to, - "The subject", - "HTML content"); - - emailMessage.From.Should().Be(from); - emailMessage.HtmlContent.Should().Be("HTML content"); - emailMessage.Subject.Should().Be("The subject"); - emailMessage.To.Should().Be(to); - } - } -} \ No newline at end of file diff --git a/tests/Core.Tests/Emailing/Graph/GraphEmailProviderTest.cs b/tests/Core.Tests/Emailing/Graph/GraphEmailProviderTest.cs deleted file mode 100644 index 455e078..0000000 --- a/tests/Core.Tests/Emailing/Graph/GraphEmailProviderTest.cs +++ /dev/null @@ -1,69 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) P.O.S Informatique. All rights reserved. -// -//----------------------------------------------------------------------- - -namespace PosInformatique.Foundations.Emailing.Graph.Tests -{ - using Microsoft.Graph; - using Microsoft.Graph.Models; - using Microsoft.Graph.Users.Item.SendMail; - using Microsoft.Kiota.Abstractions; - using Microsoft.Kiota.Abstractions.Serialization; - using Microsoft.Kiota.Serialization.Json; - - public class GraphEmailProviderTest - { - [Fact] - public async Task SendAsync() - { - var cancellationToken = new CancellationTokenSource().Token; - - var serializationWriterFactory = new Mock(MockBehavior.Strict); - serializationWriterFactory.Setup(f => f.GetSerializationWriter("application/json")) - .Returns(new JsonSerializationWriter()); - - var requestAdapter = new Mock(MockBehavior.Strict); - requestAdapter.Setup(r => r.BaseUrl) - .Returns("http://base/url"); - requestAdapter.Setup(r => r.EnableBackingStore(null)); - requestAdapter.Setup(r => r.SerializationWriterFactory) - .Returns(serializationWriterFactory.Object); - requestAdapter.Setup(r => r.SendNoContentAsync(It.IsAny(), It.IsNotNull>>(), cancellationToken)) - .Callback((RequestInformation requestInfo, Dictionary> _, CancellationToken _) => - { - requestInfo.HttpMethod.Should().Be(Method.POST); - requestInfo.URI.Should().Be("http://base/url/users/sender%40domain.com/sendMail"); - - var jsonMessage = KiotaJsonSerializer.DeserializeAsync(requestInfo.Content).GetAwaiter().GetResult(); - - jsonMessage.Message.Attachments.Should().BeNull(); - jsonMessage.Message.Body.Content.Should().Be("The HTML content"); - jsonMessage.Message.Body.ContentType.Should().Be(BodyType.Html); - jsonMessage.Message.BccRecipients.Should().BeNull(); - jsonMessage.Message.CcRecipients.Should().BeNull(); - jsonMessage.Message.ToRecipients.Should().HaveCount(1); - jsonMessage.Message.ToRecipients[0].EmailAddress.Address.Should().Be("recipient@domain.com"); - jsonMessage.Message.ToRecipients[0].EmailAddress.Name.Should().Be("The recipient"); - jsonMessage.SaveToSentItems.Should().BeFalse(); - }) - .Returns(Task.CompletedTask); - - var graphServiceClient = new Mock(MockBehavior.Strict, requestAdapter.Object, null); - - var client = new GraphEmailProvider(graphServiceClient.Object); - - var from = new EmailContact(EmailAddresses.EmailAddress.Parse("sender@domain.com"), "The sender"); - var to = new EmailContact(EmailAddresses.EmailAddress.Parse("recipient@domain.com"), "The recipient"); - - var message = new EmailMessage(from, to, "The subject", "The HTML content"); - - await client.SendAsync(message, cancellationToken); - - graphServiceClient.VerifyAll(); - requestAdapter.VerifyAll(); - serializationWriterFactory.VerifyAll(); - } - } -} \ No newline at end of file diff --git a/tests/Core.Tests/Emailing/ScribanEmailGeneratorTest.GenerateAsync.verified.txt b/tests/Core.Tests/Emailing/ScribanEmailGeneratorTest.GenerateAsync.verified.txt deleted file mode 100644 index 941dcff..0000000 --- a/tests/Core.Tests/Emailing/ScribanEmailGeneratorTest.GenerateAsync.verified.txt +++ /dev/null @@ -1,341 +0,0 @@ - - - - - - -

Entra ID app registrations secret expiration report

- - - - -
-

The tenant 1

- -
- - -
-

The app 1-1

- -
- - -
-

Secret 1-1-1

-
-
-
Status:
-
Expired
-
-
-
Expiration date:
-
1-Jan-2025
-
-
- -
Expired since:
-
10 days
- -
-
-
- -
-

Secret 1-1-2

-
-
-
Status:
-
Valid
-
-
-
Expiration date:
-
2-Feb-2025
-
-
- -
Days before expiration:
-
20 days
- -
-
-
- - -
-
- -
-

The app 1-2

- -
- - -
-

Secret 1-2-1

-
-
-
Status:
-
Valid
-
-
-
Expiration date:
-
3-Mar-2025
-
-
- -
Days before expiration:
-
30 days
- -
-
-
- -
-

Secret 1-2-2

-
-
-
Status:
-
ExpiringSoon
-
-
-
Expiration date:
-
4-Apr-2025
-
-
- -
Days before expiration:
-
40 days
- -
-
-
- - -
-
- -
-

The app 1-3

- -
- -
No secret
- -
-
- - -
-
- -
-

The tenant 2

- -
- - -
-

The app 2-1

- -
- - -
-

Secret 2-1-1

-
-
-
Status:
-
Valid
-
-
-
Expiration date:
-
5-May-2025
-
-
- -
Days before expiration:
-
50 days
- -
-
-
- - -
-
- -
-

The app 2-2

- -
- - -
-

Secret 2-1-1

-
-
-
Status:
-
Valid
-
-
-
Expiration date:
-
6-Jun-2025
-
-
- -
Days before expiration:
-
60 days
- -
-
-
- - -
-
- - -
-
- -
-

The tenant 3

- -
- -
No application
- -
-
- - - \ No newline at end of file diff --git a/tests/Core.Tests/Emailing/ScribanEmailGeneratorTest.cs b/tests/Core.Tests/Emailing/ScribanEmailGeneratorTest.cs deleted file mode 100644 index e6e9059..0000000 --- a/tests/Core.Tests/Emailing/ScribanEmailGeneratorTest.cs +++ /dev/null @@ -1,69 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) P.O.S Informatique. All rights reserved. -// -//----------------------------------------------------------------------- - -namespace PosInformatique.Azure.Identity.AppRegistrationSecretWatcher.Emailing.Tests -{ - public class ScribanEmailGeneratorTest - { - [Fact] - public async Task GenerateAsync() - { - var checkResult = new AppRegistrationSecretCheckResult( - [ - new AppRegistrationSecretCheckResultTenant( - "Id 1", - "The tenant 1", - [ - new AppRegistrationSecretCheckResultApplication( - "Id 1-1", - "The app 1-1", - [ - new AppRegistrationSecretCheckResultApplicationSecret("Secret 1-1-1", new DateTime(2025, 1, 1), -10) { Status = AppRegistrationSecretStatus.Expired }, - new AppRegistrationSecretCheckResultApplicationSecret("Secret 1-1-2", new DateTime(2025, 2, 2), 20) { Status = AppRegistrationSecretStatus.Valid }, - ]), - new AppRegistrationSecretCheckResultApplication( - "Id 1-2", - "The app 1-2", - [ - new AppRegistrationSecretCheckResultApplicationSecret("Secret 1-2-1", new DateTime(2025, 3, 3), 30) { Status = AppRegistrationSecretStatus.Valid }, - new AppRegistrationSecretCheckResultApplicationSecret("Secret 1-2-2", new DateTime(2025, 4, 4), 40) { Status = AppRegistrationSecretStatus.ExpiringSoon }, - ]), - new AppRegistrationSecretCheckResultApplication( - "Id 1-3", - "The app 1-3", - []) - ]), - new AppRegistrationSecretCheckResultTenant( - "Id 2", - "The tenant 2", - [ - new AppRegistrationSecretCheckResultApplication( - "Id 2-1", - "The app 2-1", - [ - new AppRegistrationSecretCheckResultApplicationSecret("Secret 2-1-1", new DateTime(2025, 5, 5), 50) { Status = AppRegistrationSecretStatus.Valid }, - ]), - new AppRegistrationSecretCheckResultApplication( - "Id 2-2", - "The app 2-2", - [ - new AppRegistrationSecretCheckResultApplicationSecret("Secret 2-1-1", new DateTime(2025, 6, 6), 60) { Status = AppRegistrationSecretStatus.Valid }, - ]) - ]), - new AppRegistrationSecretCheckResultTenant( - "Id 3", - "The tenant 3", - []), - ]); - - var generator = new ScribanEmailGenerator(); - - var content = await generator.GenerateAsync(checkResult, CancellationToken.None); - - await Verify(content); - } - } -} \ No newline at end of file From 407c706918e422cfd856420a642cad9a62305601 Mon Sep 17 00:00:00 2001 From: Gilles TOURREAU Date: Tue, 18 Nov 2025 15:51:24 +0100 Subject: [PATCH 03/15] Remove HTML template. --- src/Core/Emailing/EmailTemplate.html | 187 --------------------------- 1 file changed, 187 deletions(-) delete mode 100644 src/Core/Emailing/EmailTemplate.html diff --git a/src/Core/Emailing/EmailTemplate.html b/src/Core/Emailing/EmailTemplate.html deleted file mode 100644 index 663b063..0000000 --- a/src/Core/Emailing/EmailTemplate.html +++ /dev/null @@ -1,187 +0,0 @@ - - - - - - -

Entra ID app registrations secret expiration report

- - {{ - func status_to_css(status) - case status - when "Expired" - "expired" - when "ExpiringSoon" - "expiring-soon" - else - "valid" - end - end - }} - - {{ for tenant in Tenants }} -
-

{{ tenant.DisplayName }}

- -
- {{ if tenant.Applications.size == 0 }} -
No application
- {{ else }} - {{ for application in tenant.Applications }} -
-

{{ application.DisplayName }}

- -
- {{ if application.Secrets.size == 0 }} -
No secret
- {{ else }} - {{ for secret in application.Secrets }} -
-

{{ secret.DisplayName }}

-
-
-
Status:
-
{{ secret.Status }}
-
-
-
Expiration date:
-
{{ secret.EndDate | date.to_string '%e-%b-%Y' }}
-
-
- {{ if secret.DaysBeforeExpiration > 0 }} -
Days before expiration:
-
{{ secret.DaysBeforeExpiration }} days
- {{ else }} -
Expired since:
-
{{ -secret.DaysBeforeExpiration }} days
- {{ end }} -
-
-
- {{ end }} - {{ end }} -
-
- {{ end }} - {{ end }} -
-
- {{ end }} - - \ No newline at end of file From e9b2123ba073ccee3838fc9318884d59ec262636 Mon Sep 17 00:00:00 2001 From: Gilles TOURREAU Date: Tue, 18 Nov 2025 16:19:06 +0100 Subject: [PATCH 04/15] Add a summary indicators in the report template. --- .../Emailing/ReportEmailTemplateBody.razor | 50 +++++ ...portEmailTemplateBody_Render.verified.html | 207 ++++++++++++++++++ .../Core.Tests/Emailing/EmailTemplatesTest.cs | 86 ++++++++ .../Core.Tests/Emailing/RazorTemplateTools.cs | 70 ++++++ 4 files changed, 413 insertions(+) create mode 100644 tests/Core.Tests/Emailing/EmailTemplatesTest.ReportEmailTemplateBody_Render.verified.html create mode 100644 tests/Core.Tests/Emailing/EmailTemplatesTest.cs create mode 100644 tests/Core.Tests/Emailing/RazorTemplateTools.cs diff --git a/src/Core/Emailing/ReportEmailTemplateBody.razor b/src/Core/Emailing/ReportEmailTemplateBody.razor index 4779fc6..fec1196 100644 --- a/src/Core/Emailing/ReportEmailTemplateBody.razor +++ b/src/Core/Emailing/ReportEmailTemplateBody.razor @@ -11,6 +11,38 @@ color: #333; } + .summary { + background-color: #fff; + padding: 20px; + border-radius: 8px; + box-shadow: 0 1px 3px rgba(0,0,0,0.1); + margin-bottom: 20px; + } + + .summary-indicators { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 15px; + } + + .summary-indicator { + padding: 15px; + border-radius: 6px; + text-align: center; + } + + .summary-indicator-label { + font-size: 0.9em; + color: #555; + margin-top: 5px; + } + + .summary-indicator-value { + font-size: 2em; + font-weight: bold; + color: #333; + } + .tenant { margin-bottom: 15px; padding: 15px; @@ -121,6 +153,24 @@

Entra ID app registrations secret expiration report

+
+

Summary

+
+
+
@Model.Tenants.SelectMany(t => t.Applications).SelectMany(a => a.Secrets).Count(s => s.Status == AppRegistrationSecretStatus.Expired)
+
Expired
+
+
+
@Model.Tenants.SelectMany(t => t.Applications).SelectMany(a => a.Secrets).Count(s => s.Status == AppRegistrationSecretStatus.ExpiringSoon)
+
Expiring Soon
+
+
+
@Model.Tenants.SelectMany(t => t.Applications).SelectMany(a => a.Secrets).Count(s => s.Status == AppRegistrationSecretStatus.Valid)
+
Valid
+
+
+
+ @foreach (var tenant in this.Model.Tenants) {
diff --git a/tests/Core.Tests/Emailing/EmailTemplatesTest.ReportEmailTemplateBody_Render.verified.html b/tests/Core.Tests/Emailing/EmailTemplatesTest.ReportEmailTemplateBody_Render.verified.html new file mode 100644 index 0000000..d828d17 --- /dev/null +++ b/tests/Core.Tests/Emailing/EmailTemplatesTest.ReportEmailTemplateBody_Render.verified.html @@ -0,0 +1,207 @@ + + +

Entra ID app registrations secret expiration report

+ +

Summary

+
1
+
Expired
+
1
+
Expiring Soon
+
4
+
Valid

The tenant 1

+ +

The app 1-1

+ +

Secret 1-1-1

+
Status:
+
Expired
+
Expiration date:
+
1-janv.-2025
+
Expired since:
+
10 days

Secret 1-1-2

+
Status:
+
Valid
+
Expiration date:
+
2-févr.-2025
+
Days before expiration:
+
20 days

The app 1-2

+ +

Secret 1-2-1

+
Status:
+
Valid
+
Expiration date:
+
3-mars-2025
+
Days before expiration:
+
30 days

Secret 1-2-2

+
Status:
+
ExpiringSoon
+
Expiration date:
+
4-avr.-2025
+
Days before expiration:
+
40 days

The app 1-3

+ +
No secret

The tenant 2

+ +

The app 2-1

+ +

Secret 2-1-1

+
Status:
+
Valid
+
Expiration date:
+
5-mai-2025
+
Days before expiration:
+
50 days

The app 2-2

+ +

Secret 2-1-1

+
Status:
+
Valid
+
Expiration date:
+
6-juin-2025
+
Days before expiration:
+
60 days

The tenant 3

+ +
No application
\ No newline at end of file diff --git a/tests/Core.Tests/Emailing/EmailTemplatesTest.cs b/tests/Core.Tests/Emailing/EmailTemplatesTest.cs new file mode 100644 index 0000000..d8a5331 --- /dev/null +++ b/tests/Core.Tests/Emailing/EmailTemplatesTest.cs @@ -0,0 +1,86 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) P.O.S Informatique. All rights reserved. +// +//----------------------------------------------------------------------- + +namespace PosInformatique.Azure.Identity.AppRegistrationSecretWatcher.Emailing.Tests +{ + using Microsoft.Extensions.DependencyInjection; + + public class EmailTemplatesTest + { + [Fact] + public async Task ReportEmailTemplateBody_Render() + { + var checkResult = new AppRegistrationSecretCheckResult( + [ + new AppRegistrationSecretCheckResultTenant( + "Id 1", + "The tenant 1", + [ + new AppRegistrationSecretCheckResultApplication( + "Id 1-1", + "The app 1-1", + [ + new AppRegistrationSecretCheckResultApplicationSecret("Secret 1-1-1", new DateTime(2025, 1, 1), -10) { Status = AppRegistrationSecretStatus.Expired }, + new AppRegistrationSecretCheckResultApplicationSecret("Secret 1-1-2", new DateTime(2025, 2, 2), 20) { Status = AppRegistrationSecretStatus.Valid }, + ]), + new AppRegistrationSecretCheckResultApplication( + "Id 1-2", + "The app 1-2", + [ + new AppRegistrationSecretCheckResultApplicationSecret("Secret 1-2-1", new DateTime(2025, 3, 3), 30) { Status = AppRegistrationSecretStatus.Valid }, + new AppRegistrationSecretCheckResultApplicationSecret("Secret 1-2-2", new DateTime(2025, 4, 4), 40) { Status = AppRegistrationSecretStatus.ExpiringSoon }, + ]), + new AppRegistrationSecretCheckResultApplication( + "Id 1-3", + "The app 1-3", + []) + ]), + new AppRegistrationSecretCheckResultTenant( + "Id 2", + "The tenant 2", + [ + new AppRegistrationSecretCheckResultApplication( + "Id 2-1", + "The app 2-1", + [ + new AppRegistrationSecretCheckResultApplicationSecret("Secret 2-1-1", new DateTime(2025, 5, 5), 50) { Status = AppRegistrationSecretStatus.Valid }, + ]), + new AppRegistrationSecretCheckResultApplication( + "Id 2-2", + "The app 2-2", + [ + new AppRegistrationSecretCheckResultApplicationSecret("Secret 2-1-1", new DateTime(2025, 6, 6), 60) { Status = AppRegistrationSecretStatus.Valid }, + ]) + ]), + new AppRegistrationSecretCheckResultTenant( + "Id 3", + "The tenant 3", + []), + ], + new DateTime(2025, 1, 2, 3, 4, 5, 6, DateTimeKind.Utc)); + + var serviceCollection = new ServiceCollection(); + + var content = await RazorTemplateTools.RenderAsync(checkResult, serviceCollection); + + await Verify(content, "html"); + } + + [Fact] + public async Task ReportEmailTemplateSubject_Render() + { + var checkResult = new AppRegistrationSecretCheckResult( + [], + new DateTime(2025, 1, 2, 3, 4, 5, 6, DateTimeKind.Utc)); + + var serviceCollection = new ServiceCollection(); + + var content = await RazorTemplateTools.RenderAsync(checkResult, serviceCollection); + + content.Should().Be("Reminder: App Registration secrets expiring soon - [02/01/2025]"); + } + } +} \ No newline at end of file diff --git a/tests/Core.Tests/Emailing/RazorTemplateTools.cs b/tests/Core.Tests/Emailing/RazorTemplateTools.cs new file mode 100644 index 0000000..580d264 --- /dev/null +++ b/tests/Core.Tests/Emailing/RazorTemplateTools.cs @@ -0,0 +1,70 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) P.O.S Informatique. All rights reserved. +// +//----------------------------------------------------------------------- + +namespace PosInformatique +{ + using System.Diagnostics; + using Microsoft.AspNetCore.Components; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.JSInterop; + using PosInformatique.Foundations.Text.Templating; + using PosInformatique.Foundations.Text.Templating.Razor; + + public static class RazorTemplateTools + { + /*public static void DisplayHtmlPage(string content, string testName) + { + if (!Debugger.IsAttached) + { + return; + } + + var temporaryFolder = Path.Combine(UnitTestsFolders.TemporaryRoot, "Emailing Template Tests"); + var temporaryFile = Path.Combine(temporaryFolder, $"{testName}.html"); + + if (!Directory.Exists(temporaryFolder)) + { + Directory.CreateDirectory(temporaryFolder); + } + + File.WriteAllText(temporaryFile, content); + + Process.Start(new ProcessStartInfo + { + FileName = temporaryFile, + UseShellExecute = true, + }); + }*/ + + public static async Task RenderAsync(object model, IServiceCollection services) + where TComponent : ComponentBase + { +#pragma warning disable PosInfoMoq1000 // VerifyAll() method should be called when instantiate a Mock instances + var jsRuntime = new Mock(MockBehavior.Strict); +#pragma warning restore PosInfoMoq1000 // VerifyAll() method should be called when instantiate a Mock instances + + services.AddLogging(); + services.AddSingleton(jsRuntime.Object); + services.AddRazorTextTemplating(); + + var serviceProvider = services.BuildServiceProvider(); + + var context = new Mock(MockBehavior.Strict); + context.Setup(c => c.ServiceProvider) + .Returns(serviceProvider); + + var template = new RazorTextTemplate(typeof(TComponent)); + + var output = new StringWriter(); + + await template.RenderAsync(model, output, context.Object); + + context.VerifyAll(); + + return output.ToString(); + } + } +} From 3caf1ba4d08a06b7d2a157e7c009e215dea1d1aa Mon Sep 17 00:00:00 2001 From: Gilles TOURREAU Date: Tue, 18 Nov 2025 16:24:40 +0100 Subject: [PATCH 05/15] Change the subject to "Entra ID app registrations secret expiration report". --- src/Core/Emailing/ReportEmailTemplateSubject.razor | 2 +- tests/Core.Tests/Emailing/EmailTemplatesTest.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Core/Emailing/ReportEmailTemplateSubject.razor b/src/Core/Emailing/ReportEmailTemplateSubject.razor index adff9b1..e19f84e 100644 --- a/src/Core/Emailing/ReportEmailTemplateSubject.razor +++ b/src/Core/Emailing/ReportEmailTemplateSubject.razor @@ -1,3 +1,3 @@ @namespace PosInformatique.Azure.Identity.AppRegistrationSecretWatcher.Emailing @inherits RazorEmailTemplateSubject -Reminder: App Registration secrets expiring soon - [@this.Model.DateTime.ToString("d")] \ No newline at end of file +Entra ID app registrations secret expiration report - [@this.Model.DateTime.ToString("d")] \ No newline at end of file diff --git a/tests/Core.Tests/Emailing/EmailTemplatesTest.cs b/tests/Core.Tests/Emailing/EmailTemplatesTest.cs index d8a5331..aa7935f 100644 --- a/tests/Core.Tests/Emailing/EmailTemplatesTest.cs +++ b/tests/Core.Tests/Emailing/EmailTemplatesTest.cs @@ -80,7 +80,7 @@ public async Task ReportEmailTemplateSubject_Render() var content = await RazorTemplateTools.RenderAsync(checkResult, serviceCollection); - content.Should().Be("Reminder: App Registration secrets expiring soon - [02/01/2025]"); + content.Should().Be("Entra ID app registrations secret expiration report - [02/01/2025]"); } } } \ No newline at end of file From ecc5cc0246282a94a964e6605729de9770540c38 Mon Sep 17 00:00:00 2001 From: Gilles TOURREAU Date: Tue, 18 Nov 2025 17:45:07 +0100 Subject: [PATCH 06/15] Fix Verify() --- .editorconfig | 2 +- .gitattributes | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.editorconfig b/.editorconfig index bf68510..f64f1d7 100644 --- a/.editorconfig +++ b/.editorconfig @@ -101,7 +101,7 @@ dotnet_diagnostic.SA1600.severity = none dotnet_diagnostic.SA1602.severity = none # Verify -[*.{received,verified}.{txt}] +[*.{received,verified}.{html}] charset = utf-8-bom end_of_line = lf indent_size = unset diff --git a/.gitattributes b/.gitattributes index 02cd1f1..e73dc5f 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1,4 @@ -*.verified.txt text eol=lf working-tree-encoding=UTF-8 +*.verified.html text eol=lf working-tree-encoding=UTF-8 *.verified.xml text eol=lf working-tree-encoding=UTF-8 *.verified.json text eol=lf working-tree-encoding=UTF-8 *.verified.bin binary \ No newline at end of file From 2550ba2c9077130cb5b065d32b71ae934f4c0c3c Mon Sep 17 00:00:00 2001 From: Gilles TOURREAU Date: Tue, 18 Nov 2025 17:59:48 +0100 Subject: [PATCH 07/15] Raise importance message with high when secret has been expired. --- Directory.Packages.props | 2 +- src/Core/AppRegistrationSecretCheckResult.cs | 2 + ...egistrationSecretCheckResultApplication.cs | 2 + .../AppRegistrationSecretCheckResultTenant.cs | 2 + src/Core/AppRegistrationSecretManager.cs | 5 + ...trationSecretCheckResultApplicationTest.cs | 20 ++- ...RegistrationSecretCheckResultTenantTest.cs | 46 ++++- .../AppRegistrationSecretCheckResultTest.cs | 95 +++++++++- .../AppRegistrationSecretManagerTest.cs | 163 ++++++++++++++++++ 9 files changed, 331 insertions(+), 6 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 8664866..8ec0f07 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -2,7 +2,7 @@ true - 1.0.0-alpha.15 + 1.0.0-rc.2 diff --git a/src/Core/AppRegistrationSecretCheckResult.cs b/src/Core/AppRegistrationSecretCheckResult.cs index 695c67c..8631da1 100644 --- a/src/Core/AppRegistrationSecretCheckResult.cs +++ b/src/Core/AppRegistrationSecretCheckResult.cs @@ -19,5 +19,7 @@ public AppRegistrationSecretCheckResult(IReadOnlyList Tenants { get; } public DateTime DateTime { get; } + + public bool HasExpiredSecrets => this.Tenants.Any(t => t.HasExpiredSecrets); } } \ No newline at end of file diff --git a/src/Core/AppRegistrationSecretCheckResultApplication.cs b/src/Core/AppRegistrationSecretCheckResultApplication.cs index 4dbae03..f1e5ae7 100644 --- a/src/Core/AppRegistrationSecretCheckResultApplication.cs +++ b/src/Core/AppRegistrationSecretCheckResultApplication.cs @@ -20,5 +20,7 @@ public AppRegistrationSecretCheckResultApplication(string id, string displayName public string DisplayName { get; } public ReadOnlyCollection Secrets { get; } + + public bool HasExpiredSecrets => this.Secrets.Any(s => s.Status == AppRegistrationSecretStatus.Expired); } } \ No newline at end of file diff --git a/src/Core/AppRegistrationSecretCheckResultTenant.cs b/src/Core/AppRegistrationSecretCheckResultTenant.cs index 35bc697..459fbe0 100644 --- a/src/Core/AppRegistrationSecretCheckResultTenant.cs +++ b/src/Core/AppRegistrationSecretCheckResultTenant.cs @@ -20,5 +20,7 @@ public AppRegistrationSecretCheckResultTenant(string id, string displayName, IRe public string DisplayName { get; } public ReadOnlyCollection Applications { get; } + + public bool HasExpiredSecrets => this.Applications.Any(a => a.HasExpiredSecrets); } } \ No newline at end of file diff --git a/src/Core/AppRegistrationSecretManager.cs b/src/Core/AppRegistrationSecretManager.cs index a588a99..476faf6 100644 --- a/src/Core/AppRegistrationSecretManager.cs +++ b/src/Core/AppRegistrationSecretManager.cs @@ -117,6 +117,11 @@ private async Task SendEmailAsync(AppRegistrationSecretCheckResult result, Cance email.Recipients.Add(recipient, string.Empty, result); } + if (result.HasExpiredSecrets) + { + email.Importance = EmailImportance.High; + } + await this.emailManager.SendAsync(email, cancellationToken); } } diff --git a/tests/Core.Tests/AppRegistrationSecretCheckResultApplicationTest.cs b/tests/Core.Tests/AppRegistrationSecretCheckResultApplicationTest.cs index ded27c9..c399aa7 100644 --- a/tests/Core.Tests/AppRegistrationSecretCheckResultApplicationTest.cs +++ b/tests/Core.Tests/AppRegistrationSecretCheckResultApplicationTest.cs @@ -14,12 +14,30 @@ public void Constructor() var secrets = new[] { new AppRegistrationSecretCheckResultApplicationSecret(default, default, default), - new AppRegistrationSecretCheckResultApplicationSecret(default, default, default), + new AppRegistrationSecretCheckResultApplicationSecret(default, default, default) { Status = AppRegistrationSecretStatus.ExpiringSoon }, + }; + + var application = new AppRegistrationSecretCheckResultApplication("The ID", "The display name", secrets); + + application.DisplayName.Should().Be("The display name"); + application.HasExpiredSecrets.Should().BeFalse(); + application.Id.Should().Be("The ID"); + application.Secrets.Should().Equal(secrets); + } + + [Fact] + public void HasExpiredSecrets() + { + var secrets = new[] + { + new AppRegistrationSecretCheckResultApplicationSecret(default, default, default) { Status = AppRegistrationSecretStatus.Expired }, + new AppRegistrationSecretCheckResultApplicationSecret(default, default, default) { Status = AppRegistrationSecretStatus.ExpiringSoon }, }; var application = new AppRegistrationSecretCheckResultApplication("The ID", "The display name", secrets); application.DisplayName.Should().Be("The display name"); + application.HasExpiredSecrets.Should().BeTrue(); application.Id.Should().Be("The ID"); application.Secrets.Should().Equal(secrets); } diff --git a/tests/Core.Tests/AppRegistrationSecretCheckResultTenantTest.cs b/tests/Core.Tests/AppRegistrationSecretCheckResultTenantTest.cs index db5a211..3652f31 100644 --- a/tests/Core.Tests/AppRegistrationSecretCheckResultTenantTest.cs +++ b/tests/Core.Tests/AppRegistrationSecretCheckResultTenantTest.cs @@ -13,14 +13,56 @@ public void Constructor() { var applications = new[] { - new AppRegistrationSecretCheckResultApplication(default, default, []), - new AppRegistrationSecretCheckResultApplication(default, default, []), + new AppRegistrationSecretCheckResultApplication( + default, + default, + [ + new AppRegistrationSecretCheckResultApplicationSecret(default, default, default) { Status = AppRegistrationSecretStatus.Valid }, + new AppRegistrationSecretCheckResultApplicationSecret(default, default, default) { Status = AppRegistrationSecretStatus.ExpiringSoon }, + ]), + new AppRegistrationSecretCheckResultApplication( + default, + default, + [ + new AppRegistrationSecretCheckResultApplicationSecret(default, default, default) { Status = AppRegistrationSecretStatus.ExpiringSoon }, + new AppRegistrationSecretCheckResultApplicationSecret(default, default, default) { Status = AppRegistrationSecretStatus.Valid }, + ]), }; var tenant = new AppRegistrationSecretCheckResultTenant("The ID", "The display name", applications); tenant.Applications.Should().Equal(applications); tenant.DisplayName.Should().Be("The display name"); + tenant.HasExpiredSecrets.Should().BeFalse(); + tenant.Id.Should().Be("The ID"); + } + + [Fact] + public void HasExpiredSecrets() + { + var applications = new[] + { + new AppRegistrationSecretCheckResultApplication( + default, + default, + [ + new AppRegistrationSecretCheckResultApplicationSecret(default, default, default) { Status = AppRegistrationSecretStatus.Expired }, + new AppRegistrationSecretCheckResultApplicationSecret(default, default, default) { Status = AppRegistrationSecretStatus.ExpiringSoon }, + ]), + new AppRegistrationSecretCheckResultApplication( + default, + default, + [ + new AppRegistrationSecretCheckResultApplicationSecret(default, default, default) { Status = AppRegistrationSecretStatus.ExpiringSoon }, + new AppRegistrationSecretCheckResultApplicationSecret(default, default, default) { Status = AppRegistrationSecretStatus.Valid }, + ]), + }; + + var tenant = new AppRegistrationSecretCheckResultTenant("The ID", "The display name", applications); + + tenant.Applications.Should().Equal(applications); + tenant.DisplayName.Should().Be("The display name"); + tenant.HasExpiredSecrets.Should().BeTrue(); tenant.Id.Should().Be("The ID"); } } diff --git a/tests/Core.Tests/AppRegistrationSecretCheckResultTest.cs b/tests/Core.Tests/AppRegistrationSecretCheckResultTest.cs index cfe68bd..b23fd27 100644 --- a/tests/Core.Tests/AppRegistrationSecretCheckResultTest.cs +++ b/tests/Core.Tests/AppRegistrationSecretCheckResultTest.cs @@ -13,8 +13,44 @@ public void Constructor() { var tenants = new[] { - new AppRegistrationSecretCheckResultTenant(default, default, []), - new AppRegistrationSecretCheckResultTenant(default, default, []), + new AppRegistrationSecretCheckResultTenant( + default, + default, + [ + new AppRegistrationSecretCheckResultApplication( + default, + default, + [ + new AppRegistrationSecretCheckResultApplicationSecret(default, default, default) { Status = AppRegistrationSecretStatus.Valid }, + new AppRegistrationSecretCheckResultApplicationSecret(default, default, default) { Status = AppRegistrationSecretStatus.ExpiringSoon }, + ]), + new AppRegistrationSecretCheckResultApplication( + default, + default, + [ + new AppRegistrationSecretCheckResultApplicationSecret(default, default, default) { Status = AppRegistrationSecretStatus.ExpiringSoon }, + new AppRegistrationSecretCheckResultApplicationSecret(default, default, default) { Status = AppRegistrationSecretStatus.Valid }, + ]), + ]), + new AppRegistrationSecretCheckResultTenant( + default, + default, + [ + new AppRegistrationSecretCheckResultApplication( + default, + default, + [ + new AppRegistrationSecretCheckResultApplicationSecret(default, default, default) { Status = AppRegistrationSecretStatus.Valid }, + new AppRegistrationSecretCheckResultApplicationSecret(default, default, default) { Status = AppRegistrationSecretStatus.ExpiringSoon }, + ]), + new AppRegistrationSecretCheckResultApplication( + default, + default, + [ + new AppRegistrationSecretCheckResultApplicationSecret(default, default, default) { Status = AppRegistrationSecretStatus.ExpiringSoon }, + new AppRegistrationSecretCheckResultApplicationSecret(default, default, default) { Status = AppRegistrationSecretStatus.Valid }, + ]), + ]), }; var dateTime = new DateTime(2024, 1, 2, 3, 4, 5, 6, 7, DateTimeKind.Utc); @@ -22,6 +58,61 @@ public void Constructor() var tenant = new AppRegistrationSecretCheckResult(tenants, dateTime); tenant.DateTime.Should().Be(dateTime); + tenant.HasExpiredSecrets.Should().BeFalse(); + tenant.Tenants.Should().Equal(tenants); + } + + [Fact] + public void HasExpiredSecrets() + { + var tenants = new[] + { + new AppRegistrationSecretCheckResultTenant( + default, + default, + [ + new AppRegistrationSecretCheckResultApplication( + default, + default, + [ + new AppRegistrationSecretCheckResultApplicationSecret(default, default, default) { Status = AppRegistrationSecretStatus.Valid }, + new AppRegistrationSecretCheckResultApplicationSecret(default, default, default) { Status = AppRegistrationSecretStatus.ExpiringSoon }, + ]), + new AppRegistrationSecretCheckResultApplication( + default, + default, + [ + new AppRegistrationSecretCheckResultApplicationSecret(default, default, default) { Status = AppRegistrationSecretStatus.ExpiringSoon }, + new AppRegistrationSecretCheckResultApplicationSecret(default, default, default) { Status = AppRegistrationSecretStatus.Valid }, + ]), + ]), + new AppRegistrationSecretCheckResultTenant( + default, + default, + [ + new AppRegistrationSecretCheckResultApplication( + default, + default, + [ + new AppRegistrationSecretCheckResultApplicationSecret(default, default, default) { Status = AppRegistrationSecretStatus.Expired }, + new AppRegistrationSecretCheckResultApplicationSecret(default, default, default) { Status = AppRegistrationSecretStatus.ExpiringSoon }, + ]), + new AppRegistrationSecretCheckResultApplication( + default, + default, + [ + new AppRegistrationSecretCheckResultApplicationSecret(default, default, default) { Status = AppRegistrationSecretStatus.ExpiringSoon }, + new AppRegistrationSecretCheckResultApplicationSecret(default, default, default) { Status = AppRegistrationSecretStatus.Valid }, + ]), + ]), + }; + + var dateTime = new DateTime(2024, 1, 2, 3, 4, 5, 6, 7, DateTimeKind.Utc); + + var tenant = new AppRegistrationSecretCheckResult(tenants, dateTime); + + tenant.DateTime.Should().Be(dateTime); + tenant.HasExpiredSecrets.Should().BeTrue(); tenant.Tenants.Should().Equal(tenants); } } diff --git a/tests/Core.Tests/AppRegistrationSecretManagerTest.cs b/tests/Core.Tests/AppRegistrationSecretManagerTest.cs index 8058762..9375e27 100644 --- a/tests/Core.Tests/AppRegistrationSecretManagerTest.cs +++ b/tests/Core.Tests/AppRegistrationSecretManagerTest.cs @@ -23,6 +23,167 @@ public async Task CheckAsync() AppRegistrationSecretCheckResult expectedResult = null; + var entraIdClient = new Mock(MockBehavior.Strict); + entraIdClient.Setup(c => c.GetApplicationsAsync("Tenant 1", cancellationToken)) + .ReturnsAsync( + new EntraIdTenant( + "The tenant ID 1", + "The tenant display name 1", + [ + new EntraIdApplication( + "1-1", + "App 1-1", + [ + new EntraIdApplicationPasswordCredential("Secret 1-1-1", now.AddDays(60)), + new EntraIdApplicationPasswordCredential("Secret 1-1-2", now.AddDays(10)), + ]), + new EntraIdApplication( + "1-2", + "App 1-2", + [ + new EntraIdApplicationPasswordCredential("Secret 1-2-1", now.AddDays(30)), + new EntraIdApplicationPasswordCredential("Secret 1-2-2", now.AddDays(120)), + ]) + ])); + entraIdClient.Setup(c => c.GetApplicationsAsync("Tenant 2", cancellationToken)) + .ReturnsAsync( + new EntraIdTenant( + "The tenant ID 2", + "The tenant display name 2", + [ + new EntraIdApplication( + "2-1", + "App 2-1", + [ + new EntraIdApplicationPasswordCredential("Secret 2-1-1", now.AddDays(100)), + ]), + new EntraIdApplication( + "2-2", + "App 2-2", + [ + new EntraIdApplicationPasswordCredential("Secret 2-2-1", now.AddDays(300)), + ]) + ])); + + var email = new Email(EmailTemplates.Report); + + var emailManager = new Mock(MockBehavior.Strict); + emailManager.Setup(em => em.Create(EmailTemplates.ReportIdentifier)) + .Returns(email); + + emailManager.Setup(g => g.SendAsync(It.IsAny>(), cancellationToken)) + .Callback((Email e, CancellationToken _) => + { + e.Should().BeSameAs(email); + + expectedResult = email.Recipients[0].Model; + + e.Importance.Should().Be(EmailImportance.Normal); + + e.Recipients.Should().HaveCount(2); + + e.Recipients[0].Address.Should().Be(EmailAddress.Parse("email1@domain.com")); + e.Recipients[0].DisplayName.Should().BeEmpty(); + + e.Recipients[1].Address.Should().Be(EmailAddress.Parse("email2@domain.com")); + e.Recipients[1].DisplayName.Should().BeEmpty(); + e.Recipients[1].Model.Should().BeSameAs(expectedResult); + }) + .Returns(Task.CompletedTask); + + var timeProvider = new Mock(MockBehavior.Strict); + timeProvider.Setup(tp => tp.GetUtcNow()) + .Returns(now); + timeProvider.Setup(tp => tp.LocalTimeZone) + .Returns(TimeZoneInfo.FindSystemTimeZoneById("Asia/Manila")); + + var options = Options.Create(new AppRegistrationSecretManagerOptions() + { + EmailRecipients = + { + EmailAddress.Parse("email1@domain.com"), + EmailAddress.Parse("email2@domain.com"), + }, + }); + + var parameters = new AppRegistrationSecretCheckParameters() + { + ExpirationThreshold = TimeSpan.FromDays(5), + TenantIds = + { + "Tenant 1", + "Tenant 2", + }, + }; + + var manager = new AppRegistrationSecretManager(entraIdClient.Object, emailManager.Object, timeProvider.Object, options); + + var result = await manager.CheckAsync(parameters, cancellationToken); + + result.Should().BeSameAs(expectedResult); + + result.DateTime.Should().Be(now).And.BeIn(DateTimeKind.Utc); + + result.Tenants.Should().HaveCount(2); + + result.Tenants[0].DisplayName.Should().Be("The tenant display name 1"); + result.Tenants[0].Id.Should().Be("The tenant ID 1"); + result.Tenants[0].Applications.Should().HaveCount(2); + result.Tenants[0].Applications[0].DisplayName.Should().Be("App 1-1"); + result.Tenants[0].Applications[0].Id.Should().Be("1-1"); + result.Tenants[0].Applications[0].Secrets.Should().HaveCount(2); + result.Tenants[0].Applications[0].Secrets[0].DaysBeforeExpiration.Should().Be(60); + result.Tenants[0].Applications[0].Secrets[0].DisplayName.Should().Be("Secret 1-1-1"); + result.Tenants[0].Applications[0].Secrets[0].EndDate.Should().Be(now.AddDays(60).AddHours(8)).And.BeIn(DateTimeKind.Local); + result.Tenants[0].Applications[0].Secrets[0].Status.Should().Be(AppRegistrationSecretStatus.Valid); + result.Tenants[0].Applications[0].Secrets[1].DaysBeforeExpiration.Should().Be(10); + result.Tenants[0].Applications[0].Secrets[1].DisplayName.Should().Be("Secret 1-1-2"); + result.Tenants[0].Applications[0].Secrets[1].EndDate.Should().Be(now.AddDays(10).AddHours(8)).And.BeIn(DateTimeKind.Local); + result.Tenants[0].Applications[0].Secrets[1].Status.Should().Be(AppRegistrationSecretStatus.Valid); + result.Tenants[0].Applications[1].DisplayName.Should().Be("App 1-2"); + result.Tenants[0].Applications[1].Id.Should().Be("1-2"); + result.Tenants[0].Applications[1].Secrets.Should().HaveCount(2); + result.Tenants[0].Applications[1].Secrets[0].DaysBeforeExpiration.Should().Be(30); + result.Tenants[0].Applications[1].Secrets[0].DisplayName.Should().Be("Secret 1-2-1"); + result.Tenants[0].Applications[1].Secrets[0].EndDate.Should().Be(now.AddDays(30).AddHours(8)).And.BeIn(DateTimeKind.Local); + result.Tenants[0].Applications[1].Secrets[0].Status.Should().Be(AppRegistrationSecretStatus.Valid); + result.Tenants[0].Applications[1].Secrets[1].DaysBeforeExpiration.Should().Be(120); + result.Tenants[0].Applications[1].Secrets[1].DisplayName.Should().Be("Secret 1-2-2"); + result.Tenants[0].Applications[1].Secrets[1].EndDate.Should().Be(now.AddDays(120).AddHours(8)).And.BeIn(DateTimeKind.Local); + result.Tenants[0].Applications[1].Secrets[1].Status.Should().Be(AppRegistrationSecretStatus.Valid); + + result.Tenants[1].DisplayName.Should().Be("The tenant display name 2"); + result.Tenants[1].Id.Should().Be("The tenant ID 2"); + result.Tenants[1].Applications.Should().HaveCount(2); + result.Tenants[1].Applications[0].DisplayName.Should().Be("App 2-1"); + result.Tenants[1].Applications[0].Id.Should().Be("2-1"); + result.Tenants[1].Applications[0].Secrets.Should().HaveCount(1); + result.Tenants[1].Applications[0].Secrets[0].DaysBeforeExpiration.Should().Be(100); + result.Tenants[1].Applications[0].Secrets[0].DisplayName.Should().Be("Secret 2-1-1"); + result.Tenants[1].Applications[0].Secrets[0].EndDate.Should().Be(now.AddDays(100).AddHours(8)).And.BeIn(DateTimeKind.Local); + result.Tenants[1].Applications[0].Secrets[0].Status.Should().Be(AppRegistrationSecretStatus.Valid); + result.Tenants[1].Applications[1].DisplayName.Should().Be("App 2-2"); + result.Tenants[1].Applications[1].Id.Should().Be("2-2"); + result.Tenants[1].Applications[1].Secrets.Should().HaveCount(1); + result.Tenants[1].Applications[1].Secrets[0].DaysBeforeExpiration.Should().Be(300); + result.Tenants[1].Applications[1].Secrets[0].DisplayName.Should().Be("Secret 2-2-1"); + result.Tenants[1].Applications[1].Secrets[0].EndDate.Should().Be(now.AddDays(300).AddHours(8)).And.BeIn(DateTimeKind.Local); + result.Tenants[1].Applications[1].Secrets[0].Status.Should().Be(AppRegistrationSecretStatus.Valid); + + emailManager.VerifyAll(); + entraIdClient.VerifyAll(); + timeProvider.VerifyAll(); + } + + [Fact] + public async Task CheckAsync_WithImportance() + { + var now = new DateTime(2025, 6, 15, 1, 2, 3, 4, 5, DateTimeKind.Utc); + + var cancellationToken = new CancellationTokenSource().Token; + + AppRegistrationSecretCheckResult expectedResult = null; + var entraIdClient = new Mock(MockBehavior.Strict); entraIdClient.Setup(c => c.GetApplicationsAsync("Tenant 1", cancellationToken)) .ReturnsAsync( @@ -78,6 +239,8 @@ public async Task CheckAsync() expectedResult = email.Recipients[0].Model; + e.Importance.Should().Be(EmailImportance.High); + e.Recipients.Should().HaveCount(2); e.Recipients[0].Address.Should().Be(EmailAddress.Parse("email1@domain.com")); From eed6eba6aa76c6f7c0ccd2d4a25fdce335c64f55 Mon Sep 17 00:00:00 2001 From: Gilles TOURREAU Date: Wed, 19 Nov 2025 05:32:30 +0100 Subject: [PATCH 08/15] Upgrade the version of PosInformatique.Foundations to rc.3 --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 8ec0f07..24c3cf1 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -2,7 +2,7 @@ true - 1.0.0-rc.2 + 1.0.0-rc.3 From 781d556f7a61b227d0fc3c5f917de2d903dbe611 Mon Sep 17 00:00:00 2001 From: Gilles TOURREAU Date: Wed, 19 Nov 2025 06:53:56 +0100 Subject: [PATCH 09/15] Fix the template to use for the summary. --- .../Emailing/ReportEmailTemplateBody.razor | 52 +++++++++++++------ ...portEmailTemplateBody_Render.verified.html | 24 +++++---- 2 files changed, 50 insertions(+), 26 deletions(-) diff --git a/src/Core/Emailing/ReportEmailTemplateBody.razor b/src/Core/Emailing/ReportEmailTemplateBody.razor index fec1196..6ed84f5 100644 --- a/src/Core/Emailing/ReportEmailTemplateBody.razor +++ b/src/Core/Emailing/ReportEmailTemplateBody.razor @@ -13,16 +13,20 @@ .summary { background-color: #fff; - padding: 20px; + padding: 15px; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); margin-bottom: 20px; } + .summary h2 { + margin-top: 0px; + margin-bottom: 4px; + } + .summary-indicators { - display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 15px; + background-color: #ffffff; + border-radius: 8px; } .summary-indicator { @@ -156,18 +160,34 @@

Summary

-
-
@Model.Tenants.SelectMany(t => t.Applications).SelectMany(a => a.Secrets).Count(s => s.Status == AppRegistrationSecretStatus.Expired)
-
Expired
-
-
-
@Model.Tenants.SelectMany(t => t.Applications).SelectMany(a => a.Secrets).Count(s => s.Status == AppRegistrationSecretStatus.ExpiringSoon)
-
Expiring Soon
-
-
-
@Model.Tenants.SelectMany(t => t.Applications).SelectMany(a => a.Secrets).Count(s => s.Status == AppRegistrationSecretStatus.Valid)
-
Valid
-
+
+ + + + + +
+
+
+ @Model.Tenants.SelectMany(t => t.Applications).SelectMany(a => a.Secrets).Count(s => s.Status == AppRegistrationSecretStatus.Expired) +
+
Expired
+
+
+
+
+ @Model.Tenants.SelectMany(t => t.Applications).SelectMany(a => a.Secrets).Count(s => s.Status == AppRegistrationSecretStatus.ExpiringSoon) +
+
Expiring Soon
+
+
+
+
+ @Model.Tenants.SelectMany(t => t.Applications).SelectMany(a => a.Secrets).Count(s => s.Status == AppRegistrationSecretStatus.Valid) +
+
Valid
+
+
diff --git a/tests/Core.Tests/Emailing/EmailTemplatesTest.ReportEmailTemplateBody_Render.verified.html b/tests/Core.Tests/Emailing/EmailTemplatesTest.ReportEmailTemplateBody_Render.verified.html index d828d17..4810117 100644 --- a/tests/Core.Tests/Emailing/EmailTemplatesTest.ReportEmailTemplateBody_Render.verified.html +++ b/tests/Core.Tests/Emailing/EmailTemplatesTest.ReportEmailTemplateBody_Render.verified.html @@ -8,16 +8,20 @@ .summary { background-color: #fff; - padding: 20px; + padding: 15px; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); margin-bottom: 20px; } + .summary h2 { + margin-top: 0px; + margin-bottom: 4px; + } + .summary-indicators { - display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 15px; + background-color: #ffffff; + border-radius: 8px; } .summary-indicator { @@ -147,12 +151,12 @@

Entra ID app registrations secret expiration report

Summary

-
1
-
Expired
-
1
-
Expiring Soon
-
4
-
Valid

The tenant 1

+
+ +
1
+
Expired
1
+
Expiring Soon
4
+
Valid

The tenant 1

The app 1-1

From 52e5e2f1f7ff6562556daa54773b44666520ebec Mon Sep 17 00:00:00 2001 From: Gilles TOURREAU Date: Wed, 19 Nov 2025 06:55:03 +0100 Subject: [PATCH 10/15] Upgades the version. --- .github/workflows/github-actions-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/github-actions-release.yml b/.github/workflows/github-actions-release.yml index d1a7a68..43cbfa6 100644 --- a/.github/workflows/github-actions-release.yml +++ b/.github/workflows/github-actions-release.yml @@ -7,7 +7,7 @@ on: type: string description: The version of the application required: true - default: 1.0.0 + default: 1.1.0 VersionSuffix: type: string description: The version suffix of the application (for example rc.1) From 82753ba6ece140c974df36b28f9f1030490cdeef Mon Sep 17 00:00:00 2001 From: Gilles TOURREAU Date: Wed, 19 Nov 2025 08:01:11 +0100 Subject: [PATCH 11/15] Add the support of the culture used to format the dates and times for the report. --- README.md | 2 ++ .../Emailing/ReportEmailTemplateBody.razor | 3 ++- .../Emailing/ReportEmailTemplateSubject.razor | 3 ++- src/Core/FixedCulture.cs | 22 +++++++++++++++++++ src/Core/ICulture.cs | 15 +++++++++++++ ...AppRegistrationSecretWatcherApplication.cs | 16 ++++++++++++++ .../Core.Tests/Emailing/EmailTemplatesTest.cs | 15 +++++++++++++ tests/Core.Tests/FixedCultureTest.cs | 21 ++++++++++++++++++ 8 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 src/Core/FixedCulture.cs create mode 100644 src/Core/ICulture.cs create mode 100644 tests/Core.Tests/FixedCultureTest.cs diff --git a/README.md b/README.md index 9affa25..28e8b73 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,8 @@ To send the e-mail using Graph API: Client ID of the App Registration used to query secrets across tenants. If omitted, the Function managed identity is used (single-tenant only). - `APP_SECRET_WATCHER_CLIENT_SECRET`: Client secret of the App Registration. Not required if using managed identity or certificate auth. +- `APP_SECRET_WATCHER_CULTURE`: + Culture name used to format the dates and times for the reports. (`en-US` will be used if not specified). - `APP_SECRET_WATCHER_EXPIRATION_THRESHOLD`: Time span threshold to raise warnings before secret expiration. Example: `30.00:00:00` for 30 days. - `APP_SECRET_WATCHER_FREQUENCY`: diff --git a/src/Core/Emailing/ReportEmailTemplateBody.razor b/src/Core/Emailing/ReportEmailTemplateBody.razor index 6ed84f5..8b0d6db 100644 --- a/src/Core/Emailing/ReportEmailTemplateBody.razor +++ b/src/Core/Emailing/ReportEmailTemplateBody.razor @@ -1,5 +1,6 @@ @namespace PosInformatique.Azure.Identity.AppRegistrationSecretWatcher.Emailing @inherits RazorEmailTemplateBody +@inject ICulture Culture @@ -230,7 +231,7 @@
Expiration date:
-
@secret.EndDate.ToString("d-MMM-yyyy")
+
@secret.EndDate.ToString("d-MMM-yyyy", this.Culture.Current)
@if (secret.DaysBeforeExpiration > 0) diff --git a/src/Core/Emailing/ReportEmailTemplateSubject.razor b/src/Core/Emailing/ReportEmailTemplateSubject.razor index e19f84e..f990314 100644 --- a/src/Core/Emailing/ReportEmailTemplateSubject.razor +++ b/src/Core/Emailing/ReportEmailTemplateSubject.razor @@ -1,3 +1,4 @@ @namespace PosInformatique.Azure.Identity.AppRegistrationSecretWatcher.Emailing @inherits RazorEmailTemplateSubject -Entra ID app registrations secret expiration report - [@this.Model.DateTime.ToString("d")] \ No newline at end of file +@inject ICulture Culture +Entra ID app registrations secret expiration report - [@this.Model.DateTime.ToString("d", this.Culture.Current)] \ No newline at end of file diff --git a/src/Core/FixedCulture.cs b/src/Core/FixedCulture.cs new file mode 100644 index 0000000..2ffdb76 --- /dev/null +++ b/src/Core/FixedCulture.cs @@ -0,0 +1,22 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) P.O.S Informatique. All rights reserved. +// +//----------------------------------------------------------------------- + +namespace PosInformatique.Azure.Identity.AppRegistrationSecretWatcher +{ + using System.Globalization; + + public class FixedCulture : ICulture + { + private readonly CultureInfo current; + + public FixedCulture(string name) + { + this.current = CultureInfo.GetCultureInfo(name); + } + + public CultureInfo Current => this.current; + } +} \ No newline at end of file diff --git a/src/Core/ICulture.cs b/src/Core/ICulture.cs new file mode 100644 index 0000000..c2352a5 --- /dev/null +++ b/src/Core/ICulture.cs @@ -0,0 +1,15 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) P.O.S Informatique. All rights reserved. +// +//----------------------------------------------------------------------- + +namespace PosInformatique.Azure.Identity.AppRegistrationSecretWatcher +{ + using System.Globalization; + + public interface ICulture + { + CultureInfo Current { get; } + } +} \ No newline at end of file diff --git a/src/Functions/AppRegistrationSecretWatcherApplication.cs b/src/Functions/AppRegistrationSecretWatcherApplication.cs index 7ee644d..ea3406b 100644 --- a/src/Functions/AppRegistrationSecretWatcherApplication.cs +++ b/src/Functions/AppRegistrationSecretWatcherApplication.cs @@ -82,10 +82,26 @@ public static async Task Main(string[] args) expirationThreshold = expirationThresholdParsed; } + // Check the APP_SECRET_WATCHER_CULTURE + var cultureName = "en-US"; + + if (!string.IsNullOrWhiteSpace(builder.Configuration["APP_SECRET_WATCHER_CULTURE"])) + { + var cultureNameSettings = builder.Configuration["APP_SECRET_WATCHER_CULTURE"]!; + + if (!CultureInfo.GetCultures(CultureTypes.AllCultures).Any(c => c.Name.Equals(cultureNameSettings, StringComparison.Ordinal))) + { + throw new InvalidOperationException("The culture specified for the app registrations watcher is invalid (Invalid setting: APP_SECRET_WATCHER_CULTURE)."); + } + + cultureName = cultureNameSettings; + } + builder.ConfigureFunctionsWebApplication(); // Infrastructure builder.Services.AddSingleton(TimeProvider.System); + builder.Services.AddSingleton(new FixedCulture(cultureName)); // Add Application Insights builder.Services diff --git a/tests/Core.Tests/Emailing/EmailTemplatesTest.cs b/tests/Core.Tests/Emailing/EmailTemplatesTest.cs index aa7935f..6a25f88 100644 --- a/tests/Core.Tests/Emailing/EmailTemplatesTest.cs +++ b/tests/Core.Tests/Emailing/EmailTemplatesTest.cs @@ -6,6 +6,7 @@ namespace PosInformatique.Azure.Identity.AppRegistrationSecretWatcher.Emailing.Tests { + using System.Globalization; using Microsoft.Extensions.DependencyInjection; public class EmailTemplatesTest @@ -62,11 +63,18 @@ public async Task ReportEmailTemplateBody_Render() ], new DateTime(2025, 1, 2, 3, 4, 5, 6, DateTimeKind.Utc)); + var culture = new Mock(MockBehavior.Strict); + culture.Setup(c => c.Current) + .Returns(new CultureInfo("fr")); + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton(culture.Object); var content = await RazorTemplateTools.RenderAsync(checkResult, serviceCollection); await Verify(content, "html"); + + culture.VerifyAll(); } [Fact] @@ -76,11 +84,18 @@ public async Task ReportEmailTemplateSubject_Render() [], new DateTime(2025, 1, 2, 3, 4, 5, 6, DateTimeKind.Utc)); + var culture = new Mock(MockBehavior.Strict); + culture.Setup(c => c.Current) + .Returns(new CultureInfo("fr")); + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton(culture.Object); var content = await RazorTemplateTools.RenderAsync(checkResult, serviceCollection); content.Should().Be("Entra ID app registrations secret expiration report - [02/01/2025]"); + + culture.VerifyAll(); } } } \ No newline at end of file diff --git a/tests/Core.Tests/FixedCultureTest.cs b/tests/Core.Tests/FixedCultureTest.cs new file mode 100644 index 0000000..fcb57df --- /dev/null +++ b/tests/Core.Tests/FixedCultureTest.cs @@ -0,0 +1,21 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) P.O.S Informatique. All rights reserved. +// +//----------------------------------------------------------------------- + +namespace PosInformatique.Azure.Identity.AppRegistrationSecretWatcher.Tests +{ + using System.Globalization; + + public class FixedCultureTest + { + [Fact] + public void Constructor() + { + var culture = new FixedCulture("fr"); + + culture.Current.Should().BeSameAs(CultureInfo.GetCultureInfo("fr")); + } + } +} \ No newline at end of file From 441544d32151e32d3b1045845ce3229c78b916e3 Mon Sep 17 00:00:00 2001 From: Gilles TOURREAU Date: Wed, 19 Nov 2025 09:45:50 +0100 Subject: [PATCH 12/15] Upgrades to rc.4 of PosInformatique.Foundations. --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 24c3cf1..1cfa90b 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -2,7 +2,7 @@ true - 1.0.0-rc.3 + 1.0.0-rc.4 From 6c2b00ebe117fdff718d8b7c0edcd1509d392d15 Mon Sep 17 00:00:00 2001 From: Gilles TOURREAU Date: Wed, 19 Nov 2025 09:53:19 +0100 Subject: [PATCH 13/15] Fix CI release. --- .github/workflows/github-actions-release.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/github-actions-release.yml b/.github/workflows/github-actions-release.yml index 43cbfa6..5dc9868 100644 --- a/.github/workflows/github-actions-release.yml +++ b/.github/workflows/github-actions-release.yml @@ -42,6 +42,7 @@ jobs: tag_name: v${{ github.event.inputs.VersionPrefix }}${{ github.event.inputs.VersionSuffix && format('-{0}', github.event.inputs.VersionSuffix) || '' }} files: ./PosInformatique.Azure.Identity.AppRegistrationSecretWatcher.Functions.net9.0.zip overwrite_files: true - draft: ${{ !github.event.inputs.VersionSuffix }} + draft: ${{ github.event.inputs.VersionSuffix == '' }} + prerelease: ${{ github.event.inputs.VersionSuffix != '' }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file From abde21f78e8170a22c43228702e1c07e3bdaab57 Mon Sep 17 00:00:00 2001 From: Gilles TOURREAU Date: Wed, 19 Nov 2025 11:14:29 +0100 Subject: [PATCH 14/15] Improvements of the documentation. --- README.md | 5 + docs/ReportExample.html | 301 ++++++++++++++++++++++++++++++++++++++++ docs/ReportExample.png | Bin 0 -> 141187 bytes 3 files changed, 306 insertions(+) create mode 100644 docs/ReportExample.html create mode 100644 docs/ReportExample.png diff --git a/README.md b/README.md index 28e8b73..842b16a 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,14 @@ executable with the monitoring logic already packaged. ## Features - Monitor secrets across one or multiple Entra ID tenants (Entra ID, Azure B2C, Entra External ID,...). - Send a consolidated report at a customizable interval (cron-based). +- Quick summary with the number of secrets valid, expired and expiring soon. +- Customizable threshold of expiration date for the secrets. +- Date/Time formatting customizable to avoid issued between US / European expiration date formats. - Simple deployment to Azure Functions (pre-packaged, no build/CD required). - Runs on Azure Functions Consumption plan (**NO COST!!!**). +![Report example](./docs/ReportExample.png) + ## How it works - Enumerates App Registrations and checks client secrets and certificates nearing expiration. - Sends a summary report by email using Microsoft Graph. diff --git a/docs/ReportExample.html b/docs/ReportExample.html new file mode 100644 index 0000000..08b1314 --- /dev/null +++ b/docs/ReportExample.html @@ -0,0 +1,301 @@ + + + + + + +

Entra ID app registrations secret expiration report

+
+

Summary

+
+ + + + + + +
+
+
1
+
Expired
+
+
+
+
1
+
Expiring Soon
+
+
+
+
4
+
Valid
+
+
+
+
+
+

Contoso Corp

+ +
+
+

Human Resources

+ +
+
+

Secret first semester

+
+
+
Status:
+
Expired
+
+
+
Expiration date:
+
1-Jan-2025
+
+
+
Expired since:
+
10 days
+
+
+
+
+

Auto generated secret

+
+
+
Status:
+
Valid
+
+
+
Expiration date:
+
15-Sep-2025
+
+
+
Days before expiration:
+
20 days
+
+
+
+
+
+
+

Web portal

+ +
+
No secret
+
+
+
+
+
+

Customers Identity

+ +
+
+

Sales Frontend

+ +
+
+

Generated by Terraform

+
+
+
Status:
+
ExpiringSoon
+
+
+
Expiration date:
+
5-Feb-2025
+
+
+
Days before expiration:
+
50 days
+
+
+
+
+
+
+

Sales Backend

+ +
+
+

Secret 2-1-1

+
+
+
Status:
+
Valid
+
+
+
Expiration date:
+
6-juin-2025
+
+
+
Days before expiration:
+
60 days
+
+
+
+
+
+
+
+ + \ No newline at end of file diff --git a/docs/ReportExample.png b/docs/ReportExample.png new file mode 100644 index 0000000000000000000000000000000000000000..053c31a9fd32bae69f78f86877c2cca38d58be59 GIT binary patch literal 141187 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èv`1Ao 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

Sf5W>6TvmQfB(x$ali%2b1XuGRHYHR)6 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 literal 0 HcmV?d00001 From 8d5b724007d86720b329062593a3978742475100 Mon Sep 17 00:00:00 2001 From: Gilles TOURREAU Date: Wed, 19 Nov 2025 12:13:42 +0100 Subject: [PATCH 15/15] Upgrades PosInformatique.Foundations to v1.0.0 --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 1cfa90b..7dc0a65 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -2,7 +2,7 @@ true - 1.0.0-rc.4 + 1.0.0