From a22e888430c3b5385fa8ddcc47db07de8d26cec2 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Mon, 15 May 2023 10:41:51 +0800 Subject: [PATCH 001/328] Package updates --- .../DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj | 2 +- src/Certify.Service/App.config | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj index 7545564e7..bd104c8b8 100644 --- a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj +++ b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 diff --git a/src/Certify.Service/App.config b/src/Certify.Service/App.config index 05fa38a59..2383d0be7 100644 --- a/src/Certify.Service/App.config +++ b/src/Certify.Service/App.config @@ -1,4 +1,4 @@ - + From 494ad74e6e202c8de513f7197382846670f4463c Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Wed, 17 May 2023 12:55:36 +0800 Subject: [PATCH 002/328] API: add health endpoint and service config from ENV --- .../Controllers/v1/SystemController.cs | 29 ++++++++++++++++++- .../Certify.Server.Api.Public/Startup.cs | 19 +++++++++++- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/SystemController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/SystemController.cs index 5d01a0cef..3ad95e5d9 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/SystemController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/SystemController.cs @@ -1,5 +1,7 @@ -using System.Threading.Tasks; +using System; +using System.Threading.Tasks; using Certify.Client; +using Certify.Shared; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; @@ -36,9 +38,34 @@ public SystemController(ILogger logger, ICertifyInternalApiCli [Route("version")] public async Task GetSystemVersion() { + var versionInfo = await _client.GetAppVersion(); return new OkObjectResult(versionInfo); } + + /// + /// Check API is responding and can connect to background service + /// + /// + [HttpGet] + [Route("health")] + public async Task GetHealth() + { + var serviceAvailable = false; + var versionInfo = "Not available. Cannot connect to service worker."; + try + { + versionInfo = await _client.GetAppVersion(); + serviceAvailable = true; + } + catch { } + + var env = Environment.GetEnvironmentVariables(); + + var health = new { API = "OK", Service = versionInfo, ServiceAvailable = serviceAvailable, env = env }; + + return new OkObjectResult(health); + } } } diff --git a/src/Certify.Server/Certify.Server.Api.Public/Startup.cs b/src/Certify.Server/Certify.Server.Api.Public/Startup.cs index 3b2164fcb..ef6de7325 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Startup.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Startup.cs @@ -121,7 +121,24 @@ public void ConfigureServices(IServiceCollection services) #endif // connect to certify service var configManager = new ServiceConfigManager(); - var defaultConnectionConfig = new Shared.ServerConnection(configManager.GetServiceConfig()); + var serviceConfig = configManager.GetServiceConfig(); + + var serviceHostEnv = Environment.GetEnvironmentVariable("CERTIFY_SERVER_HOST"); + var servicePortEnv = Environment.GetEnvironmentVariable("CERTIFY_SERVER_PORT"); + + if (!string.IsNullOrEmpty(serviceHostEnv)) + { + serviceConfig.Host = serviceHostEnv; + } + + if (!string.IsNullOrEmpty(servicePortEnv) && int.TryParse(servicePortEnv, out var tryServicePort)) + { + serviceConfig.Port = tryServicePort; + } + + var defaultConnectionConfig = new Shared.ServerConnection(serviceConfig); + System.Diagnostics.Debug.WriteLine($"Public API: conecting to background service {serviceConfig:Host}:{serviceConfig.Port}"); + var connections = ServerConnectionManager.GetServerConnections(null, defaultConnectionConfig); var serverConnection = connections.FirstOrDefault(c => c.IsDefault == true); From dd86f40afdadd3b965eac28b7ea00390f66ef694 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Fri, 19 May 2023 11:12:11 +0800 Subject: [PATCH 003/328] Package updates and move some components to net8.0 --- .../Certify.Server.Api.Public.Tests.csproj | 4 +- .../Certify.Server.Api.Public.csproj | 4 +- .../Certify.Server.Core.csproj | 10 ++-- .../Certify.Service.Worker.csproj | 2 +- .../Certify.Shared.Extensions.csproj | 48 ++----------------- .../Certify.Core.Tests.Integration.csproj | 2 +- .../Certify.Core.Tests.Unit.csproj | 6 +-- .../Certify.Service.Tests.Integration.csproj | 2 +- .../Certify.UI.Tests.Integration.csproj | 2 +- .../Certify.UI.Desktop.csproj | 4 +- .../Certify.UI.Shared.csproj | 2 +- 11 files changed, 22 insertions(+), 64 deletions(-) diff --git a/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj b/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj index 5e18d7182..604c65257 100644 --- a/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj +++ b/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 false @@ -33,7 +33,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj index c562adaa5..f8881b424 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj +++ b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj @@ -1,7 +1,7 @@  - net7.0 + net8.0 Linux ..\..\..\..\certify-general 8793068b-aa98-48a5-807b-962b5b3e1aea @@ -13,7 +13,7 @@ - + diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj index 2aa33e944..c79d5aa03 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj @@ -1,7 +1,7 @@  - net7.0 + net8.0 3648c5f3-f642-441e-979e-d4624cd39e49 Linux AnyCPU @@ -9,15 +9,15 @@ - - - + + + - + diff --git a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Certify.Service.Worker.csproj b/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Certify.Service.Worker.csproj index dd7ffcbe6..9c296840d 100644 --- a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Certify.Service.Worker.csproj +++ b/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Certify.Service.Worker.csproj @@ -1,6 +1,6 @@  - net7.0 + net8.0 dotnet-Certify.Service.Worker-347A036F-C1EA-4D32-A163-DCB38C3CA53E Linux -v certifydata:/usr/share/Certify diff --git a/src/Certify.Shared.Extensions/Certify.Shared.Extensions.csproj b/src/Certify.Shared.Extensions/Certify.Shared.Extensions.csproj index 7e6e80ef6..283bdc62d 100644 --- a/src/Certify.Shared.Extensions/Certify.Shared.Extensions.csproj +++ b/src/Certify.Shared.Extensions/Certify.Shared.Extensions.csproj @@ -1,7 +1,7 @@  - net462;netstandard2.0;net7.0 + net462;netstandard2.0;net8.0 AnyCPU @@ -23,51 +23,9 @@ - - - - - + + diff --git a/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj b/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj index 4e6df448e..7ee8a5011 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj @@ -1,6 +1,6 @@  - net7.0;net462; + net8.0;net462; Debug;Release;Debug;Release Certify.Core.Tests Certify.Core.Tests diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj index 9e73f5bea..7e2b1e2ae 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj @@ -1,6 +1,6 @@ - net7.0;net462; + net8.0;net462; Debug;Release @@ -85,10 +85,10 @@ x64 - + 1701;1702;NU1701 - + 1701;1702;NU1701 diff --git a/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj b/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj index c37807565..f6e22b348 100644 --- a/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj @@ -1,6 +1,6 @@ - net7.0 + net8.0 Debug;Release; diff --git a/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj b/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj index 94b15d73c..dc6c10846 100644 --- a/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj @@ -1,6 +1,6 @@  - net7.0-windows + net8.0-windows Debug;Release; diff --git a/src/Certify.UI.Desktop/Certify.UI.Desktop.csproj b/src/Certify.UI.Desktop/Certify.UI.Desktop.csproj index bc5b55f80..a3b194749 100644 --- a/src/Certify.UI.Desktop/Certify.UI.Desktop.csproj +++ b/src/Certify.UI.Desktop/Certify.UI.Desktop.csproj @@ -2,12 +2,12 @@ WinExe - net7.0-windows + net8.0-windows true true icon.ico Certify.UI.App - AnyCPU;x64 + AnyCPU; True diff --git a/src/Certify.UI.Shared/Certify.UI.Shared.csproj b/src/Certify.UI.Shared/Certify.UI.Shared.csproj index a8973f02b..c7ea0f478 100644 --- a/src/Certify.UI.Shared/Certify.UI.Shared.csproj +++ b/src/Certify.UI.Shared/Certify.UI.Shared.csproj @@ -1,7 +1,7 @@ - net462;net7.0-windows; + net462;net8.0-windows; true true From 9321bb65249ffacca18cc7fb4a4339049e4ab93c Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Fri, 19 May 2023 11:49:07 +0800 Subject: [PATCH 004/328] cleanup --- src/Certify.Models/Config/CertRequestConfig.cs | 2 +- src/Certify.Server/Certify.Server.Api.Public/Startup.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Certify.Models/Config/CertRequestConfig.cs b/src/Certify.Models/Config/CertRequestConfig.cs index de88d4cfd..d57cb32c4 100644 --- a/src/Certify.Models/Config/CertRequestConfig.cs +++ b/src/Certify.Models/Config/CertRequestConfig.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; diff --git a/src/Certify.Server/Certify.Server.Api.Public/Startup.cs b/src/Certify.Server/Certify.Server.Api.Public/Startup.cs index ef6de7325..d3335a10d 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Startup.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Startup.cs @@ -137,7 +137,7 @@ public void ConfigureServices(IServiceCollection services) } var defaultConnectionConfig = new Shared.ServerConnection(serviceConfig); - System.Diagnostics.Debug.WriteLine($"Public API: conecting to background service {serviceConfig:Host}:{serviceConfig.Port}"); + System.Diagnostics.Debug.WriteLine($"Public API: connecting to background service {serviceConfig:Host}:{serviceConfig.Port}"); var connections = ServerConnectionManager.GetServerConnections(null, defaultConnectionConfig); var serverConnection = connections.FirstOrDefault(c => c.IsDefault == true); From a1e00b1c706a7514a718fbe758fa57103862b02c Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Fri, 19 May 2023 13:30:11 +0800 Subject: [PATCH 005/328] Adjust default launch settings --- .../Properties/launchSettings.json | 35 ++++++++++++------- .../appsettings.Development.json | 8 +++++ .../Certify.Service.Worker/appsettings.json | 2 +- 3 files changed, 32 insertions(+), 13 deletions(-) diff --git a/src/Certify.Server/Certify.Server.Api.Public/Properties/launchSettings.json b/src/Certify.Server/Certify.Server.Api.Public/Properties/launchSettings.json index a17832104..9db54dd53 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Properties/launchSettings.json +++ b/src/Certify.Server/Certify.Server.Api.Public/Properties/launchSettings.json @@ -1,16 +1,7 @@ { - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:59059/", - "sslPort": 44361 - } - }, "profiles": { "IIS Express": { "commandName": "IISExpress", - "launchBrowser": false, "launchUrl": "docs", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" @@ -18,11 +9,31 @@ }, "Certify.Server.Api.Public": { "commandName": "Project", - "launchBrowser": false, "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" + "ASPNETCORE_ENVIRONMENT": "Development", + "CERTIFY_SERVER_HOST": "127.0.0.2", + "CERTIFY_SERVER_PORT": "9695" + }, + "applicationUrl": "https://localhost:44361;http://localhost:44360" + }, + "WSL": { + "commandName": "WSL2", + "launchUrl": "https://localhost:5001", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "ASPNETCORE_URLS": "https://localhost:44361;http://localhost:44360", + "CERTIFY_SERVER_HOST": "localhost", + "CERTIFY_SERVER_PORT": "9695" }, - "applicationUrl": "https://localhost:5001;http://localhost:5000" + "distributionName": "" + } + }, + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:59059/", + "sslPort": 44361 } } } diff --git a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/appsettings.Development.json b/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/appsettings.Development.json index d941ff89b..1412eecd3 100644 --- a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/appsettings.Development.json +++ b/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/appsettings.Development.json @@ -7,5 +7,13 @@ "Microsoft.AspNetCore.SignalR": "Debug", "Microsoft.AspNetCore.Http.Connections": "Debug" } + }, + "API": { + "Service": { + "HttpPort": 9695, + "HttpsPort": 9443, + "UseHttps": false, + "BindingIP": "any" + } } } diff --git a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/appsettings.json b/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/appsettings.json index 937f63c3f..c723fdbd8 100644 --- a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/appsettings.json +++ b/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/appsettings.json @@ -10,7 +10,7 @@ }, "API": { "Service": { - "HttpPort": 9695, + "HttpPort": 9696, "HttpsPort": 9443, "UseHttps": false, "BindingIP": "any" From ad2412c5f37aaded7208090596ed4d34801986f0 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 25 May 2023 16:34:48 +0800 Subject: [PATCH 006/328] Package updates --- .../Certify.Service.Worker/Certify.Service.Worker.csproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Certify.Service.Worker.csproj b/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Certify.Service.Worker.csproj index 9c296840d..56fa79ead 100644 --- a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Certify.Service.Worker.csproj +++ b/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Certify.Service.Worker.csproj @@ -11,9 +11,9 @@ - - - + + + From 8b6526b407090c384d0edbedbf6febff8eb2b813 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Thu, 25 May 2023 17:11:44 +0800 Subject: [PATCH 007/328] Reduce size of API in use --- .../Certify.Server.Api.Public/Certify.Server.Api.Public.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj index f8881b424..c97fef982 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj +++ b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj @@ -10,6 +10,7 @@ DEBUG;TRACE + True From 4f3296563a1cbe5f148165fad4ea525603f43433 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Tue, 30 May 2023 12:05:21 +0800 Subject: [PATCH 008/328] Package updates --- src/Certify.Service/App.config | 2 +- src/Certify.Service/Certify.Service.csproj | 13 ++++++------- src/Certify.Shared/Certify.Shared.Core.csproj | 7 +++---- .../Certify.Core.Tests.Integration.csproj | 2 +- .../Certify.UI.Tests.Integration.csproj | 2 +- 5 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/Certify.Service/App.config b/src/Certify.Service/App.config index 2383d0be7..1222abb07 100644 --- a/src/Certify.Service/App.config +++ b/src/Certify.Service/App.config @@ -33,7 +33,7 @@ - + diff --git a/src/Certify.Service/Certify.Service.csproj b/src/Certify.Service/Certify.Service.csproj index a89735035..3f487ed0a 100644 --- a/src/Certify.Service/Certify.Service.csproj +++ b/src/Certify.Service/Certify.Service.csproj @@ -49,13 +49,12 @@ - - - - - - - + + + + + + diff --git a/src/Certify.Shared/Certify.Shared.Core.csproj b/src/Certify.Shared/Certify.Shared.Core.csproj index 254af5b42..efed0ac7c 100644 --- a/src/Certify.Shared/Certify.Shared.Core.csproj +++ b/src/Certify.Shared/Certify.Shared.Core.csproj @@ -1,7 +1,7 @@ - + - netstandard2.0;net6.0 + netstandard2.0;net8.0 AnyCPU;x64 @@ -20,8 +20,7 @@ - - + diff --git a/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj b/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj index 7ee8a5011..d5a730c52 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj @@ -1,4 +1,4 @@ - + net8.0;net462; Debug;Release;Debug;Release diff --git a/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj b/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj index dc6c10846..facbedb96 100644 --- a/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj @@ -1,4 +1,4 @@ - + net8.0-windows Debug;Release; From ec0cf58476b4217e8d09ea30c925367fb2d0aabd Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Thu, 1 Jun 2023 14:18:05 +0800 Subject: [PATCH 009/328] Cleanup --- .../Controllers/v1/SystemController.cs | 1 - src/Certify.Server/Certify.Server.Api.Public/Startup.cs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/SystemController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/SystemController.cs index 3ad95e5d9..da0203190 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/SystemController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/SystemController.cs @@ -1,7 +1,6 @@ using System; using System.Threading.Tasks; using Certify.Client; -using Certify.Shared; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; diff --git a/src/Certify.Server/Certify.Server.Api.Public/Startup.cs b/src/Certify.Server/Certify.Server.Api.Public/Startup.cs index d3335a10d..988893c56 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Startup.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Startup.cs @@ -132,7 +132,7 @@ public void ConfigureServices(IServiceCollection services) } if (!string.IsNullOrEmpty(servicePortEnv) && int.TryParse(servicePortEnv, out var tryServicePort)) - { + { serviceConfig.Port = tryServicePort; } From 901f3bb8f8f26edd58f5ef3ecebe8deb081559b5 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Thu, 1 Jun 2023 14:18:55 +0800 Subject: [PATCH 010/328] API: update endpoints and implement credential update --- .../Certify.Server.Api.Public.csproj | 4 ---- .../internal/StoredCredentialController.cs | 20 +++++++++++++++++++ .../Controllers/v1/AuthController.cs | 2 +- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj index c97fef982..0b7e2c71c 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj +++ b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj @@ -27,9 +27,5 @@ - - - - diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/StoredCredentialController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/StoredCredentialController.cs index fa2abc6dc..843116c2f 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/StoredCredentialController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/StoredCredentialController.cs @@ -46,5 +46,25 @@ public async Task GetStoredCredentials() var list = await _client.GetCredentials(); return new OkObjectResult(list); } + + /// + /// Add/Update a stored credential + /// + /// + [HttpPost] + [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Models.Config.StoredCredential))] + public async Task UpdateStoredCredential(Models.Config.StoredCredential credential) + { + var update = await _client.UpdateCredentials(credential); + if (update != null) + { + return new OkObjectResult(update); + } + else + { + return new BadRequestResult(); + } + } } } diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/AuthController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/AuthController.cs index ee27d15ca..59a35233d 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/AuthController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/AuthController.cs @@ -41,7 +41,7 @@ public AuthController(ILogger logger, ICertifyInternalApiClient [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] [HttpGet] [Route("status")] - public async Task Get() + public async Task CheckAuthStatus() { return await Task.FromResult(new OkResult()); } From 4109d02cb446bfba5042eaf1931fca3a0453ca10 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Thu, 1 Jun 2023 14:19:26 +0800 Subject: [PATCH 011/328] Core: Enable powershell on linux --- .../Certify.Shared.Extensions.csproj | 2 +- .../Scripts/DNS/PoshACME/Posh-ACME-Wrapper.ps1 | 13 ++++++++++++- .../Utils/PowerShellManager.cs | 17 +++++++++++------ 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/src/Certify.Shared.Extensions/Certify.Shared.Extensions.csproj b/src/Certify.Shared.Extensions/Certify.Shared.Extensions.csproj index 283bdc62d..b5711e9cf 100644 --- a/src/Certify.Shared.Extensions/Certify.Shared.Extensions.csproj +++ b/src/Certify.Shared.Extensions/Certify.Shared.Extensions.csproj @@ -24,7 +24,7 @@ - + diff --git a/src/Certify.Shared.Extensions/Scripts/DNS/PoshACME/Posh-ACME-Wrapper.ps1 b/src/Certify.Shared.Extensions/Scripts/DNS/PoshACME/Posh-ACME-Wrapper.ps1 index 2bbd11170..5d22f1410 100644 --- a/src/Certify.Shared.Extensions/Scripts/DNS/PoshACME/Posh-ACME-Wrapper.ps1 +++ b/src/Certify.Shared.Extensions/Scripts/DNS/PoshACME/Posh-ACME-Wrapper.ps1 @@ -15,7 +15,18 @@ try { # iwr https://tls13.1d.pw # TLS 1.3 test # Load Assembly without using Add-Type to avoid locking assembly dll -$assemblyBytes = [System.IO.File]::ReadAllBytes("$($PoshACMERoot)\..\..\..\BouncyCastle.Cryptography.dll") +$bcPath = "$($PoshACMERoot)/../../../BouncyCastle.Cryptography.dll" +If (Test-Path -Path $bcPath -PathType Leaf -ne $true) +{ + $bcPath = "$($PoshACMERoot)\lib\BC.Crypto.1.8.8.2-netstandard2.0.dll" + + If (Test-Path -Path $bcPath -PathType Leaf -ne $true){ + Write-Error "Unable to find BouncyCastle dll at $bcPath" + Exit 1 + } +} + +$assemblyBytes = [System.IO.File]::ReadAllBytes($bcPath) [System.Reflection.Assembly]::Load($assemblyBytes) | out-null # Dot source the files (in the same manner as Posh-ACME would) diff --git a/src/Certify.Shared.Extensions/Utils/PowerShellManager.cs b/src/Certify.Shared.Extensions/Utils/PowerShellManager.cs index 0502ed92d..e8fabb035 100644 --- a/src/Certify.Shared.Extensions/Utils/PowerShellManager.cs +++ b/src/Certify.Shared.Extensions/Utils/PowerShellManager.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Management.Automation; using System.Management.Automation.Runspaces; +using System.Runtime.InteropServices; using System.Security; using System.Security.AccessControl; using System.Security.Principal; @@ -383,13 +384,17 @@ private static ActionResult InvokePowershell(CertificateRequestResult result, st executionPolicy = parameters.FirstOrDefault(p => p.Key.ToLower() == "executionpolicy").Value?.ToString(); } - if (!string.IsNullOrEmpty(executionPolicy)) + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - shell.AddCommand("Set-ExecutionPolicy") - .AddParameter("ExecutionPolicy", executionPolicy) - .AddParameter("Scope", "Process") - .AddParameter("Force") - .Invoke(); + // on windows we may need to set execution policy depending on user preferences + if (!string.IsNullOrEmpty(executionPolicy)) + { + shell.AddCommand("Set-ExecutionPolicy") + .AddParameter("ExecutionPolicy", executionPolicy) + .AddParameter("Scope", "Process") + .AddParameter("Force") + .Invoke(); + } } // add script command to invoke From 6d0ebb7297621167c0929f480a6b71de666ffa44 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Thu, 8 Jun 2023 11:48:14 +0800 Subject: [PATCH 012/328] API: implement dns zone lookup --- .../internal/ChallengeProviderController.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/ChallengeProviderController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/ChallengeProviderController.cs index 6ee41ac9e..ff3234c7e 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/ChallengeProviderController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/ChallengeProviderController.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using Certify.Client; +using Certify.Models.Providers; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; @@ -38,7 +39,6 @@ public ChallengeProviderController(ILogger logger, /// [HttpGet] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] - [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(List))] public async Task GetChallengeProviders() @@ -46,5 +46,20 @@ public async Task GetChallengeProviders() var list = await _client.GetChallengeAPIList(); return new OkObjectResult(list); } + + /// + /// Fetch list of DNS zones for a given DNS provider and credential + /// + /// + /// + /// + [HttpGet] + [Route("dnszones")] + [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(List))] + public async Task> GetDnsZones(string providerTypeId, string credentialsId) + { + return await _client.GetDnsProviderZones(providerTypeId, credentialsId); + } } } From 0179361d90d7a86b6f18a239b5f7f6ab952e2abd Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 15 Jun 2023 17:34:01 +0800 Subject: [PATCH 013/328] Package updates Package updates --- src/Certify.Client/Certify.Client.csproj | 2 +- src/Certify.Core/Certify.Core.csproj | 2 +- .../DNS/Azure/Plugin.DNS.Azure.csproj | 3 ++- .../Certify.Server.Api.Public.Tests.csproj | 1 - .../Certify.Server.Api.Public.csproj | 6 +++--- .../Certify.Server.Core/Certify.Server.Core.csproj | 10 +++++----- .../Certify.Service.Worker.csproj | 2 +- src/Certify.Shared/Certify.Shared.Core.csproj | 2 +- .../Certify.Core.Tests.Integration.csproj | 2 +- src/Certify.UI.Shared/Certify.UI.Shared.csproj | 6 +++--- src/Certify.UI/Certify.UI.csproj | 3 ++- 11 files changed, 20 insertions(+), 19 deletions(-) diff --git a/src/Certify.Client/Certify.Client.csproj b/src/Certify.Client/Certify.Client.csproj index c224410c3..a7b2a9657 100644 --- a/src/Certify.Client/Certify.Client.csproj +++ b/src/Certify.Client/Certify.Client.csproj @@ -20,7 +20,7 @@ - + diff --git a/src/Certify.Core/Certify.Core.csproj b/src/Certify.Core/Certify.Core.csproj index 144ad0de9..d53f15c4f 100644 --- a/src/Certify.Core/Certify.Core.csproj +++ b/src/Certify.Core/Certify.Core.csproj @@ -71,7 +71,7 @@ PackageReference - + diff --git a/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj b/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj index f7a863373..b8bed8208 100644 --- a/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj +++ b/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 @@ -10,6 +10,7 @@ + diff --git a/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj b/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj index 604c65257..5d28b3f6f 100644 --- a/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj +++ b/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj @@ -33,7 +33,6 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - diff --git a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj index 0b7e2c71c..c38ebf9bc 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj +++ b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj @@ -14,11 +14,11 @@ - - + + - + diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj index c79d5aa03..6cf4c5298 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj @@ -9,15 +9,15 @@ - - - + + + - + - + diff --git a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Certify.Service.Worker.csproj b/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Certify.Service.Worker.csproj index 56fa79ead..559de7603 100644 --- a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Certify.Service.Worker.csproj +++ b/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Certify.Service.Worker.csproj @@ -15,7 +15,7 @@ - + diff --git a/src/Certify.Shared/Certify.Shared.Core.csproj b/src/Certify.Shared/Certify.Shared.Core.csproj index efed0ac7c..ad1a3f114 100644 --- a/src/Certify.Shared/Certify.Shared.Core.csproj +++ b/src/Certify.Shared/Certify.Shared.Core.csproj @@ -20,7 +20,7 @@ - + diff --git a/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj b/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj index d5a730c52..68915ddb5 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj @@ -84,7 +84,7 @@ - + diff --git a/src/Certify.UI.Shared/Certify.UI.Shared.csproj b/src/Certify.UI.Shared/Certify.UI.Shared.csproj index c7ea0f478..011ebd213 100644 --- a/src/Certify.UI.Shared/Certify.UI.Shared.csproj +++ b/src/Certify.UI.Shared/Certify.UI.Shared.csproj @@ -32,10 +32,10 @@ - - + + - + diff --git a/src/Certify.UI/Certify.UI.csproj b/src/Certify.UI/Certify.UI.csproj index 5e47bfd30..e0cf64800 100644 --- a/src/Certify.UI/Certify.UI.csproj +++ b/src/Certify.UI/Certify.UI.csproj @@ -1,4 +1,4 @@ - + @@ -159,6 +159,7 @@ 2.4.10 + 3.0.0-alpha0457 0.5.0.1 From e94ea0da7bb03af4620fd3779d5a10f10edb0e17 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Mon, 19 Jun 2023 13:34:18 +0800 Subject: [PATCH 014/328] Minor updates --- .../Certify.Server.Api.Public.csproj | 2 +- .../Middleware/AuthenticationExtension.cs | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj index c38ebf9bc..be973e4fa 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj +++ b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj @@ -10,7 +10,7 @@ DEBUG;TRACE - True + diff --git a/src/Certify.Server/Certify.Server.Api.Public/Middleware/AuthenticationExtension.cs b/src/Certify.Server/Certify.Server.Api.Public/Middleware/AuthenticationExtension.cs index c13134e70..240207755 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Middleware/AuthenticationExtension.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Middleware/AuthenticationExtension.cs @@ -1,4 +1,5 @@ -using System.Text; +using System; +using System.Text; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -21,6 +22,11 @@ public static IServiceCollection AddTokenAuthentication(this IServiceCollection { var secret = config.GetSection("JwtSettings").GetSection("secret").Value; + if (secret == null) + { + throw new ArgumentNullException("Token authentication requires JwtSettings > Secret to be set in order to perform JWT operations"); + } + var key = Encoding.ASCII.GetBytes(secret); services.AddAuthentication(x => { From e6e5f5436d1e3a467855a76ae726a11fc7c539af Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Mon, 19 Jun 2023 15:08:59 +0800 Subject: [PATCH 015/328] Update readme --- .../Certify.Service.Worker/readme.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/readme.md b/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/readme.md index 9f863b4a7..7546905d5 100644 --- a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/readme.md +++ b/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/readme.md @@ -1,5 +1,13 @@ Certify Service Worker (Cross Platform) ----------------- + +# Architecture +- Certify.Server.Core, running as a hosted internal API app within a Worker (Certify.Service.Worker). This is a re-implementation of the original service usedby the desktop UI. +- Certify.Server.Api.Public, running as a API for public consumption, wrapping calls to the core internal API + +The advantage of this structure is that the public API can be exposed to the network for Web UI access, without client machines talking directly to the core service. The public API can run as the least prvilelged service user, while the core service may require elevated privileges depending on how it is used. + + Development workflow From 7eeaf997eee7883e5552ca0e546cdfc7cbe76775 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Wed, 28 Jun 2023 17:23:01 +0800 Subject: [PATCH 016/328] Implement paged result set with count for managed cert search --- src/Certify.Client/CertifyApiClient.cs | 18 +++++++++++++ src/Certify.Client/ICertifyClient.cs | 1 + .../CertifyManager.ManagedCertificates.cs | 23 +++++++++++++++++ .../CertifyManager/ICertifyManager.cs | 1 + .../API/ManagedCertificateSummary.cs | 8 ++++++ .../Config/ManagedCertificate.cs | 12 +++++++++ .../Controllers/v1/CertificateController.cs | 25 +++++++++++++------ .../ManagedCertificateController.cs | 8 ++++++ 8 files changed, 89 insertions(+), 7 deletions(-) diff --git a/src/Certify.Client/CertifyApiClient.cs b/src/Certify.Client/CertifyApiClient.cs index 6b5dce441..9889fda58 100644 --- a/src/Certify.Client/CertifyApiClient.cs +++ b/src/Certify.Client/CertifyApiClient.cs @@ -379,6 +379,24 @@ public async Task> GetManagedCertificates(ManagedCertif } } + /// + /// Get search results, same as GetManagedCertificates but result has count of total results available as used when paging + /// + /// + /// + public async Task GetManagedCertificateSearchResult(ManagedCertificateFilter filter) + { + var response = await PostAsync("managedcertificates/results/", filter); + var serializer = new JsonSerializer(); + + using (var sr = new StreamReader(await response.Content.ReadAsStreamAsync())) + using (var reader = new JsonTextReader(sr)) + { + var result = serializer.Deserialize(reader); + return result; + } + } + public async Task GetManagedCertificate(string managedItemId) { var result = await FetchAsync($"managedcertificates/{managedItemId}"); diff --git a/src/Certify.Client/ICertifyClient.cs b/src/Certify.Client/ICertifyClient.cs index 844e8994e..d76755cd1 100644 --- a/src/Certify.Client/ICertifyClient.cs +++ b/src/Certify.Client/ICertifyClient.cs @@ -76,6 +76,7 @@ public interface ICertifyInternalApiClient #region Managed Certificates Task> GetManagedCertificates(ManagedCertificateFilter filter); + Task GetManagedCertificateSearchResult(ManagedCertificateFilter filter); Task GetManagedCertificate(string managedItemId); diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagedCertificates.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagedCertificates.cs index 8bc72d6cc..0141027fa 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagedCertificates.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagedCertificates.cs @@ -69,6 +69,29 @@ public async Task> GetManagedCertificates(ManagedCertif return list; } + /// + /// Get list of managed certificates based on then given filter criteria, as search result with total count + /// + /// + /// + public async Task GetManagedCertificateResults(ManagedCertificateFilter filter) + { + var result = new ManagedCertificateSearchResult(); + + var list = await _itemManager.Find(filter); + if (filter.PageSize > 0) + { + // TODO: implement count on provider directly + filter.PageSize = null; + filter.PageIndex = null; + var all = await _itemManager.Find(filter); + result.TotalResults = all.Count; + } + + result.Results = list; + + return result; + } /// /// Update the stored details for the given managed certificate and report update to client(s) /// diff --git a/src/Certify.Core/Management/CertifyManager/ICertifyManager.cs b/src/Certify.Core/Management/CertifyManager/ICertifyManager.cs index ddbd48990..04c0614cf 100644 --- a/src/Certify.Core/Management/CertifyManager/ICertifyManager.cs +++ b/src/Certify.Core/Management/CertifyManager/ICertifyManager.cs @@ -27,6 +27,7 @@ public interface ICertifyManager Task GetManagedCertificate(string id); Task> GetManagedCertificates(ManagedCertificateFilter filter = null); + Task GetManagedCertificateResults(ManagedCertificateFilter filter = null); Task UpdateManagedCertificate(ManagedCertificate site); diff --git a/src/Certify.Models/API/ManagedCertificateSummary.cs b/src/Certify.Models/API/ManagedCertificateSummary.cs index 712f5b740..8ce588047 100644 --- a/src/Certify.Models/API/ManagedCertificateSummary.cs +++ b/src/Certify.Models/API/ManagedCertificateSummary.cs @@ -53,4 +53,12 @@ public class ManagedCertificateSummary /// public bool HasCertificate { get; set; } } + + public class ManagedCertificateSummaryResult + { + public IEnumerable Results { get; set; } = new List(); + public int TotalResults { get; set; } + public int PageIndex { get; set; } + public int PageSize { get; set; } + } } diff --git a/src/Certify.Models/Config/ManagedCertificate.cs b/src/Certify.Models/Config/ManagedCertificate.cs index 6dedce45d..19f009444 100644 --- a/src/Certify.Models/Config/ManagedCertificate.cs +++ b/src/Certify.Models/Config/ManagedCertificate.cs @@ -110,6 +110,18 @@ public Lifetime(DateTimeOffset dateStart, DateTimeOffset dateEnd) } } + public class ManagedCertificateSearchResult + { + /// + /// Results in this search (may be a paged subset) + /// + public IEnumerable Results { get; set; } = Enumerable.Empty(); + /// + /// Total results available + /// + public int TotalResults { get; set; } + } + public class ManagedCertificate : BindableBase { public ManagedCertificate() diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs index c505e3a2e..33f75a9df 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs @@ -149,15 +149,19 @@ public async Task DownloadLogAsText(string managedCertId, int max /// [HttpGet] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] - [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(List))] - public async Task GetManagedCertificates(string keyword) + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(ManagedCertificateSummaryResult))] + public async Task GetManagedCertificates(string keyword, int? page = null, int? pageSize = null) { - var managedCerts = await _client.GetManagedCertificates(new Models.ManagedCertificateFilter { Keyword = keyword }); + var managedCertResult = await _client.GetManagedCertificateSearchResult( + new Models.ManagedCertificateFilter + { + Keyword = keyword, + PageIndex = page, + PageSize = pageSize + }); - //TODO: this assumes all identifiers are DNS, may be IPs in the future. - - var list = managedCerts.Select(i => new ManagedCertificateSummary + var list = managedCertResult.Results.Select(i => new ManagedCertificateSummary { Id = i.Id, Title = i.Name, @@ -170,7 +174,14 @@ public async Task GetManagedCertificates(string keyword) HasCertificate = !string.IsNullOrEmpty(i.CertificatePath) }).OrderBy(a => a.Title); - return new OkObjectResult(list); + var result = new ManagedCertificateSummaryResult { + Results = list, + TotalResults = managedCertResult.TotalResults, + PageIndex = page ?? 0, + PageSize = pageSize ?? list.Count() + }; + + return new OkObjectResult(result); } /// diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/ManagedCertificateController.cs b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/ManagedCertificateController.cs index 81bed9095..8c891b901 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/ManagedCertificateController.cs +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/ManagedCertificateController.cs @@ -34,6 +34,14 @@ public async Task> Search(ManagedCertificateFilter filt return await _certifyManager.GetManagedCertificates(filter); } + // Get List of Top N Managed Certificates, filtered by title, as a Search Result with total count + [HttpPost, Route("results")] + public async Task GetResults(ManagedCertificateFilter filter) + { + DebugLog(); + return await _certifyManager.GetManagedCertificateResults(filter); + } + [HttpGet, Route("{id}")] public async Task GetById(string id) { From 16155fb9d9ef7d51272bc30f45f77bd56f15bc80 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Wed, 28 Jun 2023 17:33:42 +0800 Subject: [PATCH 017/328] Package updates --- src/Certify.Shared.Extensions/Certify.Shared.Extensions.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Certify.Shared.Extensions/Certify.Shared.Extensions.csproj b/src/Certify.Shared.Extensions/Certify.Shared.Extensions.csproj index b5711e9cf..95aef1a40 100644 --- a/src/Certify.Shared.Extensions/Certify.Shared.Extensions.csproj +++ b/src/Certify.Shared.Extensions/Certify.Shared.Extensions.csproj @@ -1,4 +1,4 @@ - + net462;netstandard2.0;net8.0 From d220a4bb7d7a9cfba275caf81ddcf5ef96f4443e Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Fri, 30 Jun 2023 16:54:24 +0800 Subject: [PATCH 018/328] WIP: pref for powershell executable path --- .../Utils/PowerShellManager.cs | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/Certify.Shared.Extensions/Utils/PowerShellManager.cs b/src/Certify.Shared.Extensions/Utils/PowerShellManager.cs index e8fabb035..9c0693e33 100644 --- a/src/Certify.Shared.Extensions/Utils/PowerShellManager.cs +++ b/src/Certify.Shared.Extensions/Utils/PowerShellManager.cs @@ -153,13 +153,24 @@ public static async Task RunScript( } } - private static string GetPowershellExePath() + /// + /// Get the path to the pwoershell exe, optionally using a preferred path first + /// + /// + /// + private static string GetPowershellExePath(string powershellPathPreference) { var searchPaths = new List() { "%WINDIR%\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", - "%PROGRAMFILES%\\PowerShell\\7\\pwsh.exe" + "%PROGRAMFILES%\\PowerShell\\7\\pwsh.exe", + "/usr/bin/pwsh" }; + if (!string.IsNullOrWhiteSpace(powershellPathPreference)) + { + searchPaths.Insert(0, powershellPathPreference); + } + // if powershell exe path supplied, use that (with expansion) and check exe exists // otherwise detect powershell exe location foreach (var exePath in searchPaths) @@ -174,15 +185,15 @@ private static string GetPowershellExePath() return null; } - private static ActionResult ExecutePowershellAsProcess(CertificateRequestResult result, string executionPolicy, string scriptFile, Dictionary parameters, Dictionary credentials, string scriptContent, PowerShell shell, bool autoConvertBoolean = true, string[] ignoredCommandExceptions = null, int timeoutMinutes = 5) + private static ActionResult ExecutePowershellAsProcess(CertificateRequestResult result, string executionPolicy, string scriptFile, Dictionary parameters, Dictionary credentials, string scriptContent, PowerShell shell, bool autoConvertBoolean = true, string[] ignoredCommandExceptions = null, int timeoutMinutes = 5, string powershellPathPreference = null) { var _log = new StringBuilder(); - var commandExe = GetPowershellExePath(); + var commandExe = GetPowershellExePath(powershellPathPreference); if (commandExe == null) { - return new ActionResult("Failed to locate powershell exe. Cannot launch as new process.", false); + return new ActionResult("Failed to locate powershell executable. Cannot launch as new process.", false); } if (!string.IsNullOrEmpty(scriptContent)) From 6692d79bb014896ad2600148e3dd6e9c945c2fdb Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Wed, 17 May 2023 12:55:36 +0800 Subject: [PATCH 019/328] API: add health endpoint and service config from ENV --- .../Controllers/v1/SystemController.cs | 1 + src/Certify.Server/Certify.Server.Api.Public/Startup.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/SystemController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/SystemController.cs index da0203190..3ad95e5d9 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/SystemController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/SystemController.cs @@ -1,6 +1,7 @@ using System; using System.Threading.Tasks; using Certify.Client; +using Certify.Shared; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; diff --git a/src/Certify.Server/Certify.Server.Api.Public/Startup.cs b/src/Certify.Server/Certify.Server.Api.Public/Startup.cs index 988893c56..037560049 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Startup.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Startup.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; From 8b4ad6126d68af3d02c167bb20d3a0dc93996aef Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Fri, 19 May 2023 11:12:11 +0800 Subject: [PATCH 020/328] Package updates and move some components to net8.0 --- .../Certify.Server.Api.Public.Tests.csproj | 1 + .../Certify.Server.Api.Public.csproj | 2 +- .../Certify.Server.Core/Certify.Server.Core.csproj | 4 ++-- .../Certify.Shared.Extensions.csproj | 2 ++ 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj b/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj index 5d28b3f6f..604c65257 100644 --- a/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj +++ b/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj @@ -33,6 +33,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj index be973e4fa..43ef7e789 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj +++ b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj @@ -1,4 +1,4 @@ - + net8.0 diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj index 6cf4c5298..af98d2773 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj @@ -1,4 +1,4 @@ - + net8.0 @@ -17,7 +17,7 @@ - + diff --git a/src/Certify.Shared.Extensions/Certify.Shared.Extensions.csproj b/src/Certify.Shared.Extensions/Certify.Shared.Extensions.csproj index 95aef1a40..6552aef74 100644 --- a/src/Certify.Shared.Extensions/Certify.Shared.Extensions.csproj +++ b/src/Certify.Shared.Extensions/Certify.Shared.Extensions.csproj @@ -26,6 +26,8 @@ + + From 07faa32139c410e9575c80f4f733a0e2ff2b2bd4 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Thu, 1 Jun 2023 14:18:05 +0800 Subject: [PATCH 021/328] Cleanup --- .../Certify.Server.Api.Public/Controllers/v1/SystemController.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/SystemController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/SystemController.cs index 3ad95e5d9..da0203190 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/SystemController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/SystemController.cs @@ -1,7 +1,6 @@ using System; using System.Threading.Tasks; using Certify.Client; -using Certify.Shared; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; From a1d0f3be9678ec37f677c37865f57846e6300562 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 15 Jun 2023 17:34:01 +0800 Subject: [PATCH 022/328] Package updates --- src/Certify.Client/Certify.Client.csproj | 2 +- .../Certify.Server.Api.Public.Tests.csproj | 2 +- .../Certify.Server.Core/Certify.Server.Core.csproj | 2 +- .../Certify.Service.Worker/Certify.Service.Worker.csproj | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Certify.Client/Certify.Client.csproj b/src/Certify.Client/Certify.Client.csproj index a7b2a9657..f25797cdc 100644 --- a/src/Certify.Client/Certify.Client.csproj +++ b/src/Certify.Client/Certify.Client.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 diff --git a/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj b/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj index 604c65257..a89c912fd 100644 --- a/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj +++ b/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj @@ -33,7 +33,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj index af98d2773..99b1995b1 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj @@ -17,7 +17,7 @@ - + diff --git a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Certify.Service.Worker.csproj b/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Certify.Service.Worker.csproj index 559de7603..f68127134 100644 --- a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Certify.Service.Worker.csproj +++ b/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Certify.Service.Worker.csproj @@ -1,4 +1,4 @@ - + net8.0 dotnet-Certify.Service.Worker-347A036F-C1EA-4D32-A163-DCB38C3CA53E From 172bbacbe71c83226f061f8e49b34cc85b58faaf Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Fri, 16 Jun 2023 15:19:20 +0800 Subject: [PATCH 023/328] Package updates --- .../Certify.Server.Api.Public.Tests.csproj | 1 - .../Certify.Server.Core/Certify.Server.Core.csproj | 2 +- src/Certify.UI/Certify.UI.csproj | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj b/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj index a89c912fd..5d28b3f6f 100644 --- a/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj +++ b/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj @@ -33,7 +33,6 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj index 99b1995b1..d02e3857d 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj @@ -17,7 +17,7 @@ - + diff --git a/src/Certify.UI/Certify.UI.csproj b/src/Certify.UI/Certify.UI.csproj index e0cf64800..3afcb0ac0 100644 --- a/src/Certify.UI/Certify.UI.csproj +++ b/src/Certify.UI/Certify.UI.csproj @@ -158,7 +158,6 @@ 4.7.0.9 - 2.4.10 3.0.0-alpha0457 From d8bafe8388f0024a0287d08956fa385d7eca5b22 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Wed, 28 Jun 2023 17:23:01 +0800 Subject: [PATCH 024/328] Implement paged result set with count for managed cert search --- src/Certify.Models/Config/ManagedCertificate.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Certify.Models/Config/ManagedCertificate.cs b/src/Certify.Models/Config/ManagedCertificate.cs index 19f009444..3798f19af 100644 --- a/src/Certify.Models/Config/ManagedCertificate.cs +++ b/src/Certify.Models/Config/ManagedCertificate.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; From 7eeccf6b414df7218592e65c017b97fb98596e3e Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Mon, 31 Jul 2023 17:27:17 +0800 Subject: [PATCH 025/328] Package updates --- src/Certify.Client/Certify.Client.csproj | 6 +++--- src/Certify.Core/Certify.Core.csproj | 2 +- src/Certify.Models/Certify.Models.csproj | 5 ++--- .../DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj | 2 +- .../Certify.Server.Api.Public.Tests.csproj | 4 ++-- .../Certify.Server.Core/Certify.Server.Core.csproj | 6 +++--- .../Certify.Service.Worker/Certify.Service.Worker.csproj | 2 +- .../Certify.Shared.Extensions.csproj | 6 +++--- src/Certify.Shared/Certify.Shared.Core.csproj | 2 +- .../Certify.Core.Tests.Integration.csproj | 2 +- src/Certify.UI.Shared/Certify.UI.Shared.csproj | 4 ++-- 11 files changed, 20 insertions(+), 21 deletions(-) diff --git a/src/Certify.Client/Certify.Client.csproj b/src/Certify.Client/Certify.Client.csproj index f25797cdc..1387da6e6 100644 --- a/src/Certify.Client/Certify.Client.csproj +++ b/src/Certify.Client/Certify.Client.csproj @@ -16,11 +16,11 @@ - - + + - + diff --git a/src/Certify.Core/Certify.Core.csproj b/src/Certify.Core/Certify.Core.csproj index d53f15c4f..144ad0de9 100644 --- a/src/Certify.Core/Certify.Core.csproj +++ b/src/Certify.Core/Certify.Core.csproj @@ -71,7 +71,7 @@ PackageReference - + diff --git a/src/Certify.Models/Certify.Models.csproj b/src/Certify.Models/Certify.Models.csproj index dbedd5145..0e73dcb9d 100644 --- a/src/Certify.Models/Certify.Models.csproj +++ b/src/Certify.Models/Certify.Models.csproj @@ -1,4 +1,4 @@ - + netstandard2.0;net462 Certify @@ -30,8 +30,7 @@ all - - + diff --git a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj index bd104c8b8..f9fec9c23 100644 --- a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj +++ b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj b/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj index 5d28b3f6f..77bd246fa 100644 --- a/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj +++ b/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj @@ -25,8 +25,8 @@ - - + + diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj index d02e3857d..fc310df3c 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj @@ -10,10 +10,10 @@ - - + + - + diff --git a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Certify.Service.Worker.csproj b/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Certify.Service.Worker.csproj index f68127134..4cd27752d 100644 --- a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Certify.Service.Worker.csproj +++ b/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Certify.Service.Worker.csproj @@ -15,7 +15,7 @@ - + diff --git a/src/Certify.Shared.Extensions/Certify.Shared.Extensions.csproj b/src/Certify.Shared.Extensions/Certify.Shared.Extensions.csproj index 6552aef74..f4704487d 100644 --- a/src/Certify.Shared.Extensions/Certify.Shared.Extensions.csproj +++ b/src/Certify.Shared.Extensions/Certify.Shared.Extensions.csproj @@ -1,4 +1,4 @@ - + net462;netstandard2.0;net8.0 @@ -24,9 +24,9 @@ - + - + diff --git a/src/Certify.Shared/Certify.Shared.Core.csproj b/src/Certify.Shared/Certify.Shared.Core.csproj index ad1a3f114..a05ae7d49 100644 --- a/src/Certify.Shared/Certify.Shared.Core.csproj +++ b/src/Certify.Shared/Certify.Shared.Core.csproj @@ -20,7 +20,7 @@ - + diff --git a/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj b/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj index 68915ddb5..d5a730c52 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj @@ -84,7 +84,7 @@ - + diff --git a/src/Certify.UI.Shared/Certify.UI.Shared.csproj b/src/Certify.UI.Shared/Certify.UI.Shared.csproj index 011ebd213..7597ff47e 100644 --- a/src/Certify.UI.Shared/Certify.UI.Shared.csproj +++ b/src/Certify.UI.Shared/Certify.UI.Shared.csproj @@ -33,14 +33,14 @@ - + - + From 30b9a34b20e3e29dc0e3760c0062e4cd5ab12157 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Wed, 2 Aug 2023 16:23:10 +0800 Subject: [PATCH 026/328] Implement Log parsing --- src/Certify.Client/CertifyApiClient.cs | 5 +- src/Certify.Client/ICertifyClient.cs | 2 +- .../CertifyManager.ManagedCertificates.cs | 14 ++-- .../CertifyManager/ICertifyManager.cs | 3 +- src/Certify.Models/API/LogResult.cs | 72 +++++++++++++++++++ .../Certify.Server.Api.Public.csproj | 4 ++ .../Controllers/v1/CertificateController.cs | 56 +++------------ .../ManagedCertificateController.cs | 3 +- .../ManagedCertificateController.cs | 3 +- .../Certify.Core.Tests.Unit/MiscTests.cs | 22 +++++- .../ManagedCertificate/StatusInfo.xaml.cs | 3 +- .../AppViewModel.ManagedCerticates.cs | 3 +- 12 files changed, 126 insertions(+), 64 deletions(-) create mode 100644 src/Certify.Models/API/LogResult.cs diff --git a/src/Certify.Client/CertifyApiClient.cs b/src/Certify.Client/CertifyApiClient.cs index 9889fda58..faf53c515 100644 --- a/src/Certify.Client/CertifyApiClient.cs +++ b/src/Certify.Client/CertifyApiClient.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using Certify.Config.Migration; using Certify.Models; +using Certify.Models.API; using Certify.Models.Config; using Certify.Models.Utils; using Certify.Shared; @@ -564,10 +565,10 @@ public async Task> ValidateDeploymentTask(DeploymentTaskValid return JsonConvert.DeserializeObject>(await result.Content.ReadAsStringAsync()); } - public async Task GetItemLog(string id, int limit) + public async Task GetItemLog(string id, int limit) { var response = await FetchAsync($"managedcertificates/log/{id}/{limit}"); - return JsonConvert.DeserializeObject(response); + return JsonConvert.DeserializeObject(response); } public async Task> PerformManagedCertMaintenance(string id = null) diff --git a/src/Certify.Client/ICertifyClient.cs b/src/Certify.Client/ICertifyClient.cs index d76755cd1..339fac4c1 100644 --- a/src/Certify.Client/ICertifyClient.cs +++ b/src/Certify.Client/ICertifyClient.cs @@ -115,7 +115,7 @@ public interface ICertifyInternalApiClient Task> ValidateDeploymentTask(DeploymentTaskValidationInfo info); - Task GetItemLog(string id, int limit); + Task GetItemLog(string id, int limit); #endregion Managed Certificates diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagedCertificates.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagedCertificates.cs index 0141027fa..088fc4a65 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagedCertificates.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagedCertificates.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Threading.Tasks; using Certify.Models; +using Certify.Models.API; using Certify.Models.Providers; using Certify.Models.Shared; @@ -87,7 +88,7 @@ public async Task GetManagedCertificateResults(M var all = await _itemManager.Find(filter); result.TotalResults = all.Count; } - + result.Results = list; return result; @@ -472,7 +473,7 @@ public async Task> GetDnsProviderZones(string providerTypeId, stri } } - public async Task GetItemLog(string id, int limit) + public async Task GetItemLog(string id, int limit) { var logPath = ManagedCertificateLog.GetLogPath(id); @@ -485,19 +486,18 @@ public async Task GetItemLog(string id, int limit) var log = System.IO.File.ReadAllLines(logPath) .Reverse() .Take(limit) - .Reverse() .ToArray(); - - return log; + var parsed = LogParser.Parse(log); + return parsed; } catch (Exception exp) { - return new string[] { $"Failed to read log: {exp}" }; + return new LogItem[] { new LogItem { LogLevel = "ERR", EventDate = DateTime.Now, Message = $"Failed to read log: {exp}" } }; } } else { - return await Task.FromResult(new string[] { "" }); + return await Task.FromResult(Array.Empty()); } } diff --git a/src/Certify.Core/Management/CertifyManager/ICertifyManager.cs b/src/Certify.Core/Management/CertifyManager/ICertifyManager.cs index 04c0614cf..d936cf4d3 100644 --- a/src/Certify.Core/Management/CertifyManager/ICertifyManager.cs +++ b/src/Certify.Core/Management/CertifyManager/ICertifyManager.cs @@ -5,6 +5,7 @@ using Certify.Config; using Certify.Config.Migration; using Certify.Models; +using Certify.Models.API; using Certify.Models.Config; using Certify.Models.Providers; using Certify.Providers; @@ -94,7 +95,7 @@ public interface ICertifyManager Task GetDeploymentProviderDefinition(string id, DeploymentTaskConfig config); - Task GetItemLog(string id, int limit = 1000); + Task GetItemLog(string id, int limit = 1000); Task GetServiceLog(string logType, int limit = 10000); diff --git a/src/Certify.Models/API/LogResult.cs b/src/Certify.Models/API/LogResult.cs new file mode 100644 index 000000000..173c95220 --- /dev/null +++ b/src/Certify.Models/API/LogResult.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Certify.CertificateAuthorities.Definitions; + +namespace Certify.Models.API +{ + public class LogItem + { + public DateTime? EventDate { get; set; } + public string LogLevel { get; set; } = string.Empty; + public string Message { get; set; } = string.Empty; + } + public class LogResult + { + public LogItem[] Items { get; set; } = Array.Empty(); + } + + public class LogParser + { + public static LogItem[] Parse(string[] items) + { + + var output = new List(); + + var logLevelTrim = "] '".ToCharArray(); + var itemSplitChars = "[]".ToCharArray(); + + LogItem? unclosedItem = null; + LogItem? lastItem = null; + + foreach (var item in items) + { + var parts = item.Trim().Split(itemSplitChars); + if (parts.Length >= 3 && DateTime.TryParse($"{parts[0]}", out var eventDate)) + { + if (unclosedItem != null) + { + output.Add(unclosedItem); + unclosedItem = null; + } + + lastItem = new LogItem { EventDate = eventDate, LogLevel = parts[1].Trim(logLevelTrim), Message = item.Substring(item.IndexOf(']') + 1) }; + output.Add(lastItem); + + } + else + { + // line is probably a continuation + if (lastItem != null) + { + output.Remove(lastItem); // remove so we can re-add the continuation + lastItem.Message += $"\n{item}"; + unclosedItem = lastItem; + } + } + } + + if (unclosedItem != null) + { + if (lastItem != null) + { + output.Remove(lastItem); + } + + output.Add(unclosedItem); + } + + return output.ToArray(); + } + } +} diff --git a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj index 43ef7e789..fb0a08ad4 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj +++ b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj @@ -27,5 +27,9 @@ + + + + diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs index 33f75a9df..70b675e9b 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs @@ -80,7 +80,7 @@ public async Task Download(string managedCertId, string format, s [HttpGet] [Route("{managedCertId}/log")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] - [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(string))] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(LogResult))] public async Task DownloadLog(string managedCertId, int maxLines = 1000) { var managedCert = await _client.GetManagedCertificate(managedCertId); @@ -96,50 +96,9 @@ public async Task DownloadLog(string managedCertId, int maxLines } var log = await _client.GetItemLog(managedCertId, maxLines); - var logByteArrays = log.Select(l => System.Text.Encoding.UTF8.GetBytes(l + "\n")).ToArray(); - - // combine log lines to one byte array - - var bytes = new byte[logByteArrays.Sum(a => a.Length)]; - var offset = 0; - foreach (var array in logByteArrays) - { - System.Buffer.BlockCopy(array, 0, bytes, offset, array.Length); - offset += array.Length; - } - - return new FileContentResult(bytes, "text/plain") { FileDownloadName = "log.txt" }; - - } - - /// - /// Download text log for the given managed certificate - /// - /// - /// - /// Log file in text format - [HttpGet] - [Route("{managedCertId}/log/text")] - [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] - [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(string))] - public async Task DownloadLogAsText(string managedCertId, int maxLines = 1000) - { - var managedCert = await _client.GetManagedCertificate(managedCertId); - - if (managedCert == null) - { - return new NotFoundResult(); - } - - if (maxLines > 1000) - { - maxLines = 1000; - } - - var log = await _client.GetItemLog(managedCertId, maxLines); - - return new OkObjectResult(string.Join("\n", log)); + + return new OkObjectResult(new LogResult { Items = log }); } /// @@ -174,10 +133,11 @@ public async Task GetManagedCertificates(string keyword, int? pag HasCertificate = !string.IsNullOrEmpty(i.CertificatePath) }).OrderBy(a => a.Title); - var result = new ManagedCertificateSummaryResult { - Results = list, - TotalResults = managedCertResult.TotalResults, - PageIndex = page ?? 0, + var result = new ManagedCertificateSummaryResult + { + Results = list, + TotalResults = managedCertResult.TotalResults, + PageIndex = page ?? 0, PageSize = pageSize ?? list.Count() }; diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/ManagedCertificateController.cs b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/ManagedCertificateController.cs index 8c891b901..2a9a656a3 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/ManagedCertificateController.cs +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/ManagedCertificateController.cs @@ -6,6 +6,7 @@ using Certify.Config; using Certify.Management; using Certify.Models; +using Certify.Models.API; using Certify.Models.Config; using Certify.Models.Utils; using Microsoft.AspNetCore.Mvc; @@ -217,7 +218,7 @@ public RequestProgressState CheckCertificateRequest(string managedItemId) } [HttpGet, Route("log/{managedItemId}/{limit}")] - public async Task GetLog(string managedItemId, int limit) + public async Task GetLog(string managedItemId, int limit) { DebugLog(); return await _certifyManager.GetItemLog(managedItemId, limit); diff --git a/src/Certify.Service/Controllers/ManagedCertificateController.cs b/src/Certify.Service/Controllers/ManagedCertificateController.cs index 04b9170b0..db5492737 100644 --- a/src/Certify.Service/Controllers/ManagedCertificateController.cs +++ b/src/Certify.Service/Controllers/ManagedCertificateController.cs @@ -7,6 +7,7 @@ using Certify.Config; using Certify.Management; using Certify.Models; +using Certify.Models.API; using Certify.Models.Config; using Certify.Models.Utils; using Serilog; @@ -208,7 +209,7 @@ public RequestProgressState CheckCertificateRequest(string managedItemId) } [HttpGet, Route("log/{managedItemId}/{limit}")] - public async Task GetLog(string managedItemId, int limit) + public async Task GetLog(string managedItemId, int limit) { DebugLog(); return await _certifyManager.GetItemLog(managedItemId, limit); diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/MiscTests.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/MiscTests.cs index a74dbe87b..205121e47 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/MiscTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/MiscTests.cs @@ -1,4 +1,5 @@ -using System; +using Certify.Models.API; +using System; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -35,6 +36,25 @@ public void TestNullOrBlankCoalesce() Assert.AreEqual(result, null); } + [TestMethod, Description("Test log parser using array of strings")] + public void TestLogParser() + { + var testLog = new string[] + { + "2023-06-14 13:00:30.480 +08:00 [WRN] ARI Update Renewal Info Failed[MGAwDQYJYIZIAWUDBAIBBQAEIDfbgj - 5Rkkn0NG7u0eFv_M1omHdEwY_mIQn6QxbuJ68BCA9ROYZMeqCkxyMzaMePORi17Gc9xSbp8XkoE1Ub0IPrwILBm8t23CUKQnarrc] Fail to load resource from 'https://acme-staging-v02.api.letsencrypt.org/draft-ietf-acme-ari-01/renewalInfo/'." , + "urn:ietf:params:acme: error: malformed: Certificate not found" , + "2023-06-14 13:01:11.139 +08:00 [INF] Performing Certificate Request: SporkDemo[zerossl][2390d803 - e036 - 4bf5 - 8fa5 - 590497392c35: 7]" + }; + + var items = LogParser.Parse(testLog); + + Assert.AreEqual(2, items.Length); + + Assert.AreEqual("WRN", items[0].LogLevel); + Assert.AreEqual("INF", items[1].LogLevel); + + } + [TestMethod, Description("Test ntp check")] public async Task TestNtp() { diff --git a/src/Certify.UI.Shared/Controls/ManagedCertificate/StatusInfo.xaml.cs b/src/Certify.UI.Shared/Controls/ManagedCertificate/StatusInfo.xaml.cs index b72577130..47bea48a0 100644 --- a/src/Certify.UI.Shared/Controls/ManagedCertificate/StatusInfo.xaml.cs +++ b/src/Certify.UI.Shared/Controls/ManagedCertificate/StatusInfo.xaml.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Media; @@ -98,7 +99,7 @@ private async void OpenLogFile_Click(object sender, System.Windows.RoutedEventAr var log = await AppViewModel.GetItemLog(ItemViewModel.SelectedItem.Id, 1000); var tempPath = System.IO.Path.GetTempFileName() + ".txt"; - System.IO.File.WriteAllLines(tempPath, log); + System.IO.File.WriteAllLines(tempPath, log.Select(i=>i.ToString())); _tempLogFilePath = tempPath; try diff --git a/src/Certify.UI.Shared/ViewModel/AppViewModel/AppViewModel.ManagedCerticates.cs b/src/Certify.UI.Shared/ViewModel/AppViewModel/AppViewModel.ManagedCerticates.cs index f72eb274a..17108b0c6 100644 --- a/src/Certify.UI.Shared/ViewModel/AppViewModel/AppViewModel.ManagedCerticates.cs +++ b/src/Certify.UI.Shared/ViewModel/AppViewModel/AppViewModel.ManagedCerticates.cs @@ -7,6 +7,7 @@ using System.Windows.Input; using Certify.Locales; using Certify.Models; +using Certify.Models.API; using Certify.UI.Shared; using PropertyChanged; @@ -485,7 +486,7 @@ internal async Task ReapplyCertificateBindings(string /// /// /// - public async Task GetItemLog(string id, int limit) + public async Task GetItemLog(string id, int limit) { var result = await _certifyClient.GetItemLog(id, limit); return result; From 95c44e85a13b06ef6d039bdd7a5840a2da6aea10 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Fri, 4 Aug 2023 16:08:43 +0800 Subject: [PATCH 027/328] Core: implement page results --- .../CertifyManager/CertifyManager.ManagedCertificates.cs | 4 +--- src/Certify.Models/API/ManagedCertificateSummary.cs | 2 +- src/Certify.Models/Config/ManagedCertificate.cs | 4 ++-- src/Certify.Models/Providers/IManagedItemManager.cs | 1 + src/Certify.UI.Shared/Controls/ManagedCertificates.xaml.cs | 2 +- 5 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagedCertificates.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagedCertificates.cs index 088fc4a65..bf3f41218 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagedCertificates.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagedCertificates.cs @@ -82,11 +82,9 @@ public async Task GetManagedCertificateResults(M var list = await _itemManager.Find(filter); if (filter.PageSize > 0) { - // TODO: implement count on provider directly filter.PageSize = null; filter.PageIndex = null; - var all = await _itemManager.Find(filter); - result.TotalResults = all.Count; + result.TotalResults = await _itemManager.CountAll(filter); } result.Results = list; diff --git a/src/Certify.Models/API/ManagedCertificateSummary.cs b/src/Certify.Models/API/ManagedCertificateSummary.cs index 8ce588047..7c2c09b55 100644 --- a/src/Certify.Models/API/ManagedCertificateSummary.cs +++ b/src/Certify.Models/API/ManagedCertificateSummary.cs @@ -57,7 +57,7 @@ public class ManagedCertificateSummary public class ManagedCertificateSummaryResult { public IEnumerable Results { get; set; } = new List(); - public int TotalResults { get; set; } + public long TotalResults { get; set; } public int PageIndex { get; set; } public int PageSize { get; set; } } diff --git a/src/Certify.Models/Config/ManagedCertificate.cs b/src/Certify.Models/Config/ManagedCertificate.cs index 3798f19af..fda9d6f33 100644 --- a/src/Certify.Models/Config/ManagedCertificate.cs +++ b/src/Certify.Models/Config/ManagedCertificate.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; @@ -119,7 +119,7 @@ public class ManagedCertificateSearchResult /// /// Total results available /// - public int TotalResults { get; set; } + public long TotalResults { get; set; } } public class ManagedCertificate : BindableBase diff --git a/src/Certify.Models/Providers/IManagedItemManager.cs b/src/Certify.Models/Providers/IManagedItemManager.cs index 00cf45809..9fe685473 100644 --- a/src/Certify.Models/Providers/IManagedItemManager.cs +++ b/src/Certify.Models/Providers/IManagedItemManager.cs @@ -14,6 +14,7 @@ public interface IManagedItemStore Task DeleteByName(string nameStartsWith); Task GetById(string siteId); Task> Find(ManagedCertificateFilter filter); + Task CountAll(ManagedCertificateFilter filter); Task Update(ManagedCertificate managedCertificate); Task PerformMaintenance(); diff --git a/src/Certify.UI.Shared/Controls/ManagedCertificates.xaml.cs b/src/Certify.UI.Shared/Controls/ManagedCertificates.xaml.cs index 50caaffc4..e7b023501 100644 --- a/src/Certify.UI.Shared/Controls/ManagedCertificates.xaml.cs +++ b/src/Certify.UI.Shared/Controls/ManagedCertificates.xaml.cs @@ -82,7 +82,7 @@ private void SetFilter() //sort by name ascending CollectionViewSource.GetDefaultView(_appViewModel.ManagedCertificates).SortDescriptions.Clear(); - + if (_sortOrder == "NameAsc") { CollectionViewSource.GetDefaultView(_appViewModel.ManagedCertificates).SortDescriptions.Add( From 656e09cb82c45ec7773c73e35b31b0764344cfb8 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Fri, 4 Aug 2023 16:40:56 +0800 Subject: [PATCH 028/328] Update Db tests for item count --- .../ManagedItemDataStoreTests.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Certify.Tests/Certify.Core.Tests.Integration/ManagedItemDataStoreTests.cs b/src/Certify.Tests/Certify.Core.Tests.Integration/ManagedItemDataStoreTests.cs index 2bde078e2..7f8c90b22 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Integration/ManagedItemDataStoreTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Integration/ManagedItemDataStoreTests.cs @@ -101,9 +101,13 @@ public async Task TestLoadManagedCertificates(string storeType = null) try { var managedCertificate = await itemManager.Update(testCert); + var filter = new ManagedCertificateFilter { MaxResults = 10 }; + var managedCertificates = await itemManager.Find(filter); - var managedCertificates = await itemManager.Find(new ManagedCertificateFilter { MaxResults = 10 }); Assert.IsTrue(managedCertificates.Count > 0); + + var total = await itemManager.CountAll(filter); + Assert.IsTrue(total > 0); } finally { From 13b6531713186eeb9ac4543dda0560a80abff698 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Mon, 7 Aug 2023 13:52:31 +0800 Subject: [PATCH 029/328] Package updates Package updates Package updates --- src/Certify.CLI/Certify.CLI.csproj | 2 +- src/Certify.Client/Certify.Client.csproj | 6 +++--- src/Certify.Core/Certify.Core.csproj | 16 ++++++++++++++-- src/Certify.Models/Certify.Models.csproj | 2 +- .../Anvil/Certify.Providers.ACME.Anvil.csproj | 2 +- .../DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj | 2 +- .../DNS/Azure/Plugin.DNS.Azure.csproj | 6 +++--- .../Certify.Server.Api.Public.Tests.csproj | 4 ++-- .../Certify.Server.Api.Public.csproj | 4 ++-- .../Certify.Server.Core.csproj | 10 +++++----- .../Certify.Service.Worker.csproj | 10 +++++----- src/Certify.Service/App.config | 2 +- src/Certify.Service/Certify.Service.csproj | 4 ++-- .../Certify.Shared.Extensions.csproj | 1 - src/Certify.Shared/Certify.Shared.Core.csproj | 2 +- .../Certify.Core.Tests.Integration.csproj | 2 +- .../Certify.Core.Tests.Unit.csproj | 2 +- .../Certify.Service.Tests.Integration.csproj | 2 +- .../Certify.UI.Tests.Integration.csproj | 2 +- src/Certify.UI.Shared/Certify.UI.Shared.csproj | 2 +- src/Certify.UI/Certify.UI.csproj | 4 ++-- 21 files changed, 49 insertions(+), 38 deletions(-) diff --git a/src/Certify.CLI/Certify.CLI.csproj b/src/Certify.CLI/Certify.CLI.csproj index b2a6714ee..9228a8a5a 100644 --- a/src/Certify.CLI/Certify.CLI.csproj +++ b/src/Certify.CLI/Certify.CLI.csproj @@ -1,6 +1,6 @@  - net462 + net462;net8.0 Debug;Release;Debug;Release Certify Exe diff --git a/src/Certify.Client/Certify.Client.csproj b/src/Certify.Client/Certify.Client.csproj index 1387da6e6..7511ef8b5 100644 --- a/src/Certify.Client/Certify.Client.csproj +++ b/src/Certify.Client/Certify.Client.csproj @@ -16,9 +16,9 @@ - - - + + + diff --git a/src/Certify.Core/Certify.Core.csproj b/src/Certify.Core/Certify.Core.csproj index 144ad0de9..8a4bea2cb 100644 --- a/src/Certify.Core/Certify.Core.csproj +++ b/src/Certify.Core/Certify.Core.csproj @@ -1,6 +1,6 @@ - + - net462;netstandard2.0;netstandard2.1 + net462;net8.0 Debug;Release; AnyCPU @@ -70,6 +70,18 @@ PackageReference + + 1701;1702;CA1068 + + + 1701;1702;CA1068 + + + 1701;1702;CA1068 + + + 1701;1702;CA1068 + diff --git a/src/Certify.Models/Certify.Models.csproj b/src/Certify.Models/Certify.Models.csproj index 0e73dcb9d..a407227fe 100644 --- a/src/Certify.Models/Certify.Models.csproj +++ b/src/Certify.Models/Certify.Models.csproj @@ -21,7 +21,7 @@ all runtime; build; native; contentfiles; analyzers - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Certify.Providers/ACME/Anvil/Certify.Providers.ACME.Anvil.csproj b/src/Certify.Providers/ACME/Anvil/Certify.Providers.ACME.Anvil.csproj index 9200167f1..de8e5d824 100644 --- a/src/Certify.Providers/ACME/Anvil/Certify.Providers.ACME.Anvil.csproj +++ b/src/Certify.Providers/ACME/Anvil/Certify.Providers.ACME.Anvil.csproj @@ -1,7 +1,7 @@  - netstandard2.0;netstandard2.1 + netstandard2.0;net8.0 AnyCPU Certify.Providers.Acme.Anvil Certify.Providers.Acme.Anvil diff --git a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj index f9fec9c23..42b556378 100644 --- a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj +++ b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj b/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj index b8bed8208..baa13e875 100644 --- a/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj +++ b/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj @@ -7,10 +7,10 @@ - - + + - + diff --git a/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj b/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj index 77bd246fa..e30bac79b 100644 --- a/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj +++ b/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj @@ -25,8 +25,8 @@ - - + + diff --git a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj index fb0a08ad4..fff5aa804 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj +++ b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj @@ -15,10 +15,10 @@ - + - + diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj index fc310df3c..ea89f7285 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj @@ -9,12 +9,12 @@ - - - - + + + + - + diff --git a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Certify.Service.Worker.csproj b/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Certify.Service.Worker.csproj index 4cd27752d..578719047 100644 --- a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Certify.Service.Worker.csproj +++ b/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Certify.Service.Worker.csproj @@ -1,4 +1,4 @@ - + net8.0 dotnet-Certify.Service.Worker-347A036F-C1EA-4D32-A163-DCB38C3CA53E @@ -11,10 +11,10 @@ - - - - + + + + diff --git a/src/Certify.Service/App.config b/src/Certify.Service/App.config index 1222abb07..0dbd94a9f 100644 --- a/src/Certify.Service/App.config +++ b/src/Certify.Service/App.config @@ -14,7 +14,7 @@ - + diff --git a/src/Certify.Service/Certify.Service.csproj b/src/Certify.Service/Certify.Service.csproj index 3f487ed0a..5bef08190 100644 --- a/src/Certify.Service/Certify.Service.csproj +++ b/src/Certify.Service/Certify.Service.csproj @@ -54,7 +54,7 @@ - + @@ -64,7 +64,7 @@ - + diff --git a/src/Certify.Shared.Extensions/Certify.Shared.Extensions.csproj b/src/Certify.Shared.Extensions/Certify.Shared.Extensions.csproj index f4704487d..b33b24a2c 100644 --- a/src/Certify.Shared.Extensions/Certify.Shared.Extensions.csproj +++ b/src/Certify.Shared.Extensions/Certify.Shared.Extensions.csproj @@ -27,7 +27,6 @@ - diff --git a/src/Certify.Shared/Certify.Shared.Core.csproj b/src/Certify.Shared/Certify.Shared.Core.csproj index a05ae7d49..936ec0190 100644 --- a/src/Certify.Shared/Certify.Shared.Core.csproj +++ b/src/Certify.Shared/Certify.Shared.Core.csproj @@ -12,7 +12,7 @@ - + diff --git a/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj b/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj index d5a730c52..8f7b63630 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj @@ -80,7 +80,7 @@ - + diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj index 7e2b1e2ae..47e6d411f 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj @@ -131,7 +131,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj b/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj index f6e22b348..cb8c88d4a 100644 --- a/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj @@ -75,7 +75,7 @@ - + diff --git a/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj b/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj index facbedb96..4b80211c3 100644 --- a/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj @@ -69,7 +69,7 @@ - + diff --git a/src/Certify.UI.Shared/Certify.UI.Shared.csproj b/src/Certify.UI.Shared/Certify.UI.Shared.csproj index 7597ff47e..d5d62e6ad 100644 --- a/src/Certify.UI.Shared/Certify.UI.Shared.csproj +++ b/src/Certify.UI.Shared/Certify.UI.Shared.csproj @@ -39,7 +39,7 @@ - + diff --git a/src/Certify.UI/Certify.UI.csproj b/src/Certify.UI/Certify.UI.csproj index 3afcb0ac0..c759b4786 100644 --- a/src/Certify.UI/Certify.UI.csproj +++ b/src/Certify.UI/Certify.UI.csproj @@ -1,4 +1,4 @@ - + @@ -177,7 +177,7 @@ all - 2.12.0 + 3.0.1 7.0.2 From 8ad6aa321e4288d0b6224312cc7e9d745bb2ea67 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 17 Aug 2023 08:31:06 +0800 Subject: [PATCH 030/328] Update build version --- Directory.Build.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index e37c0efed..ea837cc5d 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -4,11 +4,11 @@ 6.1.0 Webprofusion Pty Ltd Webprofusion Pty Ltd - Certify Community Edition [via github] + Certify The Web - Certify SSL Manager https://certifytheweb.com https://github.com/webprofusion/certify false true - portable + full From 98ab7c83a9c5a56969ac3d511fb8a5f99cd93487 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 17 Aug 2023 08:32:20 +0800 Subject: [PATCH 031/328] Move job timers into main CertifyManager instead of firing from service --- .../CertifyManager.Maintenance.cs | 24 +++++++++++++++++ .../CertifyManager/CertifyManager.cs | 27 +++---------------- src/Certify.Service/APIHost.cs | 2 +- 3 files changed, 29 insertions(+), 24 deletions(-) diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.Maintenance.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.Maintenance.cs index d9b618382..efd5cfe43 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.Maintenance.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.Maintenance.cs @@ -37,6 +37,30 @@ private async Task UpgradeSettings() } } + /// + /// Upgrade/migrate settings from previous version if applicable + /// + /// + private async Task UpgradeSettings() + { + var systemVersion = Util.GetAppVersion().ToString(); + var previousVersion = CoreAppSettings.Current.CurrentServiceVersion; + + if (CoreAppSettings.Current.CurrentServiceVersion != systemVersion) + { + _tc?.TrackEvent("ServiceUpgrade", new Dictionary { + { "previousVersion", previousVersion }, + { "currentVersion", systemVersion } + }); + + // service has been updated, run any required migrations + await PerformServiceUpgrades(); + + CoreAppSettings.Current.CurrentServiceVersion = systemVersion; + SettingsManager.SaveAppSettings(); + } + } + /// /// When called, perform daily cache cleanup, cert cleanup, diagnostics and maintenance /// diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.cs index 9812d4128..6f1cbcd68 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; @@ -100,7 +100,6 @@ public partial class CertifyManager : ICertifyManager, IDisposable /// private Shared.ServiceConfig _serverConfig; - private System.Timers.Timer _heartbeatTimer; private System.Timers.Timer _frequentTimer; private System.Timers.Timer _hourlyTimer; private System.Timers.Timer _dailyTimer; @@ -195,21 +194,17 @@ public async Task Init() SetupJobs(); - await UpgradeSettings(); - _serviceLog?.Information("Certify Manager Started"); } + await UpgradeSettings(); + } + /// /// Setup the continuous job tasks for renewals and maintenance /// private void SetupJobs() { - // 60 second job timer (reporting etc) - _heartbeatTimer = new System.Timers.Timer(60 * 1000); // every n seconds - _heartbeatTimer.Elapsed += _heartbeatTimer_Elapsed; - _heartbeatTimer.Start(); - // 5 minute job timer (maintenance etc) _frequentTimer = new System.Timers.Timer(5 * 60 * 1000); // every 5 minutes _frequentTimer.Elapsed += _frequentTimer_Elapsed; @@ -234,20 +229,6 @@ private async void _dailyTimer_Elapsed(object sender, System.Timers.ElapsedEvent private async void _hourlyTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { await PerformCertificateMaintenanceTasks(); - - try - { - GC.Collect(GC.MaxGeneration, GCCollectionMode.Default); - } - catch - { - // failed to perform garbage collection, ignore. - } - } - - private async void _heartbeatTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) - { - } private async void _frequentTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) diff --git a/src/Certify.Service/APIHost.cs b/src/Certify.Service/APIHost.cs index d43b67f78..8cca98136 100644 --- a/src/Certify.Service/APIHost.cs +++ b/src/Certify.Service/APIHost.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; using System.Net; using System.Net.Http; using System.Net.Http.Formatting; From 1b40e513e9f35b8af5c433e63ece8c14f8813fc8 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 17 Aug 2023 08:32:48 +0800 Subject: [PATCH 032/328] Worker service updates --- .../Certify.Service.Worker/Program.cs | 169 +++++++++--------- .../Certify.Service.Worker/Worker.cs | 29 ++- .../Certify.Service.Worker/readme.md | 29 ++- 3 files changed, 141 insertions(+), 86 deletions(-) diff --git a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Program.cs b/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Program.cs index cb5ec853b..b1169602b 100644 --- a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Program.cs +++ b/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Program.cs @@ -39,114 +39,123 @@ public static IHostBuilder CreateHostBuilder(string[] args) logging.ClearProviders(); logging.AddConsole(); - }) - .UseSystemd() - .ConfigureServices((hostContext, services) => + }); + + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + builder.UseSystemd(); + } + + builder.ConfigureServices((hostContext, services) => + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - services.AddWindowsService(options => - options.ServiceName = "Certify Certificate Manager Background Service" + services.AddWindowsService(options => + options.ServiceName = "Certify Certificate Manager Background Service" - ); - } + ); + } - services.AddHostedService(); - }) - .ConfigureWebHostDefaults(webBuilder => + services.AddHostedService(); + }) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.ConfigureKestrel(serverOptions => { - webBuilder.ConfigureKestrel(serverOptions => + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { serverOptions.UseSystemd(); - // configure https listener, cert path and pwd can come either from an environment variable, usersecrets or appsettings. - // configuration precedence is secrets first, https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-5.0#default - var configuration = (IConfiguration)serverOptions.ApplicationServices.GetService(typeof(IConfiguration)); + } + + // configure https listener, cert path and pwd can come either from an environment variable, usersecrets or appsettings. + // configuration precedence is secrets first, https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-5.0#default + var configuration = (IConfiguration)serverOptions.ApplicationServices.GetService(typeof(IConfiguration)); - var useHttps = bool.Parse(configuration["API:Service:UseHttps"]); + var useHttps = bool.Parse(configuration["API:Service:UseHttps"]); - // default IP to localhost then specify from configuration - var ipSelection = configuration["API:Service:BindingIP"]; - var ipBinding = IPAddress.Loopback; + // default IP to localhost then specify from configuration + var ipSelection = configuration["API:Service:BindingIP"]; + var ipBinding = IPAddress.Loopback; - if (ipSelection != null) + if (ipSelection != null) + { + if (ipSelection.ToLower() == "loopback") { - if (ipSelection.ToLower() == "loopback") - { - ipBinding = IPAddress.Loopback; - } - else if (ipSelection.ToLower() == "any") - { - ipBinding = IPAddress.Any; - } - else - { - ipBinding = IPAddress.Parse(ipSelection); - } + ipBinding = IPAddress.Loopback; } - - if (useHttps) + else if (ipSelection.ToLower() == "any") + { + ipBinding = IPAddress.Any; + } + else { + ipBinding = IPAddress.Parse(ipSelection); + } + } - var certPassword = Environment.GetEnvironmentVariable("ASPNETCORE_Kestrel__Certificates__Development__Password"); - var certPath = Environment.GetEnvironmentVariable("ASPNETCORE_Kestrel__Certificates__Development__Path"); + if (useHttps) + { - // if not yet defined load config from usersecrets (development env only) or appsettings - if (certPassword == null) - { - certPassword = configuration["Kestrel:Certificates:Default:Password"]; - } + var certPassword = Environment.GetEnvironmentVariable("ASPNETCORE_Kestrel__Certificates__Development__Password"); + var certPath = Environment.GetEnvironmentVariable("ASPNETCORE_Kestrel__Certificates__Development__Path"); - if (certPath == null) - { - certPath = configuration["Kestrel:Certificates:Default:Path"]; - } + // if not yet defined load config from usersecrets (development env only) or appsettings + if (certPassword == null) + { + certPassword = configuration["Kestrel:Certificates:Default:Password"]; + } - try - { - var certificate = new X509Certificate2(certPath, certPassword); + if (certPath == null) + { + certPath = configuration["Kestrel:Certificates:Default:Path"]; + } - // if password is wrong at this stage the attempts to use the cert will results in SSL Protocol Error + try + { + var certificate = new X509Certificate2(certPath, certPassword); - var httpsConnectionAdapterOptions = new HttpsConnectionAdapterOptions() - { - ClientCertificateMode = ClientCertificateMode.NoCertificate, - SslProtocols = System.Security.Authentication.SslProtocols.Tls12 | System.Security.Authentication.SslProtocols.Tls13, - ServerCertificate = certificate, - }; + // if password is wrong at this stage the attempts to use the cert will results in SSL Protocol Error - var httpsPort = Convert.ToInt32(configuration["API:Service:HttpsPort"]); + var httpsConnectionAdapterOptions = new HttpsConnectionAdapterOptions() + { + ClientCertificateMode = ClientCertificateMode.NoCertificate, + SslProtocols = System.Security.Authentication.SslProtocols.Tls12 | System.Security.Authentication.SslProtocols.Tls13, + ServerCertificate = certificate, + }; - serverOptions.Listen(new System.Net.IPEndPoint(ipBinding, httpsPort), listenOptions => - { - listenOptions.UseHttps(httpsConnectionAdapterOptions); - }); + var httpsPort = Convert.ToInt32(configuration["API:Service:HttpsPort"]); - } - catch (Exception exp) + serverOptions.Listen(new System.Net.IPEndPoint(ipBinding, httpsPort), listenOptions => { - // TODO: there is no logger yet, need to report this failure to main log once the log exists - System.Diagnostics.Debug.WriteLine("Failed to load PFX certificate for application. Check service certificate config." + exp.ToString()); - } + listenOptions.UseHttps(httpsConnectionAdapterOptions); + }); + } - else + catch (Exception exp) { - var httpPort = Convert.ToInt32(configuration["API:Service:HttpPort"]); - - serverOptions.Listen(new System.Net.IPEndPoint(ipBinding, httpPort), listenOptions => - { - }); + // TODO: there is no logger yet, need to report this failure to main log once the log exists + System.Diagnostics.Debug.WriteLine("Failed to load PFX certificate for application. Check service certificate config." + exp.ToString()); } - }); - - webBuilder.ConfigureLogging(logging => - { - logging.AddFilter("Microsoft.AspNetCore.SignalR", LogLevel.Debug); - logging.AddFilter("Microsoft.AspNetCore.Http.Connections", LogLevel.Debug); - }); + } + else + { + var httpPort = Convert.ToInt32(configuration["API:Service:HttpPort"]); - webBuilder.UseStartup(); + serverOptions.Listen(new System.Net.IPEndPoint(ipBinding, httpPort), listenOptions => + { + }); + } }); + webBuilder.ConfigureLogging(logging => + { + logging.AddFilter("Microsoft.AspNetCore.SignalR", LogLevel.Debug); + logging.AddFilter("Microsoft.AspNetCore.Http.Connections", LogLevel.Debug); + }); + + webBuilder.UseStartup(); + }); + return builder; } } diff --git a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Worker.cs b/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Worker.cs index 6fd55ebef..4f05e30ab 100644 --- a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Worker.cs +++ b/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Worker.cs @@ -17,10 +17,33 @@ public Worker(ILogger logger) protected override async Task ExecuteAsync(CancellationToken stoppingToken) { - while (!stoppingToken.IsCancellationRequested) + // https://learn.microsoft.com/en-us/dotnet/core/extensions/windows-service?pivots=dotnet-7-0 + try { - _logger.LogInformation("Certify Service Worker heartbeat: {time}", DateTimeOffset.Now); - await Task.Delay(1000 * 60 * 60, stoppingToken); + while (!stoppingToken.IsCancellationRequested) + { + _logger.LogInformation("Certify Service Worker heartbeat: {time}", DateTimeOffset.Now); + await Task.Delay(1000 * 60 * 60, stoppingToken); + } + } + catch (TaskCanceledException) + { + // When the stopping token is canceled, for example, a call made from services.msc, + // we shouldn't exit with a non-zero exit code. In other words, this is expected... + } + catch (Exception ex) + { + _logger.LogError(ex, "{Message}", ex.Message); + + // Terminates this process and returns an exit code to the operating system. + // This is required to avoid the 'BackgroundServiceExceptionBehavior', which + // performs one of two scenarios: + // 1. When set to "Ignore": will do nothing at all, errors cause zombie services. + // 2. When set to "StopHost": will cleanly stop the host, and log errors. + // + // In order for the Windows Service Management system to leverage configured + // recovery options, we need to terminate the process with a non-zero exit code. + Environment.Exit(1); } } } diff --git a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/readme.md b/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/readme.md index 7546905d5..0d33aeb8a 100644 --- a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/readme.md +++ b/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/readme.md @@ -30,13 +30,13 @@ WSL debug Linux Install ------------ -apt-get certifytheweb +`apt-get certifytheweb` -sudo mkdir /opt/certifytheweb +`sudo mkdir /opt/certifytheweb` Systemd ----------- - +``` [Unit] Description=Certify The Web @@ -50,3 +50,26 @@ PrivateTmp=true [Install] WantedBy=multi-user.target +``` + +Windows Service +----------------- + +` sc create "Certify Certificate Manager [dotnet]" binpath="C:\Work\GIT\certify_dev\certify\src\Certify.Server\Certify.Service.Worker\Certify.Service.Worker\bin\Debug\net8.0\Certify.Service.Worker.exe"` + + +Publishing: + +- + +Windows: + +dotnet publish -c Release -r win-x64 --self-contained true + +Linux: + +dotnet publish -c Release -r linux-x64 --self-contained true + +Single File: + +dotnet publish -c Release -r win-x64 --self-contained true -p:PublishSingleFile=true From 1abd3a154449e25e648f86018dd5cf162747818a Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 17 Aug 2023 12:32:18 +0800 Subject: [PATCH 033/328] Import/Export: optionally skip deployment --- .../CertifyManager/CertifyManager.cs | 60 +++++++ src/Certify.Models/Config/Migration.cs | 3 +- .../Windows/ImportExport.xaml | 147 +++++++++++------- .../Windows/ImportExport.xaml.cs | 5 + 4 files changed, 156 insertions(+), 59 deletions(-) diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.cs index 6f1cbcd68..0f1405cb9 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.cs @@ -462,6 +462,66 @@ public RequestProgressState GetRequestProgressState(string managedItemId) public void Dispose() => ManagedCertificateLog.DisposeLoggers(); + /// + /// Perform (or preview) an import of settings from another instance + /// + /// + /// + public async Task> PerformImport(ImportRequest importRequest) + { + var migrationManager = new MigrationManager(_itemManager, _credentialsManager, _serverProviders); + + var importResult = await migrationManager.PerformImport(importRequest.Package, importRequest.Settings, importRequest.IsPreviewMode); + + // store and apply certs if we have no errors + + var hasError = false; + if (!importResult.Any(i => i.HasError)) + { + if (importRequest.Settings.IncludeDeployment) + { + + var deploySteps = new List(); + foreach (var m in importRequest.Package.Content.ManagedCertificates) + { + var managedCert = await GetManagedCertificate(m.Id); + + if (managedCert != null && !string.IsNullOrEmpty(managedCert.CertificatePath)) + { + var deployResult = await DeployCertificate(managedCert, null, isPreviewOnly: importRequest.IsPreviewMode); + + deploySteps.Add(new ActionStep { Category = "Deployment", HasError = !deployResult.IsSuccess, Key = managedCert.Id, Description = deployResult.Message }); + } + } + + importResult.Add(new ActionStep { Title = "Deployment" + (importRequest.IsPreviewMode ? " [Preview]" : ""), Substeps = deploySteps }); + } + } + else + { + hasError = true; + } + + _tc?.TrackEvent("Import" + (importRequest.IsPreviewMode ? "_Preview" : ""), new Dictionary { + { "hasErrors", hasError.ToString() } + }); + + return importResult; + } + + /// + /// Perform (or preview) and export of settings from this instance + /// + /// + /// + public async Task PerformExport(ExportRequest exportRequest) + { + _tc?.TrackEvent("Export" + (exportRequest.IsPreviewMode ? "_Preview" : "")); + + var migrationManager = new MigrationManager(_itemManager, _credentialsManager, _serverProviders); + return await migrationManager.PerformExport(exportRequest.Filter, exportRequest.Settings, exportRequest.IsPreviewMode); + } + /// /// Get the current service log (per line) /// diff --git a/src/Certify.Models/Config/Migration.cs b/src/Certify.Models/Config/Migration.cs index b2751b87a..9bcfa2045 100644 --- a/src/Certify.Models/Config/Migration.cs +++ b/src/Certify.Models/Config/Migration.cs @@ -82,7 +82,8 @@ public class ExportSettings public class ImportSettings { public string? EncryptionSecret { get; set; } = string.Empty; - public bool OverwriteExisting { get; set; } = false; + public bool OverwriteExisting { get; set; } + public bool IncludeDeployment { get; set; } } public class ExportRequest diff --git a/src/Certify.UI.Shared/Windows/ImportExport.xaml b/src/Certify.UI.Shared/Windows/ImportExport.xaml index e180c83ee..08d1b163b 100644 --- a/src/Certify.UI.Shared/Windows/ImportExport.xaml +++ b/src/Certify.UI.Shared/Windows/ImportExport.xaml @@ -26,72 +26,103 @@ Controls:TextBoxHelper.Watermark="Password" /> - - Export - Export a settings bundle including managed certificate settings, certificate files and encrypted credentials. - - - - Import - Import a settings bundle exported from another instance of the app. - - - + + + + Export a settings bundle including managed certificate settings, certificate files and encrypted credentials. + + + + + + Import a settings bundle exported from another instance of the app. + + + + - - + + + + + + - - + + - + + + - - diff --git a/src/Certify.UI.Shared/Windows/ImportExport.xaml.cs b/src/Certify.UI.Shared/Windows/ImportExport.xaml.cs index e73e58b04..3c4b3ae03 100644 --- a/src/Certify.UI.Shared/Windows/ImportExport.xaml.cs +++ b/src/Certify.UI.Shared/Windows/ImportExport.xaml.cs @@ -68,6 +68,7 @@ private async void Import_Click(object sender, RoutedEventArgs e) } Model.ImportSettings.OverwriteExisting = (OverwriteExisting.IsChecked == true); + Model.ImportSettings.IncludeDeployment = (IncludeDeployment.IsChecked == true); Model.ImportSettings.EncryptionSecret = txtSecret.Password; Model.InProgress = true; @@ -104,6 +105,10 @@ private async void CompleteImport_Click(object sender, RoutedEventArgs e) if (MessageBox.Show("Are you sure you wish to perform the import as shown in the preview? The import cannot be reverted once complete.", "Perform Import?", MessageBoxButton.YesNoCancel) == MessageBoxResult.Yes) { Model.InProgress = true; + + Model.ImportSettings.OverwriteExisting = (OverwriteExisting.IsChecked == true); + Model.ImportSettings.IncludeDeployment = (IncludeDeployment.IsChecked == true); + var results = await MainViewModel.PerformSettingsImport(Model.Package, Model.ImportSettings, false); PrepareImportSummary(false, results); From c099090305cf86060e0f7550185ee6d5cedcde2e Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 17 Aug 2023 12:33:25 +0800 Subject: [PATCH 034/328] Server connection: update UI --- .../AppViewModel/AppViewModel.Connections.cs | 15 +++++++++++++++ .../Windows/EditServerConnection.xaml | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/Certify.UI.Shared/ViewModel/AppViewModel/AppViewModel.Connections.cs b/src/Certify.UI.Shared/ViewModel/AppViewModel/AppViewModel.Connections.cs index 916cf1661..dad39393a 100644 --- a/src/Certify.UI.Shared/ViewModel/AppViewModel/AppViewModel.Connections.cs +++ b/src/Certify.UI.Shared/ViewModel/AppViewModel/AppViewModel.Connections.cs @@ -302,8 +302,23 @@ internal async Task SaveServerConnection(ServerConnection item) serverConnections.Remove(serverConnections.Find(c => c.Id == item.Id)); } + // if item is the default, all other items are no longer the default + if (item.IsDefault) + { + serverConnections + .Where(s => s.Id != item.Id) + .ToList() + .ForEach(s => s.IsDefault = false); + } + serverConnections.Add(item); + // if no default exists, make the first item default + if (!serverConnections.Exists(e => e.IsDefault)) + { + serverConnections.First().IsDefault = true; + } + ServerConnectionManager.Save(Log, serverConnections); return await Task.FromResult(true); diff --git a/src/Certify.UI.Shared/Windows/EditServerConnection.xaml b/src/Certify.UI.Shared/Windows/EditServerConnection.xaml index 50bc3e64d..baac89cd2 100644 --- a/src/Certify.UI.Shared/Windows/EditServerConnection.xaml +++ b/src/Certify.UI.Shared/Windows/EditServerConnection.xaml @@ -88,7 +88,7 @@ Width="120" Margin="0,0,8,0" VerticalAlignment="Top" - Content="Use https" /> + Content="Use Https" /> From fe2bc45a6705db6014851143a3a68cf2538309b4 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 17 Aug 2023 12:33:53 +0800 Subject: [PATCH 035/328] Data Stores: show the current default store --- src/Certify.UI.Shared/Windows/DataStoreConnections.xaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Certify.UI.Shared/Windows/DataStoreConnections.xaml b/src/Certify.UI.Shared/Windows/DataStoreConnections.xaml index b151a7a22..eb929c88f 100644 --- a/src/Certify.UI.Shared/Windows/DataStoreConnections.xaml +++ b/src/Certify.UI.Shared/Windows/DataStoreConnections.xaml @@ -1,4 +1,4 @@ - Date: Thu, 17 Aug 2023 15:28:46 +0800 Subject: [PATCH 036/328] Import: show option to skip deployment --- src/Certify.UI.Shared/Windows/ImportExport.xaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Certify.UI.Shared/Windows/ImportExport.xaml b/src/Certify.UI.Shared/Windows/ImportExport.xaml index 08d1b163b..560db8a5f 100644 --- a/src/Certify.UI.Shared/Windows/ImportExport.xaml +++ b/src/Certify.UI.Shared/Windows/ImportExport.xaml @@ -73,7 +73,7 @@ Content="Include Standard Certificate Storage and Auto Deployment" DockPanel.Dock="Top" IsChecked="True" - Visibility="{Binding IsImportReady, Converter={StaticResource ResourceKey=BoolToVisConverter}}" /> + /> /// - private void UnsetChanged(object obj) + private static void UnsetChanged(object obj) { if (obj is BindableBase bb) { @@ -175,13 +175,13 @@ private void UnsetChanged(object obj) { foreach (var subObj in propertyCollection) { - UnsetChanged(subObj); + BindableBase.UnsetChanged(subObj); } } if (val is BindableBase bbSub) { - UnsetChanged(bbSub); + BindableBase.UnsetChanged(bbSub); } } } @@ -190,7 +190,7 @@ private void UnsetChanged(object obj) { foreach (var subObj in collection) { - UnsetChanged(subObj); + BindableBase.UnsetChanged(subObj); } } } diff --git a/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj b/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj index 8f7b63630..0fe088f09 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj @@ -62,6 +62,18 @@ x64 + + 1701;1702;NU1701 + + + 1701;1702;NU1701 + + + 1701;1702;NU1701 + + + 1701;1702;NU1701 + diff --git a/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj b/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj index 4b80211c3..292b99844 100644 --- a/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj @@ -20,6 +20,7 @@ prompt 4 AnyCPU + 1701;1702;NU1701 true @@ -61,6 +62,7 @@ x64 + 1701;1702;NU1701 @@ -75,11 +77,7 @@ - - - - - + diff --git a/src/Certify.UI.Desktop/Certify.UI.Desktop.csproj b/src/Certify.UI.Desktop/Certify.UI.Desktop.csproj index a3b194749..c76fc4cea 100644 --- a/src/Certify.UI.Desktop/Certify.UI.Desktop.csproj +++ b/src/Certify.UI.Desktop/Certify.UI.Desktop.csproj @@ -12,7 +12,9 @@ - + + NU1701 + diff --git a/src/Certify.UI.Shared/Certify.UI.Shared.csproj b/src/Certify.UI.Shared/Certify.UI.Shared.csproj index d5d62e6ad..00486dc1b 100644 --- a/src/Certify.UI.Shared/Certify.UI.Shared.csproj +++ b/src/Certify.UI.Shared/Certify.UI.Shared.csproj @@ -34,14 +34,18 @@ - + + NU1701 + - + + NU1701 + From 39d9da9e3c7c51b1dfa0c735fed0ae7c2da8b801 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Tue, 22 Aug 2023 13:39:20 +0800 Subject: [PATCH 044/328] Package updates --- src/Certify.Models/Certify.Models.csproj | 2 +- .../DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj | 2 +- .../Certify.Server.Api.Public.Tests.csproj | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Certify.Models/Certify.Models.csproj b/src/Certify.Models/Certify.Models.csproj index a407227fe..f351c5258 100644 --- a/src/Certify.Models/Certify.Models.csproj +++ b/src/Certify.Models/Certify.Models.csproj @@ -22,7 +22,7 @@ runtime; build; native; contentfiles; analyzers - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj index 42b556378..436c1e0a8 100644 --- a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj +++ b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj b/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj index e30bac79b..8024cbd01 100644 --- a/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj +++ b/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj @@ -26,7 +26,7 @@ - + From 518367b0579ab61e07edf851243024d1b4a3bf27 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Tue, 22 Aug 2023 13:39:36 +0800 Subject: [PATCH 045/328] Cleanup --- src/Certify.Core/Certify.Core.csproj | 6 ------ src/Certify.Models/Config/ManagedCertificate.cs | 7 ++++--- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/Certify.Core/Certify.Core.csproj b/src/Certify.Core/Certify.Core.csproj index 8a4bea2cb..00d97a462 100644 --- a/src/Certify.Core/Certify.Core.csproj +++ b/src/Certify.Core/Certify.Core.csproj @@ -99,10 +99,4 @@ - - - CertifyManager.cs - - - \ No newline at end of file diff --git a/src/Certify.Models/Config/ManagedCertificate.cs b/src/Certify.Models/Config/ManagedCertificate.cs index fda9d6f33..9dbd35627 100644 --- a/src/Certify.Models/Config/ManagedCertificate.cs +++ b/src/Certify.Models/Config/ManagedCertificate.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; @@ -785,8 +785,9 @@ public static bool IsDomainOrWildcardMatch(List dnsNames, string? hostna { targetRenewalPercentage = selectedRenewalInterval; - if (targetRenewalPercentage > 100) { targetRenewalPercentage = 100; } - } + var targetRenewalMinutesAfterCertStart = certLifetime.Value.TotalMinutes * (targetRenewalPercentage / 100); + var targetRenewalDate = s.DateStart != null ? s.DateStart.Value.AddMinutes(targetRenewalMinutesAfterCertStart) : s.DateRenewed.Value; + nextRenewalAttemptDate = targetRenewalDate; var targetRenewalMinutesAfterCertStart = certLifetime.Value.TotalMinutes * (targetRenewalPercentage / 100); var targetRenewalDate = s.DateStart != null ? s.DateStart.Value.AddMinutes(targetRenewalMinutesAfterCertStart) : s.DateRenewed.Value; From bfcdb652daa7f69a00580a9b9fe394815e41a1d7 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Sat, 26 Aug 2023 09:58:45 +0800 Subject: [PATCH 046/328] U/Core: implement summary query instead of counting all managed certs in the UI --- src/Certify.Client/CertifyApiClient.cs | 14 +++++++ src/Certify.Client/ICertifyClient.cs | 2 + .../CertifyManager.Maintenance.cs | 2 +- .../CertifyManager.ManagedCertificates.cs | 24 +++++++++++- .../CertifyManager/ICertifyManager.cs | 1 + src/Certify.Models/Reporting/Summary.cs | 21 ++++++++++ .../ManagedCertificateController.cs | 9 ++++- .../ManagedCertificateController.cs | 8 ++++ .../ManagedCertificate/Dashboard.xaml.cs | 38 +++++++------------ .../AppViewModel.ManagedCerticates.cs | 16 +++++++- 10 files changed, 105 insertions(+), 30 deletions(-) create mode 100644 src/Certify.Models/Reporting/Summary.cs diff --git a/src/Certify.Client/CertifyApiClient.cs b/src/Certify.Client/CertifyApiClient.cs index f55c2c9e3..4dcbe9a62 100644 --- a/src/Certify.Client/CertifyApiClient.cs +++ b/src/Certify.Client/CertifyApiClient.cs @@ -11,6 +11,7 @@ using Certify.Models.API; using Certify.Models.Config; using Certify.Models.Utils; +using Certify.Reporting; using Certify.Shared; using Newtonsoft.Json; using Polly; @@ -398,6 +399,19 @@ public async Task GetManagedCertificateSearchRes } } + public async Task GetManagedCertificateSummary(ManagedCertificateFilter filter) + { + var response = await PostAsync("managedcertificates/summary/", filter); + var serializer = new JsonSerializer(); + + using (var sr = new StreamReader(await response.Content.ReadAsStreamAsync())) + using (var reader = new JsonTextReader(sr)) + { + var result = serializer.Deserialize(reader); + return result; + } + } + public async Task GetManagedCertificate(string managedItemId) { var result = await FetchAsync($"managedcertificates/{managedItemId}"); diff --git a/src/Certify.Client/ICertifyClient.cs b/src/Certify.Client/ICertifyClient.cs index 71246761c..4d4e48eb6 100644 --- a/src/Certify.Client/ICertifyClient.cs +++ b/src/Certify.Client/ICertifyClient.cs @@ -5,6 +5,7 @@ using Certify.Models; using Certify.Models.Config; using Certify.Models.Utils; +using Certify.Reporting; using Certify.Shared; namespace Certify.Client @@ -77,6 +78,7 @@ public interface ICertifyInternalApiClient Task> GetManagedCertificates(ManagedCertificateFilter filter); Task GetManagedCertificateSearchResult(ManagedCertificateFilter filter); + Task GetManagedCertificateSummary(ManagedCertificateFilter filter); Task GetManagedCertificate(string managedItemId); diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.Maintenance.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.Maintenance.cs index efd5cfe43..2ba2845ef 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.Maintenance.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.Maintenance.cs @@ -11,7 +11,7 @@ namespace Certify.Management { - public partial class CertifyManager : ICertifyManager, IDisposable + public partial class CertifyManager { /// /// Upgrade/migrate settings from previous version if applicable diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagedCertificates.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagedCertificates.cs index d3b565cec..5944c768f 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagedCertificates.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagedCertificates.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; @@ -7,7 +7,7 @@ using Certify.Models; using Certify.Models.API; using Certify.Models.Providers; -using Certify.Models.Shared; +using Certify.Reporting; namespace Certify.Management { @@ -90,6 +90,26 @@ public async Task GetManagedCertificateResults(M return result; } + + public async Task GetManagedCertificateSummary(ManagedCertificateFilter filter) + { + var ms = await _itemManager.Find(filter); + + var summary = new Summary(); + summary.Total = ms.Count(); + summary.Healthy = ms.Count(c => c.Health == ManagedCertificateHealth.OK); + summary.Error = ms.Count(c => c.Health == ManagedCertificateHealth.Error); + summary.Warning = ms.Count(c => c.Health == ManagedCertificateHealth.Warning); + summary.AwaitingUser = ms.Count(c => c.Health == ManagedCertificateHealth.AwaitingUser); + summary.NoCertificate = ms.Count(c => c.CertificatePath == null); + + // count items with invalid config (e.g. multiple primary domains) + summary.InvalidConfig = ms.Count(c => c.DomainOptions.Count(d => d.IsPrimaryDomain) > 1); + + summary.TotalDomains = ms.Sum(s => s.RequestConfig.SubjectAlternativeNames.Count()); + + return summary; + } /// /// Update the stored details for the given managed certificate and report update to client(s) /// diff --git a/src/Certify.Core/Management/CertifyManager/ICertifyManager.cs b/src/Certify.Core/Management/CertifyManager/ICertifyManager.cs index d94b15501..045292cb0 100644 --- a/src/Certify.Core/Management/CertifyManager/ICertifyManager.cs +++ b/src/Certify.Core/Management/CertifyManager/ICertifyManager.cs @@ -29,6 +29,7 @@ public interface ICertifyManager Task> GetManagedCertificates(ManagedCertificateFilter filter = null); Task GetManagedCertificateResults(ManagedCertificateFilter filter = null); + Task GetManagedCertificateSummary(ManagedCertificateFilter filter = null); Task UpdateManagedCertificate(ManagedCertificate site); diff --git a/src/Certify.Models/Reporting/Summary.cs b/src/Certify.Models/Reporting/Summary.cs new file mode 100644 index 000000000..792289e7d --- /dev/null +++ b/src/Certify.Models/Reporting/Summary.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Certify.Models; + +namespace Certify.Reporting +{ + public class Summary : BindableBase + { + public int Total { get; set; } + public int Healthy { get; set; } + public int Error { get; set; } + public int Warning { get; set; } + public int AwaitingUser { get; set; } + public int InvalidConfig { get; set; } + + public int NoCertificate { get; set; } + + public int TotalDomains { get; set; } + } +} diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/ManagedCertificateController.cs b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/ManagedCertificateController.cs index e26dfa264..c738a18f5 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/ManagedCertificateController.cs +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/ManagedCertificateController.cs @@ -9,7 +9,7 @@ using Certify.Models.API; using Certify.Models.Config; using Certify.Models.Utils; -using Microsoft.AspNetCore.Mvc; +using Certify.Reporting; using Serilog; namespace Certify.Service.Controllers @@ -43,6 +43,13 @@ public async Task GetResults(ManagedCertificateF return await _certifyManager.GetManagedCertificateResults(filter); } + [HttpPost, Route("summary")] + public async Task GetSummary(ManagedCertificateFilter filter) + { + DebugLog(); + return await _certifyManager.GetManagedCertificateSummary(filter); + } + [HttpGet, Route("{id}")] public async Task GetById(string id) { diff --git a/src/Certify.Service/Controllers/ManagedCertificateController.cs b/src/Certify.Service/Controllers/ManagedCertificateController.cs index 47b6fd053..2a55ea676 100644 --- a/src/Certify.Service/Controllers/ManagedCertificateController.cs +++ b/src/Certify.Service/Controllers/ManagedCertificateController.cs @@ -10,6 +10,7 @@ using Certify.Models.API; using Certify.Models.Config; using Certify.Models.Utils; +using Certify.Reporting; using Serilog; namespace Certify.Service.Controllers @@ -42,6 +43,13 @@ public async Task GetResults(ManagedCertificateF return await _certifyManager.GetManagedCertificateResults(filter); } + [HttpPost, Route("summary")] + public async Task GetSummary(ManagedCertificateFilter filter) + { + DebugLog(); + return await _certifyManager.GetManagedCertificateSummary(filter); + } + [HttpGet, Route("{id}")] public async Task GetById(string id) { diff --git a/src/Certify.UI.Shared/Controls/ManagedCertificate/Dashboard.xaml.cs b/src/Certify.UI.Shared/Controls/ManagedCertificate/Dashboard.xaml.cs index 35509a987..322ec8aa1 100644 --- a/src/Certify.UI.Shared/Controls/ManagedCertificate/Dashboard.xaml.cs +++ b/src/Certify.UI.Shared/Controls/ManagedCertificate/Dashboard.xaml.cs @@ -2,8 +2,10 @@ using System.Collections.ObjectModel; using System.ComponentModel; using System.Linq; +using System.Threading.Tasks; using System.Windows.Controls; using Certify.Models; +using Certify.Reporting; using Certify.UI.ViewModel; namespace Certify.UI.Controls.ManagedCertificate @@ -20,22 +22,7 @@ public class DailySummary : BindableBase } - public class SummaryModel : BindableBase - { - public int Total { get; set; } - public int Healthy { get; set; } - public int Error { get; set; } - public int Warning { get; set; } - public int AwaitingUser { get; set; } - public int InvalidConfig { get; set; } - - public int NoCertificate { get; set; } - - public int TotalDomains { get; set; } - - public ObservableCollection DailyRenewals { get; set; } - } - public SummaryModel ViewModel { get; set; } = new SummaryModel(); + public Summary ViewModel { get; set; } = new Summary(); protected ViewModel.AppViewModel _appViewModel => AppViewModel.Current; @@ -57,25 +44,26 @@ private void AppViewModel_PropertyChanged(object sender, PropertyChangedEventArg } } - public void RefreshSummary() + public async Task RefreshSummary() { if (AppViewModel.Current.ManagedCertificates?.Any() == true) { + var summary = await AppViewModel.Current.GetManagedCertificateSummary(); var ms = AppViewModel.Current.ManagedCertificates; - ViewModel.Total = ms.Count(); - ViewModel.Healthy = ms.Count(c => c.Health == ManagedCertificateHealth.OK); - ViewModel.Error = ms.Count(c => c.Health == ManagedCertificateHealth.Error); - ViewModel.Warning = ms.Count(c => c.Health == ManagedCertificateHealth.Warning); - ViewModel.AwaitingUser = ms.Count(c => c.Health == ManagedCertificateHealth.AwaitingUser); - ViewModel.NoCertificate = ms.Count(c => c.CertificatePath == null); + ViewModel.Total = summary.Total; + ViewModel.Healthy = summary.Healthy; + ViewModel.Error = summary.Error; + ViewModel.Warning = summary.Warning; + ViewModel.AwaitingUser = summary.AwaitingUser; + ViewModel.NoCertificate = summary.NoCertificate; // count items with invalid config (e.g. multiple primary domains) - ViewModel.InvalidConfig = ms.Count(c => c.DomainOptions.Count(d => d.IsPrimaryDomain) > 1); + ViewModel.InvalidConfig = summary.InvalidConfig; - ViewModel.TotalDomains = ms.Sum(s => s.RequestConfig.SubjectAlternativeNames.Count()); + ViewModel.TotalDomains = summary.TotalDomains; PanelTotal.Visibility = ViewModel.Total == 0 ? System.Windows.Visibility.Collapsed : System.Windows.Visibility.Visible; PanelHealthy.Visibility = ViewModel.Healthy == 0 ? System.Windows.Visibility.Collapsed : System.Windows.Visibility.Visible; diff --git a/src/Certify.UI.Shared/ViewModel/AppViewModel/AppViewModel.ManagedCerticates.cs b/src/Certify.UI.Shared/ViewModel/AppViewModel/AppViewModel.ManagedCerticates.cs index 29b1516bd..7ffdd1593 100644 --- a/src/Certify.UI.Shared/ViewModel/AppViewModel/AppViewModel.ManagedCerticates.cs +++ b/src/Certify.UI.Shared/ViewModel/AppViewModel/AppViewModel.ManagedCerticates.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Threading; @@ -8,6 +8,7 @@ using Certify.Locales; using Certify.Models; using Certify.Models.API; +using Certify.Reporting; using Certify.UI.Shared; using PropertyChanged; @@ -80,6 +81,11 @@ public ObservableCollection ManagedCertificates } } + public long TotalManagedCertificates + { + get; set; + } + /// /// Cached count of the number of managed certificate (not counting external certificate managers) /// @@ -111,6 +117,14 @@ public virtual async Task RefreshManagedCertificates() var result = await _certifyClient.GetManagedCertificateSearchResult(filter); ManagedCertificates = new ObservableCollection(result.Results); + TotalManagedCertificates = result.TotalResults; + } + + public async Task GetManagedCertificateSummary() + { + var filter = new ManagedCertificateFilter(); + + return await _certifyClient.GetManagedCertificateSummary(filter); } /// From 72d4a62d61f9a426ff7309ccc8f12031fcc83844 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 31 Aug 2023 17:17:42 +0800 Subject: [PATCH 047/328] Cleanup --- .../CertifyManager.ManagedCertificates.cs | 55 +++++++------ .../Challenges/DNS/DnsChallengeHelper.cs | 79 +++++++++---------- src/Certify.Core/Management/RenewalManager.cs | 60 +++++++------- .../ManagedCertificateController.cs | 1 + .../Controls/ManagedCertificates.xaml | 10 ++- 5 files changed, 104 insertions(+), 101 deletions(-) diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagedCertificates.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagedCertificates.cs index 5944c768f..833581cef 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagedCertificates.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagedCertificates.cs @@ -96,7 +96,7 @@ public async Task GetManagedCertificateResults(M var ms = await _itemManager.Find(filter); var summary = new Summary(); - summary.Total = ms.Count(); + summary.Total = ms.Count; summary.Healthy = ms.Count(c => c.Health == ManagedCertificateHealth.OK); summary.Error = ms.Count(c => c.Health == ManagedCertificateHealth.Error); summary.Warning = ms.Count(c => c.Health == ManagedCertificateHealth.Warning); @@ -110,6 +110,7 @@ public async Task GetManagedCertificateResults(M return summary; } + /// /// Update the stored details for the given managed certificate and report update to client(s) /// @@ -188,7 +189,7 @@ private async Task UpdateManagedCertificateStatus(ManagedCertificate managedCert await ReportManagedCertificateStatus(managedCertificate); } - _tc?.TrackEvent("UpdateManagedCertificatesStatus_" + status.ToString()); + _tc?.TrackEvent("UpdateManagedCertificatesStatus_" + status); } private ConcurrentDictionary _statusReportQueue { get; set; } = new ConcurrentDictionary(); @@ -509,7 +510,13 @@ public async Task GetItemLog(string id, int limit) } catch (Exception exp) { - return new LogItem[] { new LogItem { LogLevel = "ERR", EventDate = DateTime.Now, Message = $"Failed to read log: {exp}" } }; + return new LogItem[] + { + new LogItem + { + LogLevel = "ERR", EventDate = DateTime.Now, Message = $"Failed to read log: {exp}" + } + }; } } else @@ -681,36 +688,32 @@ private async Task StartHttpChallengeServer() /// Stop our temporary http challenge response service /// /// - private async Task StopHttpChallengeServer() + private async Task StopHttpChallengeServer() { - if (_httpChallengeServerClient != null) + if (_httpChallengeServerClient == null) { - try + return; + } + + try + { + var response = await _httpChallengeServerClient.GetAsync($"http://127.0.0.1:{_httpChallengePort}/.well-known/acme-challenge/{_httpChallengeControlKey}"); + if (response.IsSuccessStatusCode) { - var response = await _httpChallengeServerClient.GetAsync($"http://127.0.0.1:{_httpChallengePort}/.well-known/acme-challenge/{_httpChallengeControlKey}"); - if (response.IsSuccessStatusCode) - { - return true; - } - else - { - try - { - if (_httpChallengeProcess != null && !_httpChallengeProcess.HasExited) - { - _httpChallengeProcess.CloseMainWindow(); - } - } - catch { } - } + return; } - catch + else { - return true; + if (_httpChallengeProcess?.HasExited == false) + { + _httpChallengeProcess.CloseMainWindow(); + } } } - - return true; + catch + { + // ignored + } } } } diff --git a/src/Certify.Core/Management/Challenges/DNS/DnsChallengeHelper.cs b/src/Certify.Core/Management/Challenges/DNS/DnsChallengeHelper.cs index a04d9c3ea..f8fa7f26b 100644 --- a/src/Certify.Core/Management/Challenges/DNS/DnsChallengeHelper.cs +++ b/src/Certify.Core/Management/Challenges/DNS/DnsChallengeHelper.cs @@ -167,54 +167,52 @@ public async Task CompleteDNSChallenge(ILog log, Manag #pragma warning restore CS0618 // Type or member is obsolete } - if (dnsAPIProvider != null) - { - //most DNS providers require domains to by ASCII - txtRecordName = _idnMapping.GetAscii(txtRecordName).ToLower().Trim(); + //most DNS providers require domains to by ASCII + txtRecordName = _idnMapping.GetAscii(txtRecordName).ToLower().Trim(); - if (!string.IsNullOrEmpty(challengeConfig.ChallengeDelegationRule)) - { - var delegatedTXTRecordName = ApplyChallengeDelegationRule(domain.Value, txtRecordName, challengeConfig.ChallengeDelegationRule); - log.Information($"DNS: Challenge Delegation Domain enabled, using {delegatedTXTRecordName} in place of {txtRecordName}."); + if (!string.IsNullOrEmpty(challengeConfig.ChallengeDelegationRule)) + { + var delegatedTxtRecordName = ApplyChallengeDelegationRule(domain.Value, txtRecordName, challengeConfig.ChallengeDelegationRule); + log.Information($"DNS: Challenge Delegation Domain enabled, using {delegatedTxtRecordName} in place of {txtRecordName}."); - txtRecordName = delegatedTXTRecordName; - } + txtRecordName = delegatedTxtRecordName; + } - log.Information($"DNS: Creating TXT Record '{txtRecordName}' with value '{txtRecordValue}', [{domain.Value}] {(zoneId != null ? $"in ZoneId '{zoneId}'" : "")} using API provider '{dnsAPIProvider.ProviderTitle}'"); - try + log.Information($"DNS: Creating TXT Record '{txtRecordName}' with value '{txtRecordValue}', [{domain.Value}] {(zoneId != null ? $"in ZoneId '{zoneId}'" : "")} using API provider '{dnsAPIProvider.ProviderTitle}'"); + try + { + var result = await dnsAPIProvider.CreateRecord(new DnsRecord { - var result = await dnsAPIProvider.CreateRecord(new DnsRecord - { - RecordType = "TXT", - TargetDomainName = domain.Value.Trim(), - RecordName = txtRecordName, - RecordValue = txtRecordValue, - ZoneId = zoneId - }); + RecordType = "TXT", + TargetDomainName = domain.Value.Trim(), + RecordName = txtRecordName, + RecordValue = txtRecordValue, + ZoneId = zoneId + }); - result.Message = $"{dnsAPIProvider.ProviderTitle} :: {result.Message}"; + result.Message = $"{dnsAPIProvider.ProviderTitle} :: {result.Message}"; - var isAwaitingUser = false; + var isAwaitingUser = false; - if (challengeConfig.ChallengeProvider.Contains(".Manual") || result.Message.Contains("[Action Required]")) - { - isAwaitingUser = true; - } - - return new DnsChallengeHelperResult - { - Result = result, - PropagationSeconds = dnsAPIProvider.PropagationDelaySeconds, - IsAwaitingUser = isAwaitingUser - }; - } - catch (Exception exp) + if (challengeConfig.ChallengeProvider.Contains(".Manual") || result.Message.Contains("[Action Required]")) { - return new DnsChallengeHelperResult(failureMsg: $"Failed [{dnsAPIProvider.ProviderTitle}]: {exp}"); + isAwaitingUser = true; } - //TODO: DNS query to check for new record - /* + return new DnsChallengeHelperResult + { + Result = result, + PropagationSeconds = dnsAPIProvider.PropagationDelaySeconds, + IsAwaitingUser = isAwaitingUser + }; + } + catch (Exception exp) + { + return new DnsChallengeHelperResult(failureMsg: $"Failed [{dnsAPIProvider.ProviderTitle}]: {exp}"); + } + + //TODO: DNS query to check for new record + /* if (result.IsSuccess) { // do our own txt record query before proceeding with challenge completion @@ -245,11 +243,6 @@ public async Task CompleteDNSChallenge(ILog log, Manag return result; } */ - } - else - { - return new DnsChallengeHelperResult(failureMsg: "Error: Could not determine DNS API Provider."); - } } /// diff --git a/src/Certify.Core/Management/RenewalManager.cs b/src/Certify.Core/Management/RenewalManager.cs index 348023306..e93a25f4b 100644 --- a/src/Certify.Core/Management/RenewalManager.cs +++ b/src/Certify.Core/Management/RenewalManager.cs @@ -2,9 +2,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; -using System.Threading; using System.Threading.Tasks; -using Certify.Locales; using Certify.Models; using Certify.Models.Providers; using Certify.Providers; @@ -23,7 +21,7 @@ public static class RenewalManager private static Progress SetupProgressTracker( ManagedCertificate item, string renewalReason, ConcurrentDictionary> progressTrackers, - Action, RequestProgressState, bool> ReportProgress + Action, RequestProgressState, bool> reportProgress ) { @@ -33,19 +31,19 @@ Action, RequestProgressState, bool> ReportProgre progressTrackers.TryAdd(item.Id, progressTracker); - ReportProgress(progressTracker, new RequestProgressState(RequestState.Queued, $"Queued for renewal: {renewalReason}", item), false); + reportProgress(progressTracker, new RequestProgressState(RequestState.Queued, $"Queued for renewal: {renewalReason}", item), false); return progressTracker; } public static async Task> PerformRenewAll( - ILog _serviceLog, - IManagedItemStore _itemManager, + ILog serviceLog, + IManagedItemStore itemManager, RenewalSettings settings, RenewalPrefs prefs, - Action, RequestProgressState, bool> ReportProgress, - Func> IsManagedCertificateRunning, - Func, bool, string, Task> PerformCertificateRequest, + Action, RequestProgressState, bool> reportProgress, + Func> isManagedCertificateRunning, + Func, bool, string, Task> performCertificateRequest, ConcurrentDictionary> progressTrackers = null ) { @@ -72,18 +70,18 @@ public static async Task> PerformRenewAll( foreach (var id in settings.TargetManagedCertificates) { - targetCerts.Add(await _itemManager.GetById(id)); + targetCerts.Add(await itemManager.GetById(id)); } managedCertificateBatch = targetCerts; foreach (var item in managedCertificateBatch) { - var progressTracker = SetupProgressTracker(item, "", progressTrackers, ReportProgress); + var progressTracker = SetupProgressTracker(item, "", progressTrackers, reportProgress); renewalTasks.Add( new Task( - () => PerformCertificateRequest(item, progressTracker, settings.IsPreviewMode, "Renewal requested").Result, + () => performCertificateRequest(item, progressTracker, settings.IsPreviewMode, "Renewal requested").Result, TaskCreationOptions.LongRunning ) ); @@ -122,7 +120,7 @@ public static async Task> PerformRenewAll( .OrderBy(s => s.DateLastRenewalAttempt ?? DateTimeOffset.UtcNow.AddHours(-1)); }*/ - var totalRenewalCandidates = await _itemManager.CountAll(filter); + var totalRenewalCandidates = await itemManager.CountAll(filter); var renewalIntervalDays = prefs.RenewalIntervalDays; var renewalIntervalMode = prefs.RenewalIntervalMode ?? RenewalIntervalModes.DaysAfterLastRenewal; @@ -134,14 +132,14 @@ public static async Task> PerformRenewAll( var resultsRemaining = totalRenewalCandidates; // identify items we will attempt and begin tracking progress - while (batch.Count() < maxRenewalTasks && resultsRemaining > 0) + while (batch.Count < maxRenewalTasks && resultsRemaining > 0) { - var results = await _itemManager.Find(filter); - resultsRemaining = results.Count(); + var results = await itemManager.Find(filter); + resultsRemaining = results.Count; foreach (var item in results) { - if (batch.Count() < maxRenewalTasks) + if (batch.Count < maxRenewalTasks) { // if cert is not awaiting manual user input (manual DNS etc), proceed with renewal checks if (item.LastRenewalStatus != RequestState.Paused) @@ -163,7 +161,7 @@ public static async Task> PerformRenewAll( // if we care about stopped sites being stopped, check if a specific site is selected and if it's running if (!prefs.IncludeStoppedSites && !string.IsNullOrEmpty(item.ServerSiteId) && item.RequestConfig.DeploymentSiteOption == DeploymentOption.SingleSite) { - var isSiteRunning = await IsManagedCertificateRunning(item.Id); + var isSiteRunning = await isManagedCertificateRunning(item.Id); if (!isSiteRunning) { @@ -176,11 +174,11 @@ public static async Task> PerformRenewAll( { batch.Add(item); - var progressTracker = SetupProgressTracker(item, "", progressTrackers, ReportProgress); + var progressTracker = SetupProgressTracker(item, "", progressTrackers, reportProgress); renewalTasks.Add( new Task( - () => PerformCertificateRequest(item, progressTracker, settings.IsPreviewMode, renewalReason).Result, + () => performCertificateRequest(item, progressTracker, settings.IsPreviewMode, renewalReason).Result, TaskCreationOptions.LongRunning ) ); @@ -197,7 +195,7 @@ public static async Task> PerformRenewAll( if (managedCertificateBatch.Count(c => c.LastRenewalStatus == RequestState.Error) > MAX_CERTIFICATE_REQUEST_TASKS) { - _serviceLog?.Warning("Too many failed certificates outstanding. Fix failures or delete. Failures: " + managedCertificateBatch.Count(c => c.LastRenewalStatus == RequestState.Error)); + serviceLog?.Warning("Too many failed certificates outstanding. Fix failures or delete. Failures: " + managedCertificateBatch.Count(c => c.LastRenewalStatus == RequestState.Error)); } if (!renewalTasks.Any()) @@ -208,7 +206,7 @@ public static async Task> PerformRenewAll( } else { - _serviceLog.Information($"Attempting {renewalTasks.Count} renewal tasks. Max renewal tasks is set to {maxRenewalTasks}, max supported tasks is {MAX_CERTIFICATE_REQUEST_TASKS}"); + serviceLog?.Information($"Attempting {renewalTasks.Count} renewal tasks. Max renewal tasks is set to {maxRenewalTasks}, max supported tasks is {MAX_CERTIFICATE_REQUEST_TASKS}"); } if (prefs.PerformParallelRenewals) @@ -241,42 +239,42 @@ public static async Task> PerformRenewAll( /// private static List GetAccountsWithRequiredCAFeatures(ManagedCertificate item, string defaultCA, ICollection certificateAuthorities, List accounts) { - var requiredCAFeatures = new List(); + var requiredCaFeatures = new List(); var identifiers = item.GetCertificateIdentifiers(); if (identifiers.Any(i => i.IdentifierType == CertIdentifierType.Dns && i.Value.StartsWith("*"))) { - requiredCAFeatures.Add(CertAuthoritySupportedRequests.DOMAIN_WILDCARD); + requiredCaFeatures.Add(CertAuthoritySupportedRequests.DOMAIN_WILDCARD); } if (identifiers.Count(i => i.IdentifierType == CertIdentifierType.Dns) == 1) { - requiredCAFeatures.Add(CertAuthoritySupportedRequests.DOMAIN_SINGLE); + requiredCaFeatures.Add(CertAuthoritySupportedRequests.DOMAIN_SINGLE); } if (identifiers.Count(i => i.IdentifierType == CertIdentifierType.Dns) > 2) { - requiredCAFeatures.Add(CertAuthoritySupportedRequests.DOMAIN_MULTIPLE_SAN); + requiredCaFeatures.Add(CertAuthoritySupportedRequests.DOMAIN_MULTIPLE_SAN); } if (identifiers.Any(i => i.IdentifierType == CertIdentifierType.Ip)) { - requiredCAFeatures.Add(CertAuthoritySupportedRequests.IP_SINGLE); + requiredCaFeatures.Add(CertAuthoritySupportedRequests.IP_SINGLE); } if (identifiers.Count(i => i.IdentifierType == CertIdentifierType.Ip) > 1) { - requiredCAFeatures.Add(CertAuthoritySupportedRequests.IP_MULTIPLE); + requiredCaFeatures.Add(CertAuthoritySupportedRequests.IP_MULTIPLE); } if (identifiers.Any(i => i.IdentifierType == CertIdentifierType.TnAuthList)) { - requiredCAFeatures.Add(CertAuthoritySupportedRequests.TNAUTHLIST); + requiredCaFeatures.Add(CertAuthoritySupportedRequests.TNAUTHLIST); } if (item.RequestConfig.PreferredExpiryDays > 0) { - requiredCAFeatures.Add(CertAuthoritySupportedRequests.OPTIONAL_LIFETIME_DAYS); + requiredCaFeatures.Add(CertAuthoritySupportedRequests.OPTIONAL_LIFETIME_DAYS); } var fallbackCandidateAccounts = accounts.Where(a => a.CertificateAuthorityId != defaultCA && a.IsStagingAccount == item.UseStagingMode); @@ -287,7 +285,7 @@ private static List GetAccountsWithRequiredCAFeatures(ManagedCer // select a candidate based on features required by the certificate. If a CA has no known features we assume it supports all the ones we might be interested in foreach (var ca in certificateAuthorities) { - if (!ca.SupportedFeatures.Any() || requiredCAFeatures.All(r => ca.SupportedFeatures.Contains(r.ToString()))) + if (!ca.SupportedFeatures.Any() || requiredCaFeatures.All(r => ca.SupportedFeatures.Contains(r.ToString()))) { fallbackAccounts.AddRange(fallbackCandidateAccounts.Where(f => f.CertificateAuthorityId == ca.Id)); } diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/ManagedCertificateController.cs b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/ManagedCertificateController.cs index c738a18f5..559029026 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/ManagedCertificateController.cs +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/ManagedCertificateController.cs @@ -10,6 +10,7 @@ using Certify.Models.Config; using Certify.Models.Utils; using Certify.Reporting; +using Microsoft.AspNetCore.Mvc; using Serilog; namespace Certify.Service.Controllers diff --git a/src/Certify.UI.Shared/Controls/ManagedCertificates.xaml b/src/Certify.UI.Shared/Controls/ManagedCertificates.xaml index 06ef4a448..b430a74d7 100644 --- a/src/Certify.UI.Shared/Controls/ManagedCertificates.xaml +++ b/src/Certify.UI.Shared/Controls/ManagedCertificates.xaml @@ -88,7 +88,15 @@ PreviewKeyDown="TxtFilter_PreviewKeyDown" TextChanged="TxtFilter_TextChanged" /> - + + + + + Date: Fri, 1 Sep 2023 16:23:24 +0800 Subject: [PATCH 048/328] Update reporting namespace --- src/Certify.Client/CertifyApiClient.cs | 2 +- src/Certify.Client/ICertifyClient.cs | 2 +- .../CertifyManager.ManagedCertificates.cs | 4 ++-- .../CertifyManager/ICertifyManager.cs | 2 +- src/Certify.Models/Reporting/Summary.cs | 2 +- .../Controllers/v1/CertificateController.cs | 22 +++++++++++++++++++ .../ManagedCertificateController.cs | 2 +- .../ManagedCertificateController.cs | 2 +- .../ManagedCertificate/Dashboard.xaml.cs | 2 +- .../AppViewModel.ManagedCerticates.cs | 2 +- 10 files changed, 32 insertions(+), 10 deletions(-) diff --git a/src/Certify.Client/CertifyApiClient.cs b/src/Certify.Client/CertifyApiClient.cs index 4dcbe9a62..bb7217688 100644 --- a/src/Certify.Client/CertifyApiClient.cs +++ b/src/Certify.Client/CertifyApiClient.cs @@ -11,7 +11,7 @@ using Certify.Models.API; using Certify.Models.Config; using Certify.Models.Utils; -using Certify.Reporting; +using Certify.Models.Reporting; using Certify.Shared; using Newtonsoft.Json; using Polly; diff --git a/src/Certify.Client/ICertifyClient.cs b/src/Certify.Client/ICertifyClient.cs index 4d4e48eb6..a216a74e4 100644 --- a/src/Certify.Client/ICertifyClient.cs +++ b/src/Certify.Client/ICertifyClient.cs @@ -5,7 +5,7 @@ using Certify.Models; using Certify.Models.Config; using Certify.Models.Utils; -using Certify.Reporting; +using Certify.Models.Reporting; using Certify.Shared; namespace Certify.Client diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagedCertificates.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagedCertificates.cs index 833581cef..95585ace2 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagedCertificates.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagedCertificates.cs @@ -7,7 +7,7 @@ using Certify.Models; using Certify.Models.API; using Certify.Models.Providers; -using Certify.Reporting; +using Certify.Models.Reporting; namespace Certify.Management { @@ -91,7 +91,7 @@ public async Task GetManagedCertificateResults(M return result; } - public async Task GetManagedCertificateSummary(ManagedCertificateFilter filter) + public async Task GetManagedCertificateSummary(ManagedCertificateFilter filter) { var ms = await _itemManager.Find(filter); diff --git a/src/Certify.Core/Management/CertifyManager/ICertifyManager.cs b/src/Certify.Core/Management/CertifyManager/ICertifyManager.cs index 045292cb0..c4c3fac72 100644 --- a/src/Certify.Core/Management/CertifyManager/ICertifyManager.cs +++ b/src/Certify.Core/Management/CertifyManager/ICertifyManager.cs @@ -29,7 +29,7 @@ public interface ICertifyManager Task> GetManagedCertificates(ManagedCertificateFilter filter = null); Task GetManagedCertificateResults(ManagedCertificateFilter filter = null); - Task GetManagedCertificateSummary(ManagedCertificateFilter filter = null); + Task GetManagedCertificateSummary(ManagedCertificateFilter filter = null); Task UpdateManagedCertificate(ManagedCertificate site); diff --git a/src/Certify.Models/Reporting/Summary.cs b/src/Certify.Models/Reporting/Summary.cs index 792289e7d..0e1e4cc48 100644 --- a/src/Certify.Models/Reporting/Summary.cs +++ b/src/Certify.Models/Reporting/Summary.cs @@ -3,7 +3,7 @@ using System.Text; using Certify.Models; -namespace Certify.Reporting +namespace Certify.Models.Reporting { public class Summary : BindableBase { diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs index 70b675e9b..4f945e5ce 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using Certify.Client; using Certify.Models.API; +using Certify.Models.Reporting; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; @@ -143,6 +144,27 @@ public async Task GetManagedCertificates(string keyword, int? pag return new OkObjectResult(result); } + + /// + /// Get summary counts of all managed certs + /// + /// + /// + [HttpPost] + [Route("summary")] + [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Summary))] + public async Task GetManagedCertificateSummary(string keyword) + { + + var summary = await _client.GetManagedCertificateSummary( + new Models.ManagedCertificateFilter + { + Keyword = keyword + }); + + return new OkObjectResult(summary); + } /// /// Gets the full settings for a specific managed certificate diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/ManagedCertificateController.cs b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/ManagedCertificateController.cs index 559029026..856054b0e 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/ManagedCertificateController.cs +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/ManagedCertificateController.cs @@ -9,7 +9,7 @@ using Certify.Models.API; using Certify.Models.Config; using Certify.Models.Utils; -using Certify.Reporting; +using Certify.Models.Reporting; using Microsoft.AspNetCore.Mvc; using Serilog; diff --git a/src/Certify.Service/Controllers/ManagedCertificateController.cs b/src/Certify.Service/Controllers/ManagedCertificateController.cs index 2a55ea676..5cd750443 100644 --- a/src/Certify.Service/Controllers/ManagedCertificateController.cs +++ b/src/Certify.Service/Controllers/ManagedCertificateController.cs @@ -10,7 +10,7 @@ using Certify.Models.API; using Certify.Models.Config; using Certify.Models.Utils; -using Certify.Reporting; +using Certify.Models.Reporting; using Serilog; namespace Certify.Service.Controllers diff --git a/src/Certify.UI.Shared/Controls/ManagedCertificate/Dashboard.xaml.cs b/src/Certify.UI.Shared/Controls/ManagedCertificate/Dashboard.xaml.cs index 322ec8aa1..6e071d5f4 100644 --- a/src/Certify.UI.Shared/Controls/ManagedCertificate/Dashboard.xaml.cs +++ b/src/Certify.UI.Shared/Controls/ManagedCertificate/Dashboard.xaml.cs @@ -5,7 +5,7 @@ using System.Threading.Tasks; using System.Windows.Controls; using Certify.Models; -using Certify.Reporting; +using Certify.Models.Reporting; using Certify.UI.ViewModel; namespace Certify.UI.Controls.ManagedCertificate diff --git a/src/Certify.UI.Shared/ViewModel/AppViewModel/AppViewModel.ManagedCerticates.cs b/src/Certify.UI.Shared/ViewModel/AppViewModel/AppViewModel.ManagedCerticates.cs index 7ffdd1593..3c8850e49 100644 --- a/src/Certify.UI.Shared/ViewModel/AppViewModel/AppViewModel.ManagedCerticates.cs +++ b/src/Certify.UI.Shared/ViewModel/AppViewModel/AppViewModel.ManagedCerticates.cs @@ -8,7 +8,7 @@ using Certify.Locales; using Certify.Models; using Certify.Models.API; -using Certify.Reporting; +using Certify.Models.Reporting; using Certify.UI.Shared; using PropertyChanged; From 83c1231f6190d2a840de9462b7f95b314c3f714b Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Tue, 5 Sep 2023 13:23:52 +0800 Subject: [PATCH 049/328] Package updates --- src/Certify.Client/Certify.Client.csproj | 2 +- src/Certify.Models/Certify.Models.csproj | 2 +- .../DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj | 2 +- .../Certify.Server.Api.Public.Tests.csproj | 2 +- .../Certify.Server.Api.Public.csproj | 4 ++-- src/Certify.Service/App.config | 2 +- .../Certify.Core.Tests.Integration.csproj | 2 +- .../Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj | 6 +++--- .../Certify.Service.Tests.Integration.csproj | 6 +++--- .../Certify.UI.Tests.Integration.csproj | 6 +++--- 10 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/Certify.Client/Certify.Client.csproj b/src/Certify.Client/Certify.Client.csproj index 7511ef8b5..fe19a90fc 100644 --- a/src/Certify.Client/Certify.Client.csproj +++ b/src/Certify.Client/Certify.Client.csproj @@ -18,7 +18,7 @@ - + diff --git a/src/Certify.Models/Certify.Models.csproj b/src/Certify.Models/Certify.Models.csproj index f351c5258..0bea35ab6 100644 --- a/src/Certify.Models/Certify.Models.csproj +++ b/src/Certify.Models/Certify.Models.csproj @@ -21,7 +21,7 @@ all runtime; build; native; contentfiles; analyzers - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj index 436c1e0a8..2f11dbab9 100644 --- a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj +++ b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj b/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj index 8024cbd01..b57ef4fe2 100644 --- a/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj +++ b/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj @@ -26,7 +26,7 @@ - + diff --git a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj index fff5aa804..b412fd086 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj +++ b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj @@ -15,10 +15,10 @@ - + - + diff --git a/src/Certify.Service/App.config b/src/Certify.Service/App.config index 0dbd94a9f..6152ece63 100644 --- a/src/Certify.Service/App.config +++ b/src/Certify.Service/App.config @@ -33,7 +33,7 @@ - + diff --git a/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj b/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj index 0fe088f09..ddef73206 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj @@ -88,7 +88,7 @@ - + diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj index 47e6d411f..70c813aa7 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj @@ -133,9 +133,9 @@ - - - + + + diff --git a/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj b/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj index cb8c88d4a..354fde3b9 100644 --- a/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj @@ -77,9 +77,9 @@ - - - + + + diff --git a/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj b/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj index 292b99844..3ff65e868 100644 --- a/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj @@ -73,9 +73,9 @@ - - - + + + From 5e2f836ed93c51b30218c56e671b315490ab524c Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 7 Sep 2023 17:44:25 +0800 Subject: [PATCH 050/328] UI: Implement result paging and XAML cleanup --- src/Certify.UI.Desktop/App.xaml | 22 +++++---- .../Controls/ManagedCertificates.xaml | 30 ++++++++---- .../Controls/ManagedCertificates.xaml.cs | 10 ++++ .../AppViewModel.ManagedCerticates.cs | 48 ++++++++++++++----- src/Certify.UI/App.xaml | 25 +++++----- 5 files changed, 95 insertions(+), 40 deletions(-) diff --git a/src/Certify.UI.Desktop/App.xaml b/src/Certify.UI.Desktop/App.xaml index efbd87683..a0a6511ac 100644 --- a/src/Certify.UI.Desktop/App.xaml +++ b/src/Certify.UI.Desktop/App.xaml @@ -1,8 +1,8 @@ - @@ -10,10 +10,13 @@ - - + + - + @@ -71,8 +75,8 @@ - - + + diff --git a/src/Certify.UI.Shared/Controls/ManagedCertificates.xaml b/src/Certify.UI.Shared/Controls/ManagedCertificates.xaml index b430a74d7..b5caf5da6 100644 --- a/src/Certify.UI.Shared/Controls/ManagedCertificates.xaml +++ b/src/Certify.UI.Shared/Controls/ManagedCertificates.xaml @@ -88,15 +88,27 @@ PreviewKeyDown="TxtFilter_PreviewKeyDown" TextChanged="TxtFilter_TextChanged" /> - - - - - + + + + + /// If set, there are one or more vault items available to be imported as managed sites /// @@ -81,10 +80,7 @@ public ObservableCollection ManagedCertificates } } - public long TotalManagedCertificates - { - get; set; - } + public long TotalManagedCertificates { get; set; } /// /// Cached count of the number of managed certificate (not counting external certificate managers) @@ -92,14 +88,11 @@ public long TotalManagedCertificates [DependsOn(nameof(ManagedCertificates))] public int NumManagedCerts { - get - { - return ManagedCertificates?.Where(c => string.IsNullOrEmpty(c.SourceId)).Count() ?? 0; - } + get { return ManagedCertificates?.Where(c => string.IsNullOrEmpty(c.SourceId)).Count() ?? 0; } } int _filterPageIndex = 0; - int _filterPageSize = 10; + int _filterPageSize = 15; /// /// Refresh the cached list of managed certs via the connected service @@ -112,7 +105,7 @@ public virtual async Task RefreshManagedCertificates() // include external managed certs if enabled filter.IncludeExternal = IsFeatureEnabled(FeatureFlags.EXTERNAL_CERT_MANAGERS) && Preferences.EnableExternalCertManagers; filter.PageSize = _filterPageSize; - filter.PageIndex= _filterPageIndex; + filter.PageIndex = _filterPageIndex; var result = await _certifyClient.GetManagedCertificateSearchResult(filter); @@ -127,6 +120,39 @@ public async Task GetManagedCertificateSummary() return await _certifyClient.GetManagedCertificateSummary(filter); } + public async Task ManagedCertificatesNextPage() + { + if (ManagedCertificates.Count() >= _filterPageSize) + { + _filterPageIndex++; + await RefreshManagedCertificates(); + RaisePropertyChangedEvent(nameof(ResultPageDescription)); + } + } + + public async Task ManagedCertificatesPrevPage() + { + if (_filterPageIndex > 0) + { + _filterPageIndex--; + await RefreshManagedCertificates(); + RaisePropertyChangedEvent(nameof(ResultPageDescription)); + } + } + + /// + /// Formatted description of the current page index in the result set + /// + public string ResultPageDescription + { + get { return $"Page {_filterPageIndex + 1} of {Math.Ceiling((decimal)TotalManagedCertificates / _filterPageSize)}"; } + } + + /// + /// True if there are more managed certificates than the current result set batch size + /// + public bool HasPagesOfResults => TotalManagedCertificates > _filterPageSize; + /// /// Add/Update a managed certificate via service /// diff --git a/src/Certify.UI/App.xaml b/src/Certify.UI/App.xaml index 014fd7976..dad6035b3 100644 --- a/src/Certify.UI/App.xaml +++ b/src/Certify.UI/App.xaml @@ -1,8 +1,8 @@ - @@ -10,10 +10,13 @@ - - + + - + @@ -71,11 +75,10 @@ - - + + - - + From 221b7e895e86b5bd89a610da9cf3c6045859ce1a Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Tue, 12 Sep 2023 20:52:46 +0800 Subject: [PATCH 051/328] Package updates --- src/Certify.Client/Certify.Client.csproj | 2 +- src/Certify.Models/Certify.Models.csproj | 2 +- .../DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj | 2 +- src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj | 2 +- .../Certify.Server.Api.Public.csproj | 4 ++-- src/Certify.Service/App.config | 4 ++-- src/Certify.Service/Certify.Service.csproj | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Certify.Client/Certify.Client.csproj b/src/Certify.Client/Certify.Client.csproj index fe19a90fc..658b60abb 100644 --- a/src/Certify.Client/Certify.Client.csproj +++ b/src/Certify.Client/Certify.Client.csproj @@ -18,7 +18,7 @@ - + diff --git a/src/Certify.Models/Certify.Models.csproj b/src/Certify.Models/Certify.Models.csproj index 0bea35ab6..94ae051fe 100644 --- a/src/Certify.Models/Certify.Models.csproj +++ b/src/Certify.Models/Certify.Models.csproj @@ -21,7 +21,7 @@ all runtime; build; native; contentfiles; analyzers - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj index 2f11dbab9..59198bc1c 100644 --- a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj +++ b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj b/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj index baa13e875..0337ec6d3 100644 --- a/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj +++ b/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj index b412fd086..be2e1d996 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj +++ b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj @@ -15,10 +15,10 @@ - + - + diff --git a/src/Certify.Service/App.config b/src/Certify.Service/App.config index 6152ece63..a8ae0f42f 100644 --- a/src/Certify.Service/App.config +++ b/src/Certify.Service/App.config @@ -14,7 +14,7 @@ - + @@ -33,7 +33,7 @@ - + diff --git a/src/Certify.Service/Certify.Service.csproj b/src/Certify.Service/Certify.Service.csproj index 5bef08190..2a81cda48 100644 --- a/src/Certify.Service/Certify.Service.csproj +++ b/src/Certify.Service/Certify.Service.csproj @@ -54,7 +54,7 @@ - + From 751f9bc86e2de49c7c46a46f0aa77b30aef30e7f Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Tue, 12 Sep 2023 20:54:02 +0800 Subject: [PATCH 052/328] Dashboard summary fix --- .../Controls/ManagedCertificate/Dashboard.xaml.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Certify.UI.Shared/Controls/ManagedCertificate/Dashboard.xaml.cs b/src/Certify.UI.Shared/Controls/ManagedCertificate/Dashboard.xaml.cs index 6e071d5f4..c75548ae8 100644 --- a/src/Certify.UI.Shared/Controls/ManagedCertificate/Dashboard.xaml.cs +++ b/src/Certify.UI.Shared/Controls/ManagedCertificate/Dashboard.xaml.cs @@ -46,12 +46,10 @@ private void AppViewModel_PropertyChanged(object sender, PropertyChangedEventArg public async Task RefreshSummary() { + var summary = await AppViewModel.Current.GetManagedCertificateSummary(); - if (AppViewModel.Current.ManagedCertificates?.Any() == true) + if (summary?.Total > 0) { - var summary = await AppViewModel.Current.GetManagedCertificateSummary(); - - var ms = AppViewModel.Current.ManagedCertificates; ViewModel.Total = summary.Total; ViewModel.Healthy = summary.Healthy; From 8f5237d08e449f6d9d456395fd37fb10fdc551c0 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Tue, 12 Sep 2023 20:54:27 +0800 Subject: [PATCH 053/328] WIP: text filter refresh results --- .../Controls/ManagedCertificates.xaml.cs | 87 ++++++++++++++++--- .../AppViewModel.ManagedCerticates.cs | 11 ++- 2 files changed, 84 insertions(+), 14 deletions(-) diff --git a/src/Certify.UI.Shared/Controls/ManagedCertificates.xaml.cs b/src/Certify.UI.Shared/Controls/ManagedCertificates.xaml.cs index a859bed0e..d9cf9ea77 100644 --- a/src/Certify.UI.Shared/Controls/ManagedCertificates.xaml.cs +++ b/src/Certify.UI.Shared/Controls/ManagedCertificates.xaml.cs @@ -1,6 +1,8 @@ using System; using System.ComponentModel; using System.Linq; +using System.Threading.Tasks; +using System.Threading; using System.Windows; using System.Windows.Controls; using System.Windows.Data; @@ -82,7 +84,7 @@ private void SetFilter() //sort by name ascending CollectionViewSource.GetDefaultView(_appViewModel.ManagedCertificates).SortDescriptions.Clear(); - + if (_sortOrder == "NameAsc") { CollectionViewSource.GetDefaultView(_appViewModel.ManagedCertificates).SortDescriptions.Add( @@ -121,21 +123,78 @@ private async void ListViewItem_InteractionEvent(object sender, InputEventArgs e } } - private void TxtFilter_TextChanged(object sender, TextChangedEventArgs e) + class Debouncer : IDisposable { - var defaultView = CollectionViewSource.GetDefaultView(lvManagedCertificates.ItemsSource); + private CancellationTokenSource lastCancellationTokenSource; + private int milliseconds; - defaultView.Refresh(); + public Debouncer(int milliseconds = 300) + { + this.milliseconds = milliseconds; + } - if (lvManagedCertificates.SelectedIndex == -1 && _appViewModel.SelectedItem != null) + public async Task Debounce(Func action) { - // if the data model's selected item has come into view after filter box text - // changed, select the item in the list - if (defaultView.Filter(_appViewModel.SelectedItem)) + Cancel(lastCancellationTokenSource); + + var tokenSrc = lastCancellationTokenSource = new CancellationTokenSource(); + + try { - lvManagedCertificates.SelectedItem = _appViewModel.SelectedItem; + await Task.Delay(new TimeSpan(milliseconds), tokenSrc.Token); + if (!tokenSrc.IsCancellationRequested) + { + await Task.Run(action, tokenSrc.Token); + } + } + catch (TaskCanceledException) + { + } + } + + public void Cancel(CancellationTokenSource source) + { + if (source != null) + { + source.Cancel(); + source.Dispose(); } } + + public void Dispose() + { + Cancel(lastCancellationTokenSource); + } + + ~Debouncer() + { + Dispose(); + } + } + + private Debouncer _filterDebouncer = new Debouncer(); + + private async void TxtFilter_TextChanged(object sender, TextChangedEventArgs e) + { + // refresh db results, then refresh UI view + + _appViewModel.FilterKeyword = txtFilter.Text; + + await _filterDebouncer.Debounce(_appViewModel.RefreshManagedCertificates); + + var defaultView = CollectionViewSource.GetDefaultView(lvManagedCertificates.ItemsSource); + + defaultView.Refresh(); + + /* if (lvManagedCertificates.SelectedIndex == -1 && _appViewModel.SelectedItem != null) + { + // if the data model's selected item has come into view after filter box text + // changed, select the item in the list + if (defaultView.Filter(_appViewModel.SelectedItem)) + { + lvManagedCertificates.SelectedItem = _appViewModel.SelectedItem; + } + }*/ } private async void TxtFilter_PreviewKeyDown(object sender, KeyEventArgs e) @@ -169,6 +228,8 @@ private async void TxtFilter_PreviewKeyDown(object sender, KeyEventArgs e) private void ResetFilter() { + _appViewModel.FilterKeyword = string.Empty; + txtFilter.Text = ""; txtFilter.Focus(); @@ -372,14 +433,14 @@ private void GettingStarted_FilterApplied(string filter) txtFilter.Text = filter; } - private void Prev_Click(object sender, RoutedEventArgs e) + private async void Prev_Click(object sender, RoutedEventArgs e) { - _appViewModel.ManagedCertificatesPrevPage(); + await _appViewModel.ManagedCertificatesPrevPage(); } - private void Next_Click(object sender, RoutedEventArgs e) + private async void Next_Click(object sender, RoutedEventArgs e) { - _appViewModel.ManagedCertificatesNextPage(); + await _appViewModel.ManagedCertificatesNextPage(); } } diff --git a/src/Certify.UI.Shared/ViewModel/AppViewModel/AppViewModel.ManagedCerticates.cs b/src/Certify.UI.Shared/ViewModel/AppViewModel/AppViewModel.ManagedCerticates.cs index 012ecd8f0..c0ae7c5c2 100644 --- a/src/Certify.UI.Shared/ViewModel/AppViewModel/AppViewModel.ManagedCerticates.cs +++ b/src/Certify.UI.Shared/ViewModel/AppViewModel/AppViewModel.ManagedCerticates.cs @@ -94,6 +94,8 @@ public int NumManagedCerts int _filterPageIndex = 0; int _filterPageSize = 15; + public string FilterKeyword { get; set; }= string.Empty; + /// /// Refresh the cached list of managed certs via the connected service /// @@ -106,6 +108,8 @@ public virtual async Task RefreshManagedCertificates() filter.IncludeExternal = IsFeatureEnabled(FeatureFlags.EXTERNAL_CERT_MANAGERS) && Preferences.EnableExternalCertManagers; filter.PageSize = _filterPageSize; filter.PageIndex = _filterPageIndex; + + filter.Keyword = string.IsNullOrWhiteSpace(FilterKeyword) ? null : FilterKeyword; var result = await _certifyClient.GetManagedCertificateSearchResult(filter); @@ -115,9 +119,14 @@ public virtual async Task RefreshManagedCertificates() public async Task GetManagedCertificateSummary() { + if (!IsServiceAvailable) + { + return null; + } + var filter = new ManagedCertificateFilter(); - return await _certifyClient.GetManagedCertificateSummary(filter); + return await _certifyClient?.GetManagedCertificateSummary(filter); } public async Task ManagedCertificatesNextPage() From b9b7dfc78415cf82fdab0b1f3772d45476f77442 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Fri, 22 Sep 2023 16:30:04 +0800 Subject: [PATCH 054/328] Access Control: WIP implement RBAC and storage --- .../Management/Access/AccessControl.cs | 346 +++++++++--------- .../Management/Access/IAccessControl.cs | 25 ++ .../Config/AccessControl,Config.cs | 141 +++++++ src/Certify.Models/Config/AccessControl.cs | 126 +++++++ .../Providers/IAccessControlStore.cs | 16 + .../DataStores/AccessControlDataStoreTests.cs | 192 ++++++++++ .../ManagedItemDataStoreTests.cs | 4 +- .../StoredCredentialsDataStoreTests.cs | 4 +- .../AccessControlTests.cs | 225 +++++++----- 9 files changed, 808 insertions(+), 271 deletions(-) create mode 100644 src/Certify.Core/Management/Access/IAccessControl.cs create mode 100644 src/Certify.Models/Config/AccessControl,Config.cs create mode 100644 src/Certify.Models/Config/AccessControl.cs create mode 100644 src/Certify.Models/Providers/IAccessControlStore.cs create mode 100644 src/Certify.Tests/Certify.Core.Tests.Integration/DataStores/AccessControlDataStoreTests.cs rename src/Certify.Tests/Certify.Core.Tests.Integration/{ => DataStores}/ManagedItemDataStoreTests.cs (99%) rename src/Certify.Tests/Certify.Core.Tests.Integration/{ => DataStores}/StoredCredentialsDataStoreTests.cs (98%) diff --git a/src/Certify.Core/Management/Access/AccessControl.cs b/src/Certify.Core/Management/Access/AccessControl.cs index 4c79e939a..6a26dafe8 100644 --- a/src/Certify.Core/Management/Access/AccessControl.cs +++ b/src/Certify.Core/Management/Access/AccessControl.cs @@ -1,96 +1,21 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Security.Cryptography; using System.Threading.Tasks; +using Certify.Models.Config.AccessControl; using Certify.Models.Providers; +using Certify.Providers; namespace Certify.Core.Management.Access { - public enum SecurityPrincipleType - { - User = 1, - Application = 2 - } - - public class SecurityPrinciple - { - public string Id { get; set; } - public string Username { get; set; } - public string Password { get; set; } - public string Email { get; set; } - public string Description { get; set; } - - /// - /// If true, user is a mapping to an external AD/LDAP group or user - /// - public bool IsDirectoryMapping { get; set; } - - public List SystemRoleIds { get; set; } - public SecurityPrincipleType PrincipleType { get; set; } - - public string AuthKey { get; set; } - } - - public class StandardRoles - { - public static Role Administrator { get; } = new Role("sysadmin", "Administrator", "Certify Server Administrator"); - public static Role DomainOwner { get; } = new Role("domain_owner", "Domain Owner", "Controls certificate access for a given domain"); - public static Role DomainRequestor { get; } = new Role("subdomain_requestor", "Subdomain Requestor", "Can request new certs for subdomains on a given domain"); - public static Role CertificateConsumer { get; } = new Role("cert_consumer", "Certificate Consumer", "User of a given certificate"); - - } - - public class ResourceTypes + public class AccessControl : IAccessControl { - public static string System { get; } = "system"; - public static string Domain { get; } = "domain"; - } - - public class Role - { - public string Id { get; set; } - public string Title { get; set; } - public string Description { get; set; } - - public Role() { } - public Role(string id, string title, string description) - { - Id = id; - Title = title; - Description = description; - } - } - - public class ResourceAssignedRole - { - public string PrincipleId { get; set; } - public string RoleId { get; set; } - } - /// - /// Define a domain or resource and who the controlling users are - /// - public class ResourceProfile - { - public string Id { get; set; } = new Guid().ToString(); - public string ResourceType { get; set; } - public string Identifier { get; set; } - public List AssignedRoles { get; set; } - // public List DefaultChallenges { get; set; } - } - - public interface IObjectStore - { - Task Save(string id, object item); - Task Load(string id); - } - - public class AccessControl - { - private IObjectStore _store; + private IAccessControlStore _store; private ILog _log; - public AccessControl(ILog log, IObjectStore store) + public AccessControl(ILog log, IAccessControlStore store) { _store = store; _log = log; @@ -98,55 +23,48 @@ public AccessControl(ILog log, IObjectStore store) public async Task> GetSystemRoles() { - return await Task.FromResult(new List { StandardRoles.Administrator, - StandardRoles.DomainOwner, + StandardRoles.IdentifierController, StandardRoles.CertificateConsumer }); } - public async Task> GetSecurityPrinciples() + public async Task> GetSecurityPrinciples(string contextUserId) { - return await _store.Load>("principles"); + return await _store.GetItems(nameof(SecurityPrinciple)); } - public async Task AddSecurityPrinciple(SecurityPrinciple principle, string contextUserId, bool bypassIntegrityCheck = false) + public async Task AddSecurityPrinciple(string contextUserId, SecurityPrinciple principle, bool bypassIntegrityCheck = false) { - if (!await IsPrincipleInRole(contextUserId, StandardRoles.Administrator.Id, contextUserId) && !bypassIntegrityCheck) + if (!bypassIntegrityCheck && !await IsPrincipleInRole(contextUserId, contextUserId, StandardRoles.Administrator.Id)) { _log?.Warning($"User {contextUserId} attempted to use AddSecurityPrinciple [{principle?.Id}] without being in required role."); return false; } - var principles = await GetSecurityPrinciples(); - principles.Add(principle); - await _store.Save>("principles", principles); + if (!string.IsNullOrWhiteSpace(principle.Password)) + { + principle.Password = HashPassword(principle.Password); + } + + await _store.Add(nameof(SecurityPrinciple), principle); _log?.Information($"User {contextUserId} added security principle [{principle?.Id}] {principle?.Username}"); return true; } - public async Task UpdateSecurityPrinciple(SecurityPrinciple principle, string contextUserId) + public async Task UpdateSecurityPrinciple(string contextUserId, SecurityPrinciple principle) { - if (!await IsPrincipleInRole(contextUserId, StandardRoles.Administrator.Id, contextUserId)) + if (!await IsPrincipleInRole(contextUserId, contextUserId, StandardRoles.Administrator.Id)) { _log?.Warning($"User {contextUserId} attempted to use UpdateSecurityPrinciple [{principle?.Id}] without being in required role."); return false; } - var principles = await GetSecurityPrinciples(); - - var existing = principles.Find(p => p.Id == principle.Id); - if (existing != null) - { - principles.Remove(existing); - } - - principles.Add(principle); - await _store.Save>("principles", principles); + await _store.Update(nameof(SecurityPrinciple), principle); _log?.Information($"User {contextUserId} updated security principle [{principle?.Id}] {principle?.Username}"); return true; @@ -155,91 +73,113 @@ public async Task UpdateSecurityPrinciple(SecurityPrinciple principle, str /// /// delete a single security principle /// - /// /// + /// /// - public async Task DeleteSecurityPrinciple(string id, string contextUserId) + public async Task DeleteSecurityPrinciple(string contextUserId, string id, bool allowSelfDelete = false) { - if (!await IsPrincipleInRole(contextUserId, StandardRoles.Administrator.Id, contextUserId)) + if (!await IsPrincipleInRole(contextUserId, contextUserId, StandardRoles.Administrator.Id)) { _log?.Warning($"User {contextUserId} attempted to use DeleteSecurityPrinciple [{id}] without being in required role."); return false; } - if (id == contextUserId) + if (!allowSelfDelete && id == contextUserId) { _log?.Information($"User {contextUserId} tried to delete themselves."); return false; } - var principles = await GetSecurityPrinciples(); - - var existing = principles.Find(p => p.Id == id); - if (existing != null) - { - principles.Remove(existing); - } - - await _store.Save>("principles", principles); + var existing = await GetSecurityPrinciple(contextUserId, id); - // TODO: remove assigned roles within all resource profiles + await _store.Delete(nameof(SecurityPrinciple), id); - var allResourceProfiles = await GetResourceProfiles(id, contextUserId); - foreach (var r in allResourceProfiles) - { - if (r.AssignedRoles.Any(ro => ro.PrincipleId == id)) - { - var newAssignedRoles = r.AssignedRoles.Where(ra => ra.PrincipleId != id).ToList(); - r.AssignedRoles = newAssignedRoles; - } - } - - await _store.Save>("resourceprofiles", allResourceProfiles); + // TODO: remove assigned roles _log?.Information($"User {contextUserId} deleted security principle [{id}] {existing?.Username}"); return true; } - public async Task IsAuthorised(string principleId, string roleId, string resourceType, string identifier, string contextUserId) + public async Task GetSecurityPrinciple(string contextUserId, string id) { - var resourceProfiles = await GetResourceProfiles(principleId, contextUserId); + return await _store.Get(nameof(SecurityPrinciple), id); + } - if (resourceProfiles.Any(r => r.ResourceType == resourceType && r.Identifier == identifier && r.AssignedRoles.Any(a => a.PrincipleId == principleId && a.RoleId == roleId))) - { - // principle has an exactly matching role granted for this resource - return true; - } + public async Task IsAuthorised(string contextUserId, string principleId, string roleId, string resourceType, string actionId, string identifier) + { + // to determine is a principle has access to perform a particular action + // for each group the principle is part of - if (resourceType == ResourceTypes.Domain && !identifier.Trim().StartsWith("*") && identifier.Contains(".")) - { - // get wildcard for respective domain identifier - var identifierComponents = identifier.Split('.'); + var allAssignedRoles = await _store.GetItems(nameof(AssignedRole)); + + var spAssigned = allAssignedRoles.Where(a => a.SecurityPrincipleId == principleId); + + var allRoles = await _store.GetItems(nameof(Role)); + + var spAssignedRoles = allRoles.Where(r => spAssigned.Any(t => t.RoleId == r.Id)); + + var spSpecificAssignedRoles = spAssigned.Where(a => spAssignedRoles.Any(r => r.Id == a.RoleId)); - var wildcard = "*." + string.Join(".", identifierComponents.Skip(1)); + var allPolicies = await _store.GetItems(nameof(ResourcePolicy)); - if (resourceProfiles.Any(r => r.ResourceType == resourceType && r.Identifier == wildcard && r.AssignedRoles.Any(a => a.PrincipleId == principleId && a.RoleId == roleId))) + var spAssignedPolicies = allPolicies.Where(r => spAssignedRoles.Any(p => p.Policies.Contains(r.Id))); + + if (spAssignedPolicies.Any(a => a.ResourceActions.Contains(actionId))) + { + // if and of the service principles assigned roles are restricted by the type of resource type, check for identifier matches (e.g. role assignment restricted on domains ) + if (spSpecificAssignedRoles.Any(a => a.IncludedResources.Any(r => r.ResourceType == resourceType))) + { + var allIncludedResources = spSpecificAssignedRoles.SelectMany(a => a.IncludedResources).Distinct(); + + if (resourceType == ResourceTypes.Domain && !identifier.Trim().StartsWith("*") && identifier.Contains(".")) + { + // get wildcard for respective domain identifier + var identifierComponents = identifier.Split('.'); + + var wildcard = "*." + string.Join(".", identifierComponents.Skip(1)); + + // search for matching identifier + + foreach (var includedResource in allIncludedResources) + { + if (includedResource.ResourceType == resourceType && includedResource.Identifier == wildcard) + { + return true; + } + else if (includedResource.ResourceType == resourceType && includedResource.Identifier == identifier) + { + return true; + } + } + } + + // no match + return false; + } + else { - // principle has an matching role granted for this resource as a wildcard return true; } } - - return false; + else + { + return false; + } } /// /// Check security principle is in a given role at the system level /// + /// /// /// - /// /// - public async Task IsPrincipleInRole(string id, string roleId, string contextUserId) + public async Task IsPrincipleInRole(string contextUserId, string id, string roleId) { - var resourceProfiles = await GetResourceProfiles(id, contextUserId); + var assignedRoles = await _store.GetItems(nameof(AssignedRole)); - if (resourceProfiles.Any(r => r.ResourceType == ResourceTypes.System && r.AssignedRoles.Any(a => a.PrincipleId == id && a.RoleId == roleId))) + if (assignedRoles.Any(a => a.RoleId == roleId && a.SecurityPrincipleId == id)) { return true; } @@ -249,46 +189,110 @@ public async Task IsPrincipleInRole(string id, string roleId, string conte } } - /// - /// return list of resources this user has some access to - /// - /// - /// - public async Task> GetResourceProfiles(string userId, string contextUserId) + public async Task AddResourcePolicy(string contextUserId, ResourcePolicy resourceProfile, bool bypassIntegrityCheck = false) { - var allResourceProfiles = await _store.Load>("resourceprofiles"); + if (!bypassIntegrityCheck && !await IsPrincipleInRole(contextUserId, contextUserId, StandardRoles.Administrator.Id)) + { + _log?.Warning($"User {contextUserId} attempted to use AddResourcePolicy [{resourceProfile.Id}] without being in required role."); + return false; + } + + await _store.Add(nameof(ResourcePolicy), resourceProfile); + + _log?.Information($"User {contextUserId} added resource policy [{resourceProfile.Id}]"); + return true; + } - if (userId != null) + public async Task UpdateSecurityPrinciplePassword(string contextUserId, string id, string oldpassword, string newpassword) + { + if (id != contextUserId && !await IsPrincipleInRole(contextUserId, contextUserId, StandardRoles.Administrator.Id)) { - var filteredprofiles = allResourceProfiles.Where(r => r.AssignedRoles.Any(ra => ra.PrincipleId == userId)); + _log?.Warning("User {contextUserId} attempted to use updated password for [{username} - {id}] without being in required role."); + return false; + } - foreach (var f in filteredprofiles) - { - f.AssignedRoles = f.AssignedRoles.Where(a => a.PrincipleId == userId).ToList(); - } + var updated = false; + + var principle = await GetSecurityPrinciple(contextUserId, id); - return filteredprofiles.ToList(); + if (IsPasswordValid(oldpassword, principle.Password)) + { + principle.Password = HashPassword(newpassword); + updated = await UpdateSecurityPrinciple(contextUserId, principle); } else { - return allResourceProfiles; + _log?.Information("Previous password did not match while updating security principle password", contextUserId, principle.Username, principle.Id); } + + if (updated) + { + _log?.Information("User {contextUserId} updated password for [{username} - {id}]", contextUserId, principle.Username, principle.Id); + } + else + { + + _log?.Warning("User {contextUserId} failed to update password for [{username} - {id}]", contextUserId, principle.Username, principle.Id); + } + + return updated; } - public async Task AddResourceProfile(ResourceProfile resourceProfile, string contextUserId, bool bypassIntegrityCheck = false) + public bool IsPasswordValid(string password, string currentHash) { - if (!await IsPrincipleInRole(contextUserId, StandardRoles.Administrator.Id, contextUserId) && !bypassIntegrityCheck) + var components = currentHash.Split('.'); + + // hash provided password with same salt to compare result + return currentHash == HashPassword(password, components[1]); + } + + /// + /// Hash password, optionally using the provided salt or generating new salt + /// + /// + /// + /// + public string HashPassword(string password, string saltString = null) + { + var iterations = 600000; + var salt = new byte[24]; + + if (saltString == null) { - _log?.Warning($"User {contextUserId} attempted to use AddResourceProfile [{resourceProfile.Identifier}] without being in required role."); - return false; + RandomNumberGenerator.Create().GetBytes(salt); } + else + { + salt = Convert.FromBase64String(saltString); + } +#if NET8_0_OR_GREATER + var pbkdf2 = new Rfc2898DeriveBytes(password, salt, iterations, HashAlgorithmName.SHA512); +#else + var pbkdf2 = new Rfc2898DeriveBytes(password, salt, iterations); +#endif - var profiles = await GetResourceProfiles(null, contextUserId); - profiles.Add(resourceProfile); - await _store.Save>("resourceprofiles", profiles); + var hash = pbkdf2.GetBytes(24); - _log?.Information($"User {contextUserId} added resource profile [{resourceProfile.Identifier}]"); - return true; + var hashed = $"v1.{Convert.ToBase64String(salt)}.{Convert.ToBase64String(hash)}"; + + return hashed; + } + + public Task> GetSecurityPrincipleResourcePolicies(string contextUserId, string userId) => throw new NotImplementedException(); + + public async Task AddRole(Role r) + { + await _store.Add(nameof(Role), r); + } + + public async Task AddAssignedRole(AssignedRole r) + { + await _store.Add(nameof(AssignedRole), r); + } + + public async Task AddAction(ResourceAction action) + { + await _store.Add(nameof(ResourceAction), action); } } } diff --git a/src/Certify.Core/Management/Access/IAccessControl.cs b/src/Certify.Core/Management/Access/IAccessControl.cs new file mode 100644 index 000000000..a6593f4a5 --- /dev/null +++ b/src/Certify.Core/Management/Access/IAccessControl.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Certify.Models.Config.AccessControl; + +namespace Certify.Core.Management.Access +{ + public interface IAccessControl + { + Task AddResourcePolicy(string contextUserId, ResourcePolicy resourceProfile, bool bypassIntegrityCheck = false); + Task AddSecurityPrinciple(string contextUserId, SecurityPrinciple principle, bool bypassIntegrityCheck = false); + Task DeleteSecurityPrinciple(string contextUserId, string id, bool allowSelfDelete = false); + Task> GetSecurityPrincipleResourcePolicies(string contextUserId, string userId); + Task> GetSecurityPrinciples(string contextUserId); + + /// + /// Get the list of standard roles built-in to the system + /// + /// + Task> GetSystemRoles(); + Task IsAuthorised(string contextUserId, string principleId, string roleId, string resourceType, string actionId, string identifier); + Task IsPrincipleInRole(string contextUserId, string id, string roleId); + Task UpdateSecurityPrinciple(string contextUserId, SecurityPrinciple principle); + Task UpdateSecurityPrinciplePassword(string contextUserId, string id, string oldpassword, string newpassword); + } +} diff --git a/src/Certify.Models/Config/AccessControl,Config.cs b/src/Certify.Models/Config/AccessControl,Config.cs new file mode 100644 index 000000000..3e8f0cdd3 --- /dev/null +++ b/src/Certify.Models/Config/AccessControl,Config.cs @@ -0,0 +1,141 @@ +using System; +using System.Collections.Generic; + +namespace Certify.Models.Config.AccessControl +{ + public enum SecurityPrincipleType + { + User = 1, + Application = 2, + Group + } + + public enum SecurityPermissionType + { + ALLOW = 1, + DENY = 0 + } + + public class StandardRoles + { + + public static Role Administrator { get; } = new Role("sysadmin", "Administrator", "Certify Server Administrator", policies: new List { + "managed_item_admin", + "access_admin", + "storedcredential_admin" + }); + public static Role CertificateManager { get; } = new Role("cert_manager", "Certificate Manager", "Can manage and administer all certificates"); + public static Role CertificateConsumer { get; } = new Role("cert_consumer", "Certificate Consumer", "User of a given certificate", policies: new List { "certificate_consumer" }); + public static Role StoredCredentialConsumer { get; } = new Role("storedcredential_consumer", "Stored Credential Fetch Consumer", "Can fetch a decrypted stored credential", policies: new List { "storedcredential_fetch" }); + public static Role IdentifierController { get; } = new Role("identifier_controller", "Subject Identifier Controller", "Controls certificate access for a given domain/identifier"); + public static Role IdentifierRequestor { get; } = new Role("identifier_requestor", "Subject Identifier Requestor", "Can request new certs for subdomains/subresources on a given domain/identifier "); + } + + public class StandardProviders + { + /// + /// Identity is stored in the app/service database + /// + public const string INTERNAL = "INTERNAL"; + + /// + /// Identity is provided by the OS + /// + public const string OS = "OS"; + + /// + /// Identity is stored in LDAP/AD + /// + public const string LDAP = "LDAP"; + + /// + /// Identity is provided by OpenID + /// + public const string OID = "OID"; + } + + public class ResourceTypes + { + public static string System { get; } = "system"; + public static string Domain { get; } = "domain"; + public static string ManagedItem { get; } = "manageditem"; + public static string Certificate { get; } = "certificate"; + public static string StoredCredential { get; } = "storedcredential"; + public static string CertificateAuthority { get; } = "ca"; + } + + public static class Policies + { + public static List GetStandardResourceActions() + { + return new List { + + new ResourceAction("certificate_download", "Certificate Download", ResourceTypes.Certificate), + + new ResourceAction("storedcredential_add", "Add New Stored Credential", ResourceTypes.StoredCredential), + new ResourceAction("storedcredential_update", "Update Stored Credential", ResourceTypes.StoredCredential), + new ResourceAction("storedcredential_delete", "Delete Stored Credential", ResourceTypes.StoredCredential), + new ResourceAction("storedcredential_fetch", "Fetch Decrypted Stored Credential", ResourceTypes.StoredCredential), + + new ResourceAction("securityprinciple_add", "Add New Security Principle", ResourceTypes.System), + new ResourceAction("securityprinciple_update", "UPdate Security Principles", ResourceTypes.System), + new ResourceAction("securityprinciple_changepassword", "Update Security Principle Passwords", ResourceTypes.System), + new ResourceAction("securityprinciple_delete", "Delete Security Principle", ResourceTypes.System), + + new ResourceAction("manageditem_requester", "Request New Managed Items", ResourceTypes.ManagedItem), + new ResourceAction("manageditem_add", "Add Managed Items", ResourceTypes.ManagedItem), + new ResourceAction("manageditem_update", "Update Managed Items", ResourceTypes.ManagedItem), + new ResourceAction("manageditem_delete", "Delete Managed Items", ResourceTypes.ManagedItem), + new ResourceAction("manageditem_test", "Test Managed Item Renewal Checks", ResourceTypes.ManagedItem), + new ResourceAction("manageditem_renew", "Request/Renew Managed Items", ResourceTypes.ManagedItem), + new ResourceAction("manageditem_updatetasks", "Add or Update Managed Item Tasks", ResourceTypes.ManagedItem), + new ResourceAction("manageditem_updatescript", "Add or Update Managed Item Deployment Scripts", ResourceTypes.ManagedItem), + new ResourceAction("manageditem_log", "View/Download Managed Item Log", ResourceTypes.ManagedItem), + }; + } + + public static List GetStandardPolicies() + { + return new List { + new ResourcePolicy{ Id="managed_item_admin", Title="Managed Item Administration", SecurityPermissionType= SecurityPermissionType.ALLOW, + ResourceActions= new List{ + "manageditem_add", + "manageditem_update", + "manageditem_delete", + "manageditem_test", + "manageditem_renew", + "manageditem_updatetasks", + "manageditem_updatescript", + "manageditem_log", + } + }, + new ResourcePolicy{ Id="access_admin", Title="Access Control Administration", SecurityPermissionType= SecurityPermissionType.ALLOW, + ResourceActions= new List{ + "securityprinciple_add", + "securityprinciple_update", + "securityprinciple_changepassword", + "securityprinciple_delete" + } + }, + new ResourcePolicy{ Id="certificate_consumer", Title="Consume Certificates", SecurityPermissionType= SecurityPermissionType.ALLOW, + ResourceActions= new List{ + "certificate_download", + "certificate_key_download" + } + }, + new ResourcePolicy{ Id="storedcredential_admin", Title="Stored Credential Administration", SecurityPermissionType= SecurityPermissionType.ALLOW, + ResourceActions= new List{ + "storedcredential_add", + "storedcredential_update", + "storedcredential_delete" + } + }, + new ResourcePolicy{ Id="storedcredential_fetch", Title="Stored Credential Fetch", Description="Provides access to fetch a decrypted stored credential.", SecurityPermissionType= SecurityPermissionType.ALLOW, + ResourceActions= new List{ + "storedcredential_fetch" + } + } + }; + } + } +} diff --git a/src/Certify.Models/Config/AccessControl.cs b/src/Certify.Models/Config/AccessControl.cs new file mode 100644 index 000000000..4a8a4830d --- /dev/null +++ b/src/Certify.Models/Config/AccessControl.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections.Generic; + +namespace Certify.Models.Config.AccessControl +{ + public class AccessStoreItem + { + public AccessStoreItem() + { + Id = Guid.NewGuid().ToString(); + } + public string Id { get; set; } + public string Title { get; set; } = string.Empty; + public string Description { get; set; } = string.Empty; + public string ItemType { get; set; } = string.Empty; + } + + /// + /// A Security Principle is a user or service account which can be assigned roles and other permissions + /// + public class SecurityPrinciple : AccessStoreItem + { + + public string? Username { get; set; } + public string? Password { get; set; } + public string? Email { get; set; } + + /// + /// Provider e.g. if identifier is a mapping to an external AD/LDAP group or user + /// + public string? Provider { get; set; } + + /// + /// If principle is externally controlled, this is the identifier from the external system + /// + public string? ExternalIdentifier { get; set; } + + public SecurityPrincipleType? PrincipleType { get; set; } + + public string? AuthKey { get; set; } + } + + public class Role : AccessStoreItem + { + public List Policies { get; set; } = new List(); + public Role(string id, string title, string description, List policies = null) + { + Id = id; + Title = title; + Description = description; + + if (policies != null) + { + Policies = policies; + } + } + } + + /// + /// A role assigned to a security principle + /// + public class AssignedRole : AccessStoreItem + { + /// + /// Defines the role to be assigned + /// + public string? RoleId { get; set; } + + /// + /// Specific security principle assigned to the role + /// + public string? SecurityPrincipleId { get; set; } + + public List IncludedResources { get; set; } + } + + /// + /// Defines a restricted resource + /// + public class Resource : AccessStoreItem + { + /// + /// Type of this resource + /// + public string? ResourceType { get; set; } + + /// + /// Identifier for this resource, can include wildcards for domains etc + /// + public string? Identifier { get; set; } + } + + public class ResourcePolicy : AccessStoreItem + { + + /// + /// Whether policy is allow or deny for the set of actions + /// + public SecurityPermissionType SecurityPermissionType { get; set; } = SecurityPermissionType.DENY; + + /// + /// List of actions to apply to this policy + /// + public List ResourceActions { get; set; } = new List(); + + /// + /// If true, this policy requires on or more specific identified resources and cannot be applied to all resources + /// + public bool IsResourceSpecific { get; set; } + } + + /// + /// Specific system action which may be allowed/disallowed on a specific type of resource + /// + public class ResourceAction : AccessStoreItem + { + public ResourceAction(string id, string title, string resourceType) + { + Id = id; + Title = title; + ResourceType = resourceType; + } + + public string? ResourceType { get; set; } + } +} diff --git a/src/Certify.Models/Providers/IAccessControlStore.cs b/src/Certify.Models/Providers/IAccessControlStore.cs new file mode 100644 index 000000000..2a2a70408 --- /dev/null +++ b/src/Certify.Models/Providers/IAccessControlStore.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace Certify.Providers +{ + public interface IAccessControlStore + { + Task Get(string itemType, string id); + Task Add(string itemType, T item); + Task Update(string itemType, T item); + Task Delete(string itemType, string id); + Task> GetItems(string itemType); + } +} diff --git a/src/Certify.Tests/Certify.Core.Tests.Integration/DataStores/AccessControlDataStoreTests.cs b/src/Certify.Tests/Certify.Core.Tests.Integration/DataStores/AccessControlDataStoreTests.cs new file mode 100644 index 000000000..dc9b5492c --- /dev/null +++ b/src/Certify.Tests/Certify.Core.Tests.Integration/DataStores/AccessControlDataStoreTests.cs @@ -0,0 +1,192 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Certify.Core.Management.Access; +using Certify.Datastore.Postgres; +using Certify.Datastore.SQLServer; +using Certify.Management; +using Certify.Models.Config.AccessControl; +using Certify.Providers; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Certify.Core.Tests.DataStores +{ + [TestClass] + public class AccessControlDataStoreTests + { + private string _storeType = "sqlite"; + private const string TEST_PATH = "Tests\\credentials"; + + public static IEnumerable TestDataStores + { + get + { + return new[] + { + new object[] { "sqlite" }, + //new object[] { "postgres" }, + //new object[] { "sqlserver" } + }; + } + } + + private IAccessControlStore GetStore(string storeType = null) + { + if (storeType == null) + { + storeType = _storeType; + } + + if (storeType == "sqlite") + { + return new SQLiteAccessControlStore(storageSubfolder: TEST_PATH); + } + /* else if (storeType == "postgres") + { + return new PostgresCredentialStore(Environment.GetEnvironmentVariable("CERTIFY_TEST_POSTGRES")); + } + else if (storeType == "sqlserver") + { + return new SQLServerCredentialStore(Environment.GetEnvironmentVariable("CERTIFY_TEST_SQLSERVER")); + }*/ + else + { + throw new ArgumentOutOfRangeException(nameof(storeType), "Unsupported store type " + storeType); + } + } + + [TestMethod] + [DynamicData(nameof(TestDataStores))] + public async Task TestStoreSecurityPrinciple(string storeType) + { + var store = GetStore(storeType ?? _storeType); + + var sp = new SecurityPrinciple + { + Email = "test@test.com", + PrincipleType = SecurityPrincipleType.User, + Username = "test", + Provider = StandardProviders.INTERNAL + }; + + try + { + await store.Add(nameof(SecurityPrinciple), sp); + + var list = await store.GetItems(nameof(SecurityPrinciple)); + + Assert.IsTrue(list.Any(l => l.Id == sp.Id), "Security Principle retrieved"); + } + finally + { + // cleanup + await store.Delete(nameof(SecurityPrinciple), sp.Id); + } + } + + [TestMethod] + [DynamicData(nameof(TestDataStores))] + public async Task TestStoreRole(string storeType) + { + var store = GetStore(storeType ?? _storeType); + + var role1 = new Role("test", "Test Role", "A test role"); + var role2 = new Role("test2", "Test Role 2", "A test role 2"); + + try + { + await store.Add(nameof(Role), role1); + await store.Add(nameof(Role), role2); + + var item = await store.Get(nameof(Role), role1.Id); + + Assert.IsTrue(item.Id == role1.Id, "Role retrieved"); + } + finally + { + // cleanup + await store.Delete(nameof(Role), role1.Id); + await store.Delete(nameof(Role), role2.Id); + } + } + + [TestMethod] + public void TestStorePasswordHashing() + { + var store = GetStore(_storeType); + var access = new AccessControl(null, store); + + var firstHash = access.HashPassword("secret"); + + Assert.IsNotNull(firstHash); + + Assert.IsTrue(access.IsPasswordValid("secret", firstHash)); + } + + [TestMethod] + [DynamicData(nameof(TestDataStores))] + public async Task TestStoreGeneralAccessControl(string storeType) + { + + var store = GetStore(storeType ?? _storeType); + + var access = new AccessControl(null, store); + + var adminSp = new SecurityPrinciple + { + Id = "admin_01", + Email = "admin@test.com", + Description = "Primary test admin", + PrincipleType = SecurityPrincipleType.User, + Username = "admin01", + Provider = StandardProviders.INTERNAL + }; + + var consumerSp = new SecurityPrinciple + { + Id = "dev_01", + Email = "dev_test01@test.com", + Description = "Consumer test", + PrincipleType = SecurityPrincipleType.User, + Username = "dev01", + Password = "oldpassword", + Provider = StandardProviders.INTERNAL + }; + + try + { + // add first admin security principle, bypass role check as there is no user to check yet + + await access.AddSecurityPrinciple(adminSp.Id, adminSp, bypassIntegrityCheck: true); + + // add second security principle, enforcing role checks for calling user + await access.AddSecurityPrinciple(adminSp.Id, consumerSp); + + var list = await access.GetSecurityPrinciples(adminSp.Id); + + Assert.IsTrue(list.Any()); + + // get updated sp so that password is hashed for comparison check + consumerSp = await access.GetSecurityPrinciple(adminSp.Id, consumerSp.Id); + + Assert.IsTrue(access.IsPasswordValid("oldpassword", consumerSp.Password)); + + var updated = await access.UpdateSecurityPrinciplePassword(adminSp.Id, consumerSp.Id, "oldpassword", "newpassword"); + + Assert.IsTrue(updated, "SP password should have been updated OK"); + + consumerSp = await access.GetSecurityPrinciple(adminSp.Id, consumerSp.Id); + + Assert.IsFalse(access.IsPasswordValid("oldpassword", consumerSp.Password), "Old password should no longer be valid"); + + Assert.IsTrue(access.IsPasswordValid("newpassword", consumerSp.Password), "New password should be valid"); + } + finally + { + await access.DeleteSecurityPrinciple(adminSp.Id, consumerSp.Id); + await access.DeleteSecurityPrinciple(adminSp.Id, adminSp.Id, allowSelfDelete: true); + } + } + } +} diff --git a/src/Certify.Tests/Certify.Core.Tests.Integration/ManagedItemDataStoreTests.cs b/src/Certify.Tests/Certify.Core.Tests.Integration/DataStores/ManagedItemDataStoreTests.cs similarity index 99% rename from src/Certify.Tests/Certify.Core.Tests.Integration/ManagedItemDataStoreTests.cs rename to src/Certify.Tests/Certify.Core.Tests.Integration/DataStores/ManagedItemDataStoreTests.cs index 4a878b0eb..7e71f821a 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Integration/ManagedItemDataStoreTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Integration/DataStores/ManagedItemDataStoreTests.cs @@ -11,7 +11,7 @@ using Certify.Providers; using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace Certify.Core.Tests +namespace Certify.Core.Tests.DataStores { [TestClass] public class ManagedItemDataStoreTests @@ -58,7 +58,7 @@ private IManagedItemStore GetManagedItemStore(string storeType = null) } else { - throw new ArgumentOutOfRangeException(nameof(storeType), "Unsupport store type " + storeType); + throw new ArgumentOutOfRangeException(nameof(storeType), "Unsupported store type " + storeType); } } diff --git a/src/Certify.Tests/Certify.Core.Tests.Integration/StoredCredentialsDataStoreTests.cs b/src/Certify.Tests/Certify.Core.Tests.Integration/DataStores/StoredCredentialsDataStoreTests.cs similarity index 98% rename from src/Certify.Tests/Certify.Core.Tests.Integration/StoredCredentialsDataStoreTests.cs rename to src/Certify.Tests/Certify.Core.Tests.Integration/DataStores/StoredCredentialsDataStoreTests.cs index 4915a7e3d..ad8884431 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Integration/StoredCredentialsDataStoreTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Integration/DataStores/StoredCredentialsDataStoreTests.cs @@ -8,7 +8,7 @@ using Certify.Models.Config; using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace Certify.Core.Tests +namespace Certify.Core.Tests.DataStores { [TestClass] public class StoredCredentialsDataStoreTests @@ -50,7 +50,7 @@ private ICredentialsManager GetCredentialManager(string storeType = null) } else { - throw new ArgumentOutOfRangeException(nameof(storeType), "Unsupport store type " + storeType); + throw new ArgumentOutOfRangeException(nameof(storeType), "Unsupported store type " + storeType); } } diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/AccessControlTests.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/AccessControlTests.cs index 39bcdeec8..3bb72c655 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/AccessControlTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/AccessControlTests.cs @@ -1,57 +1,77 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Certify.Core.Management.Access; using Certify.Models; +using Certify.Models.Config.AccessControl; +using Certify.Providers; using Microsoft.VisualStudio.TestTools.UnitTesting; using Serilog; namespace Certify.Core.Tests.Unit { - - public class MemoryObjectStore : IObjectStore + public class MemoryObjectStore : IAccessControlStore { - private ConcurrentDictionary _store = new ConcurrentDictionary(); + private ConcurrentDictionary _store = new ConcurrentDictionary(); - public Task Load(string id) + public Task Add(string itemType, AccessStoreItem item) { - if (_store.TryGetValue(id, out var value)) - { - return Task.FromResult((T)value); - } - else - { - var empty = (T)Activator.CreateInstance(typeof(T)); + item.ItemType = itemType; + return Task.FromResult(_store.TryAdd(item.Id, item)); + } - return Task.FromResult(empty); - } + public Task Delete(string itemType, string id) + { + return Task.FromResult((_store.TryRemove(id, out _))); + } + + public Task> GetItems(string itemType) + { + var items = _store.Values + .Where((s => s.ItemType == itemType)) + .Select(s => (T)Convert.ChangeType(s, typeof(T))); + + return Task.FromResult((items.ToList())); + } + + public Task Get(string itemType, string id) + { + _store.TryGetValue(id, out var value); + return Task.FromResult((T)Convert.ChangeType(value, typeof(T))); + } + + public Task Add(string itemType, T item) + { + var o = item as AccessStoreItem; + o.ItemType = itemType; + return Task.FromResult(_store.TryAdd(o.Id, o)); } - public Task Save(string id, object item) + public Task Update(string itemType, T item) { - _ = _store.AddOrUpdate(id, item, (key, oldVal) => item); - return Task.FromResult(true); + var o = item as AccessStoreItem; + return Task.FromResult(_store.TryUpdate(o.Id, o, o)); } } [TestClass] public class AccessControlTests { - private List GetTestSecurityPrinciples() { - - return new List { - new SecurityPrinciple { + return new List + { + new SecurityPrinciple + { Id = "admin_01", Username = "admin", Description = "Administrator account", - Email="info@test.com", Password="ABCDEFG", - PrincipleType= SecurityPrincipleType.User, - SystemRoleIds=new List{ StandardRoles.Administrator.Id - } -}, + Email = "info@test.com", + Password = "ABCDEFG", + PrincipleType = SecurityPrincipleType.User + }, new SecurityPrinciple { Id = "domain_owner_01", @@ -59,65 +79,34 @@ private List GetTestSecurityPrinciples() Description = "Example domain owner", Email = "domains@test.com", Password = "ABCDEFG", - PrincipleType = SecurityPrincipleType.User, - SystemRoleIds = new List { StandardRoles.DomainOwner.Id } - }, - new SecurityPrinciple - { - Id = "devops_user_01", - Username = "devops_01", - Description = "Example devops user", - Email = "devops01@test.com", - Password = "ABCDEFG", - PrincipleType = SecurityPrincipleType.User, - SystemRoleIds = new List { StandardRoles.CertificateConsumer.Id, StandardRoles.DomainRequestor.Id } - }, - new SecurityPrinciple - { - Id = "devops_app_01", - Username = "devapp_01", - Description = "Example devops app domain consumer", - Email = "dev_app01@test.com", - Password = "ABCDEFG", - PrincipleType = SecurityPrincipleType.User, - SystemRoleIds = new List { StandardRoles.CertificateConsumer.Id } - } - }; - } - - public List GetTestResourceProfiles() - { - return new List { - new ResourceProfile { - ResourceType = ResourceTypes.System, - AssignedRoles = new List{ - new ResourceAssignedRole{ RoleId=StandardRoles.Administrator.Id, PrincipleId = "admin_01" }, - new ResourceAssignedRole{ RoleId=StandardRoles.CertificateConsumer.Id, PrincipleId = "devops_user_01" }, - new ResourceAssignedRole{ RoleId=StandardRoles.DomainRequestor.Id, PrincipleId = "devops_user_01" } - } + PrincipleType = SecurityPrincipleType.User }, - new ResourceProfile { - ResourceType = ResourceTypes.Domain, - Identifier = "example.com", - AssignedRoles= new List{ - new ResourceAssignedRole{ RoleId=StandardRoles.CertificateConsumer.Id, PrincipleId = "devops_user_01" }, - new ResourceAssignedRole{ RoleId=StandardRoles.DomainRequestor.Id, PrincipleId = "devops_user_01" } - } + new SecurityPrinciple + { + Id = "devops_user_01", + Username = "devops_01", + Description = "Example devops user", + Email = "devops01@test.com", + Password = "ABCDEFG", + PrincipleType = SecurityPrincipleType.User }, - new ResourceProfile { - ResourceType = ResourceTypes.Domain, - Identifier = "www.example.com", - AssignedRoles= new List{ - new ResourceAssignedRole{ RoleId=StandardRoles.CertificateConsumer.Id, PrincipleId = "devops_user_01" }, - new ResourceAssignedRole{ RoleId=StandardRoles.DomainRequestor.Id, PrincipleId = "devops_user_01" } - } + new SecurityPrinciple + { + Id = "devops_app_01", + Username = "devapp_01", + Description = "Example devops app domain consumer", + Email = "dev_app01@test.com", + Password = "ABCDEFG", + PrincipleType = SecurityPrincipleType.User }, - new ResourceProfile { - ResourceType = ResourceTypes.Domain, - Identifier = "*.microsoft.com", - AssignedRoles= new List{ - new ResourceAssignedRole{ RoleId=StandardRoles.CertificateConsumer.Id, PrincipleId = "devops_user_01" } - } + new SecurityPrinciple + { + Id = "[test]", + Username = "test administrator", + Description = "Example test administrator used as context user during test", + Email = "test_admin@test.com", + Password = "ABCDEFG", + PrincipleType = SecurityPrincipleType.User } }; } @@ -126,8 +115,8 @@ public List GetTestResourceProfiles() public async Task TestAccessControlChecks() { var log = new LoggerConfiguration() - .WriteTo.Debug() - .CreateLogger(); + .WriteTo.Debug() + .CreateLogger(); var loggy = new Loggy(log); @@ -139,46 +128,90 @@ public async Task TestAccessControlChecks() var allPrinciples = GetTestSecurityPrinciples(); foreach (var p in allPrinciples) { - _ = await access.AddSecurityPrinciple(p, contextUserId, bypassIntegrityCheck: true); + _ = await access.AddSecurityPrinciple(contextUserId, p, bypassIntegrityCheck: true); + } + + // setup known actions + var actions = Policies.GetStandardResourceActions(); + + foreach (var action in actions) + { + await access.AddAction(action); + } + + // setup policies with actions + + var policies = Policies.GetStandardPolicies(); + + // add policies to store + foreach (var r in policies) + { + _ = await access.AddResourcePolicy(contextUserId, r, bypassIntegrityCheck: true); } - // assign resource roles per principle - var allResourceProfiles = GetTestResourceProfiles(); - foreach (var r in allResourceProfiles) + // setup roles with policies + var roles = await access.GetSystemRoles(); + + foreach (var r in roles) + { + // add roles and policy assignments to store + await access.AddRole(r); + } + + // assign security principles to roles + var assignedRoles = new List { + // administrator + new AssignedRole{ + RoleId=StandardRoles.Administrator.Id, + SecurityPrincipleId="admin_01" + }, + // devops user in consumer role for a couple of specific domains + new AssignedRole{ + RoleId=StandardRoles.CertificateConsumer.Id, + SecurityPrincipleId="devops_user_01", + IncludedResources=new List{ + new Resource{ ResourceType=ResourceTypes.Domain, Identifier="www.example.com" }, + new Resource{ ResourceType=ResourceTypes.Domain, Identifier="*.microsoft.com" } + } + } + }; + + foreach (var r in assignedRoles) { - _ = await access.AddResourceProfile(r, contextUserId, bypassIntegrityCheck: true); + // add roles and policy assignments to store + await access.AddAssignedRole(r); } // assert - var hasAccess = await access.IsPrincipleInRole("admin_01", StandardRoles.Administrator.Id, contextUserId); + var hasAccess = await access.IsPrincipleInRole(contextUserId, "admin_01", StandardRoles.Administrator.Id); Assert.IsTrue(hasAccess, "User should be in role"); - hasAccess = await access.IsPrincipleInRole("admin_02", StandardRoles.Administrator.Id, contextUserId); + hasAccess = await access.IsPrincipleInRole(contextUserId, "admin_02", StandardRoles.Administrator.Id); Assert.IsFalse(hasAccess, "User should not be in role"); // check user can consume a cert for a given domain - var isAuthorised = await access.IsAuthorised("devops_user_01", StandardRoles.CertificateConsumer.Id, ResourceTypes.Domain, "www.example.com", contextUserId); + var isAuthorised = await access.IsAuthorised(contextUserId, "devops_user_01", StandardRoles.CertificateConsumer.Id, ResourceTypes.Domain, "certificate_download", "www.example.com"); Assert.IsTrue(isAuthorised, "User should be a cert consumer for this domain"); // check user can't consume a cert for a subdomain they haven't been granted - isAuthorised = await access.IsAuthorised("devops_user_01", StandardRoles.CertificateConsumer.Id, ResourceTypes.Domain, "secure.example.com", contextUserId); + isAuthorised = await access.IsAuthorised(contextUserId, "devops_user_01", StandardRoles.CertificateConsumer.Id, ResourceTypes.Domain, "certificate_download", "secure.example.com"); Assert.IsFalse(isAuthorised, "User should not be a cert consumer for this domain"); // check user can consume any subdomain via a granted wildcard - isAuthorised = await access.IsAuthorised("devops_user_01", StandardRoles.CertificateConsumer.Id, ResourceTypes.Domain, "random.microsoft.com", contextUserId); + isAuthorised = await access.IsAuthorised(contextUserId, "devops_user_01", StandardRoles.CertificateConsumer.Id, ResourceTypes.Domain, "certificate_download", "random.microsoft.com"); Assert.IsTrue(isAuthorised, "User should be a cert consumer for this subdomain via wildcard"); // check user can't consume a random wildcard - isAuthorised = await access.IsAuthorised("devops_user_01", StandardRoles.CertificateConsumer.Id, ResourceTypes.Domain, "* lkjhasdf98862364", contextUserId); + isAuthorised = await access.IsAuthorised(contextUserId, "devops_user_01", StandardRoles.CertificateConsumer.Id, ResourceTypes.Domain, "certificate_download", "* lkjhasdf98862364"); Assert.IsFalse(isAuthorised, "User should not be a cert consumer for random wildcard"); // check user can't consume a random wildcard - isAuthorised = await access.IsAuthorised("devops_user_01", StandardRoles.CertificateConsumer.Id, ResourceTypes.Domain, " lkjhasdf98862364.*.microsoft.com", contextUserId); + isAuthorised = await access.IsAuthorised(contextUserId, "devops_user_01", StandardRoles.CertificateConsumer.Id, ResourceTypes.Domain, "certificate_download", "lkjhasdf98862364.*.microsoft.com"); Assert.IsFalse(isAuthorised, "User should not be a cert consumer for random wildcard"); // random user should not be authorised - isAuthorised = await access.IsAuthorised("randomuser", StandardRoles.CertificateConsumer.Id, ResourceTypes.Domain, "random.microsoft.com", contextUserId); + isAuthorised = await access.IsAuthorised(contextUserId, "randomuser", StandardRoles.CertificateConsumer.Id, ResourceTypes.Domain, "certificate_download", "random.microsoft.com"); Assert.IsFalse(isAuthorised, "Unknown user should not be a cert consumer for this subdomain via wildcard"); } } From bdbeb638a652435faaef7432bcb987ed4186575d Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Fri, 22 Sep 2023 16:30:21 +0800 Subject: [PATCH 055/328] Cleanup --- .../DNS/DnsAPITest.AWSRoute53.cs | 8 ++++---- .../DNS/DnsAPITest.Azure.cs | 8 ++++---- .../DNS/DnsAPITest.Cloudflare.cs | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Certify.Tests/Certify.Core.Tests.Integration/DNS/DnsAPITest.AWSRoute53.cs b/src/Certify.Tests/Certify.Core.Tests.Integration/DNS/DnsAPITest.AWSRoute53.cs index 2513984b3..7d3fc8b7f 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Integration/DNS/DnsAPITest.AWSRoute53.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Integration/DNS/DnsAPITest.AWSRoute53.cs @@ -5,7 +5,7 @@ using Certify.Models.Providers; using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace Certify.Core.Tests +namespace Certify.Core.Tests.DNS { [TestClass] public class DnsAPITestAWSRoute53 : IntegrationTestBase @@ -40,7 +40,7 @@ public async Task TestCreateRecord() Assert.IsTrue(createResult.IsSuccess); stopwatch.Stop(); - System.Diagnostics.Debug.WriteLine($"Create DNS Record {createRequest.RecordName} took {stopwatch.Elapsed.TotalSeconds} seconds"); + Debug.WriteLine($"Create DNS Record {createRequest.RecordName} took {stopwatch.Elapsed.TotalSeconds} seconds"); return createRequest; } @@ -62,7 +62,7 @@ public async Task TestCreateRecords() // also create a duplicate var record2 = await TestCreateRecord(); - System.Diagnostics.Debug.WriteLine($"Cloudflare DNS should now have record {record1.RecordName} with values {record1.RecordValue} and {record2.RecordValue}"); + Debug.WriteLine($"Cloudflare DNS should now have record {record1.RecordName} with values {record1.RecordValue} and {record2.RecordValue}"); } [TestMethod, TestCategory("DNS")] @@ -80,7 +80,7 @@ public async Task TestDeleteRecord() var deleteResult = await _provider.DeleteRecord(deleteRequest); Assert.IsTrue(deleteResult.IsSuccess); - System.Diagnostics.Debug.WriteLine($"Delete DNS Record {deleteRequest.RecordName} took {stopwatch.Elapsed.TotalSeconds} seconds"); + Debug.WriteLine($"Delete DNS Record {deleteRequest.RecordName} took {stopwatch.Elapsed.TotalSeconds} seconds"); } [TestMethod, TestCategory("DNS")] diff --git a/src/Certify.Tests/Certify.Core.Tests.Integration/DNS/DnsAPITest.Azure.cs b/src/Certify.Tests/Certify.Core.Tests.Integration/DNS/DnsAPITest.Azure.cs index bb2ed8933..86d5669e6 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Integration/DNS/DnsAPITest.Azure.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Integration/DNS/DnsAPITest.Azure.cs @@ -5,7 +5,7 @@ using Certify.Models.Providers; using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace Certify.Core.Tests +namespace Certify.Core.Tests.DNS { [TestClass] [Ignore("Requires credential setup")] @@ -41,7 +41,7 @@ private async Task TestCreateRecord() Assert.IsTrue(createResult.IsSuccess); stopwatch.Stop(); - System.Diagnostics.Debug.WriteLine($"Create DNS Record {createRequest.RecordName} took {stopwatch.Elapsed.TotalSeconds} seconds"); + Debug.WriteLine($"Create DNS Record {createRequest.RecordName} took {stopwatch.Elapsed.TotalSeconds} seconds"); return createRequest; } @@ -63,7 +63,7 @@ public async Task TestCreateRecords() // also create a duplicate var record2 = await TestCreateRecord(); - System.Diagnostics.Debug.WriteLine($"Azure DNS should now have record {record1.RecordName} with values {record1.RecordValue} and {record2.RecordValue}"); + Debug.WriteLine($"Azure DNS should now have record {record1.RecordName} with values {record1.RecordValue} and {record2.RecordValue}"); } [TestMethod, TestCategory("DNS")] @@ -81,7 +81,7 @@ public async Task TestDeleteRecord() var deleteResult = await _provider.DeleteRecord(deleteRequest); Assert.IsTrue(deleteResult.IsSuccess); - System.Diagnostics.Debug.WriteLine($"Delete DNS Record {deleteRequest.RecordName} took {stopwatch.Elapsed.TotalSeconds} seconds"); + Debug.WriteLine($"Delete DNS Record {deleteRequest.RecordName} took {stopwatch.Elapsed.TotalSeconds} seconds"); } } } diff --git a/src/Certify.Tests/Certify.Core.Tests.Integration/DNS/DnsAPITest.Cloudflare.cs b/src/Certify.Tests/Certify.Core.Tests.Integration/DNS/DnsAPITest.Cloudflare.cs index 3657e7f57..dd3351d24 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Integration/DNS/DnsAPITest.Cloudflare.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Integration/DNS/DnsAPITest.Cloudflare.cs @@ -5,7 +5,7 @@ using Certify.Models.Providers; using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace Certify.Core.Tests +namespace Certify.Core.Tests.DNS { [TestClass] public class DnsAPITestCloudflare : IntegrationTestBase @@ -33,7 +33,7 @@ public async Task TestCreateRecord() Assert.IsTrue(createResult.IsSuccess); stopwatch.Stop(); - System.Diagnostics.Debug.WriteLine($"Create DNS Record {createRequest.RecordName} took {stopwatch.Elapsed.TotalSeconds} seconds"); + Debug.WriteLine($"Create DNS Record {createRequest.RecordName} took {stopwatch.Elapsed.TotalSeconds} seconds"); return createRequest; } @@ -62,7 +62,7 @@ public async Task TestCreateRecords() // also create a duplicate var record2 = await TestCreateRecord(); - System.Diagnostics.Debug.WriteLine($"Cloudflare DNS should now have record {record1.RecordName} with values {record1.RecordValue} and {record2.RecordValue}"); + Debug.WriteLine($"Cloudflare DNS should now have record {record1.RecordName} with values {record1.RecordValue} and {record2.RecordValue}"); } [TestMethod, TestCategory("DNS")] @@ -80,7 +80,7 @@ public async Task TestDeleteRecord() var deleteResult = await _provider.DeleteRecord(deleteRequest); Assert.IsTrue(deleteResult.IsSuccess); - System.Diagnostics.Debug.WriteLine($"Delete DNS Record {deleteRequest.RecordName} took {stopwatch.Elapsed.TotalSeconds} seconds"); + Debug.WriteLine($"Delete DNS Record {deleteRequest.RecordName} took {stopwatch.Elapsed.TotalSeconds} seconds"); } } } From 7ea95114f794897e954c84dafa797440331d921b Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Fri, 22 Sep 2023 16:30:33 +0800 Subject: [PATCH 056/328] Package updates --- .../Certify.Server.Api.Public.csproj | 4 ++++ .../Certify.Server.Core.csproj | 19 +++++-------------- .../Certify.Service.Worker.csproj | 10 +++------- .../Certify.UI.Tests.Integration.csproj | 2 +- 4 files changed, 13 insertions(+), 22 deletions(-) diff --git a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj index be2e1d996..a3be4f8b1 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj +++ b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj @@ -19,6 +19,10 @@ + + + + diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj index ea89f7285..0ee3ccf66 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj @@ -1,35 +1,26 @@ - net8.0 3648c5f3-f642-441e-979e-d4624cd39e49 Linux AnyCPU - - - - - - - - + + + + - - - - - + \ No newline at end of file diff --git a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Certify.Service.Worker.csproj b/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Certify.Service.Worker.csproj index 578719047..401a265e9 100644 --- a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Certify.Service.Worker.csproj +++ b/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Certify.Service.Worker.csproj @@ -9,17 +9,13 @@ portable true - - - - - + + + - - \ No newline at end of file diff --git a/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj b/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj index 3ff65e868..09aded4a4 100644 --- a/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj @@ -1,4 +1,4 @@ - + net8.0-windows Debug;Release; From ce109b24912437ebd62301caa0b330933215a8f5 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Wed, 4 Oct 2023 17:10:46 +0800 Subject: [PATCH 057/328] Package updates --- .../DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj | 2 +- src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj | 2 +- .../Certify.Server.Api.Public.Tests.csproj | 2 +- .../Certify.Shared.Extensions.csproj | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj index 59198bc1c..5bd45b48c 100644 --- a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj +++ b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj b/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj index 0337ec6d3..3ecb168ba 100644 --- a/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj +++ b/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj b/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj index b57ef4fe2..5d2eb357d 100644 --- a/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj +++ b/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj @@ -25,7 +25,7 @@ - + diff --git a/src/Certify.Shared.Extensions/Certify.Shared.Extensions.csproj b/src/Certify.Shared.Extensions/Certify.Shared.Extensions.csproj index b33b24a2c..7ac20406d 100644 --- a/src/Certify.Shared.Extensions/Certify.Shared.Extensions.csproj +++ b/src/Certify.Shared.Extensions/Certify.Shared.Extensions.csproj @@ -1,4 +1,4 @@ - + net462;netstandard2.0;net8.0 @@ -24,8 +24,8 @@ - - + + From ce1f8f53a8b6bb25eb828dc88d1e464378eabf57 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Fri, 13 Oct 2023 18:02:53 +0800 Subject: [PATCH 058/328] Package updates (.net 8 rc2 etc) --- src/Certify.Client/Certify.Client.csproj | 6 +++--- src/Certify.Models/Certify.Models.csproj | 4 ++-- .../DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj | 2 +- .../DNS/Azure/Plugin.DNS.Azure.csproj | 4 ++-- .../Certify.Server.Api.Public.Tests.csproj | 2 +- .../Certify.Server.Api.Public.csproj | 13 +++++++------ .../Certify.Server.Core/Certify.Server.Core.csproj | 8 ++++---- .../Certify.Service.Worker.csproj | 6 +++--- src/Certify.Service/Certify.Service.csproj | 2 +- .../Certify.Shared.Extensions.csproj | 4 +--- src/Certify.Shared/Certify.Shared.Core.csproj | 2 +- .../Certify.Core.Tests.Integration.csproj | 2 +- .../Certify.Core.Tests.Unit.csproj | 4 ++-- .../Certify.Service.Tests.Integration.csproj | 2 +- src/Certify.UI.Shared/Certify.UI.Shared.csproj | 2 +- src/Certify.UI/Certify.UI.csproj | 2 +- 16 files changed, 32 insertions(+), 33 deletions(-) diff --git a/src/Certify.Client/Certify.Client.csproj b/src/Certify.Client/Certify.Client.csproj index 658b60abb..d3d975772 100644 --- a/src/Certify.Client/Certify.Client.csproj +++ b/src/Certify.Client/Certify.Client.csproj @@ -16,9 +16,9 @@ - - - + + + diff --git a/src/Certify.Models/Certify.Models.csproj b/src/Certify.Models/Certify.Models.csproj index 94ae051fe..680a47cbf 100644 --- a/src/Certify.Models/Certify.Models.csproj +++ b/src/Certify.Models/Certify.Models.csproj @@ -21,7 +21,7 @@ all runtime; build; native; contentfiles; analyzers - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -30,7 +30,7 @@ all - + diff --git a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj index 5bd45b48c..7a5ef76b5 100644 --- a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj +++ b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj b/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj index 3ecb168ba..085c1960f 100644 --- a/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj +++ b/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj @@ -8,9 +8,9 @@ - + - + diff --git a/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj b/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj index 5d2eb357d..be4eca0ee 100644 --- a/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj +++ b/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj @@ -25,7 +25,7 @@ - + diff --git a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj index a3be4f8b1..76e40b01b 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj +++ b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj @@ -14,14 +14,15 @@ - - + + + - - - - + + + + diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj index 0ee3ccf66..d560be7b9 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj @@ -10,10 +10,10 @@ - - - - + + + + diff --git a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Certify.Service.Worker.csproj b/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Certify.Service.Worker.csproj index 401a265e9..70cd9e1bf 100644 --- a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Certify.Service.Worker.csproj +++ b/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Certify.Service.Worker.csproj @@ -11,9 +11,9 @@ - - - + + + diff --git a/src/Certify.Service/Certify.Service.csproj b/src/Certify.Service/Certify.Service.csproj index 2a81cda48..b587d2ff3 100644 --- a/src/Certify.Service/Certify.Service.csproj +++ b/src/Certify.Service/Certify.Service.csproj @@ -67,7 +67,7 @@ - + diff --git a/src/Certify.Shared.Extensions/Certify.Shared.Extensions.csproj b/src/Certify.Shared.Extensions/Certify.Shared.Extensions.csproj index 7ac20406d..a05eb2590 100644 --- a/src/Certify.Shared.Extensions/Certify.Shared.Extensions.csproj +++ b/src/Certify.Shared.Extensions/Certify.Shared.Extensions.csproj @@ -23,10 +23,8 @@ - - + - diff --git a/src/Certify.Shared/Certify.Shared.Core.csproj b/src/Certify.Shared/Certify.Shared.Core.csproj index 936ec0190..31d03bdd2 100644 --- a/src/Certify.Shared/Certify.Shared.Core.csproj +++ b/src/Certify.Shared/Certify.Shared.Core.csproj @@ -18,7 +18,7 @@ - + diff --git a/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj b/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj index ddef73206..e62f834d5 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj @@ -103,7 +103,7 @@ - + diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj index 70c813aa7..0c4c2be7e 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj @@ -137,8 +137,8 @@ - - + + diff --git a/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj b/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj index 354fde3b9..0a192b307 100644 --- a/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj @@ -81,7 +81,7 @@ - + diff --git a/src/Certify.UI.Shared/Certify.UI.Shared.csproj b/src/Certify.UI.Shared/Certify.UI.Shared.csproj index 00486dc1b..49b0f8756 100644 --- a/src/Certify.UI.Shared/Certify.UI.Shared.csproj +++ b/src/Certify.UI.Shared/Certify.UI.Shared.csproj @@ -42,7 +42,7 @@ - + NU1701 diff --git a/src/Certify.UI/Certify.UI.csproj b/src/Certify.UI/Certify.UI.csproj index c759b4786..4abb7cb30 100644 --- a/src/Certify.UI/Certify.UI.csproj +++ b/src/Certify.UI/Certify.UI.csproj @@ -180,7 +180,7 @@ 3.0.1 - 7.0.2 + 8.0.0-rc.2.23479.6 4.3.4 From 7d19b8a3c2e49e2623c7bd6c070c34d4e310305c Mon Sep 17 00:00:00 2001 From: Justin Nelson Date: Fri, 13 Oct 2023 18:10:19 -0700 Subject: [PATCH 059/328] Fix to Debug Port of Public.API always being overwritten by servers.json WebUI would not launch in Debug mode because the default servers.json file always overwrites the debug port values (9695) with release port (9696) when ServerConnectionManager.GetServerConnections() is called on line 142. --- src/Certify.Server/Certify.Server.Api.Public/Startup.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Certify.Server/Certify.Server.Api.Public/Startup.cs b/src/Certify.Server/Certify.Server.Api.Public/Startup.cs index 037560049..e623a8ee2 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Startup.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Startup.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -141,7 +141,9 @@ public void ConfigureServices(IServiceCollection services) var connections = ServerConnectionManager.GetServerConnections(null, defaultConnectionConfig); var serverConnection = connections.FirstOrDefault(c => c.IsDefault == true); - +#if DEBUG + serverConnection = defaultConnectionConfig; +#endif var internalServiceClient = new Client.CertifyServiceClient(configManager, serverConnection); internalServiceClient.ConnectStatusStreamAsync(); From dddd240e456b62fdc0b5f50d970fb25d2497cdd5 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Mon, 16 Oct 2023 10:00:08 +0800 Subject: [PATCH 060/328] WIP: scaffold access control Api --- src/Certify.Client/CertifyApiClient.cs | 6 +++ src/Certify.Client/ICertifyClient.cs | 5 ++ ...ntrol,Config.cs => AccessControlConfig.cs} | 0 .../Controllers/internal/AccessController.cs | 50 +++++++++++++++++++ .../Controllers/AccessController.cs | 40 +++++++++++++++ 5 files changed, 101 insertions(+) rename src/Certify.Models/Config/{AccessControl,Config.cs => AccessControlConfig.cs} (100%) create mode 100644 src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/AccessController.cs create mode 100644 src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccessController.cs diff --git a/src/Certify.Client/CertifyApiClient.cs b/src/Certify.Client/CertifyApiClient.cs index bb7217688..ffe9d474f 100644 --- a/src/Certify.Client/CertifyApiClient.cs +++ b/src/Certify.Client/CertifyApiClient.cs @@ -15,6 +15,7 @@ using Certify.Shared; using Newtonsoft.Json; using Polly; +using Certify.Models.Config.AccessControl; namespace Certify.Client { @@ -712,6 +713,11 @@ public async Task RefreshAccessToken() return _refreshToken; } + public async Task> GetAccessSecurityPrinciples() + { + var result = await FetchAsync("access/securityprinciples"); + return JsonConvert.DeserializeObject>(result); + } #endregion } } diff --git a/src/Certify.Client/ICertifyClient.cs b/src/Certify.Client/ICertifyClient.cs index a216a74e4..6ccc5fb89 100644 --- a/src/Certify.Client/ICertifyClient.cs +++ b/src/Certify.Client/ICertifyClient.cs @@ -7,6 +7,7 @@ using Certify.Models.Utils; using Certify.Models.Reporting; using Certify.Shared; +using Certify.Models.Config.AccessControl; namespace Certify.Client { @@ -130,6 +131,10 @@ public interface ICertifyInternalApiClient Task ChangeAccountKey(string storageKey, string newKeyPEM = null); #endregion Accounts + Task> GetAccessSecurityPrinciples(); + #region Access Control + + #endregion } /// diff --git a/src/Certify.Models/Config/AccessControl,Config.cs b/src/Certify.Models/Config/AccessControlConfig.cs similarity index 100% rename from src/Certify.Models/Config/AccessControl,Config.cs rename to src/Certify.Models/Config/AccessControlConfig.cs diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/AccessController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/AccessController.cs new file mode 100644 index 000000000..d4beb18bb --- /dev/null +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/AccessController.cs @@ -0,0 +1,50 @@ +using Certify.Client; +using Certify.Server.API.Controllers; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Authorization; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using Certify.Models.Config.AccessControl; + +namespace Certify.Server.Api.Public.Controllers +{ + /// + /// Internal API controller for access related admin + /// + [Route("internal/v1/[controller]")] + [ApiController] + + public class AccessController : ControllerBase + { + private readonly ILogger _logger; + + private readonly ICertifyInternalApiClient _client; + + /// + /// Constructor + /// + /// + /// + public AccessController(ILogger logger, ICertifyInternalApiClient client) + { + _logger = logger; + _client = client; + } + + /// + /// Get list of Security Principles + /// + /// + [HttpGet] + [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(List))] + public async Task GetSecurityPrinciples() + { + var list = await _client.GetAccessSecurityPrinciples(); + return new OkObjectResult(list); + } + } +} diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccessController.cs b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccessController.cs new file mode 100644 index 000000000..b3c66e1b6 --- /dev/null +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccessController.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Certify.Core.Management.Access; +using Certify.Models.Config.AccessControl; +using Microsoft.AspNetCore.Mvc; + +namespace Certify.Service.Controllers +{ + [ApiController] + [Route("api/access")] + public class AccessController : ControllerBase + { + private IAccessControl _accessControl; + + private string GetContextUserId() + { + // TODO: sign passed value provided by public API using public APIs access token + var contextUserId = Request.Headers["X-Context-User-Id"]; + return contextUserId; + } + + public AccessController(IAccessControl manager) + { + _accessControl = manager; + } + + [HttpGet, Route("securityprinciples")] + public async Task> GetSecurityPrinciples() + { + + return await _accessControl.GetSecurityPrinciples(GetContextUserId()); + } + + [HttpPost, Route("updatepassword")] + public async Task UpdatePassword(string id, string oldpassword, string newpassword) + { + return await _accessControl.UpdateSecurityPrinciplePassword(GetContextUserId(), id: id, oldpassword: oldpassword, newpassword: newpassword); + } + } +} From 67552baa65eaa00e96d7baff8760fdc8faee4258 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Wed, 18 Oct 2023 14:57:30 +0800 Subject: [PATCH 061/328] Package updates --- src/Certify.Client/Certify.Client.csproj | 3 ++- src/Certify.Models/Certify.Models.csproj | 2 +- .../Certify.Server.Api.Public.csproj | 17 ++++++++++------- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/Certify.Client/Certify.Client.csproj b/src/Certify.Client/Certify.Client.csproj index d3d975772..f874fdbfc 100644 --- a/src/Certify.Client/Certify.Client.csproj +++ b/src/Certify.Client/Certify.Client.csproj @@ -18,13 +18,14 @@ - + + diff --git a/src/Certify.Models/Certify.Models.csproj b/src/Certify.Models/Certify.Models.csproj index 680a47cbf..1aa1612ce 100644 --- a/src/Certify.Models/Certify.Models.csproj +++ b/src/Certify.Models/Certify.Models.csproj @@ -21,7 +21,7 @@ all runtime; build; native; contentfiles; analyzers - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj index 76e40b01b..504d52806 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj +++ b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj @@ -1,4 +1,4 @@ - + net8.0 @@ -13,10 +13,12 @@ + + - - + + @@ -26,11 +28,12 @@ - - - + + + + - + From 197480fce5bb867017646537318a9818c74af2aa Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Wed, 18 Oct 2023 15:02:13 +0800 Subject: [PATCH 062/328] Cleanup --- .../CertifyManager/CertifyManager.CertificateRequest.cs | 5 +++-- src/Certify.Models/API/LogResult.cs | 2 -- src/Certify.Models/Reporting/Summary.cs | 7 +------ .../Controllers/v1/CertificateController.cs | 8 +++++--- src/Certify.Server/Certify.Server.Api.Public/Program.cs | 1 + .../Controllers/ManagedCertificateController.cs | 3 +-- .../Controllers/ManagedCertificateController.cs | 2 +- src/Certify.Tests/Certify.Core.Tests.Unit/MiscTests.cs | 2 +- .../Controls/ManagedCertificate/AdvancedOptions.xaml.cs | 1 - .../Controls/ManagedCertificate/Dashboard.xaml.cs | 5 +---- .../Controls/ManagedCertificate/StatusInfo.xaml.cs | 2 +- .../Controls/ManagedCertificates.xaml.cs | 4 ++-- .../AppViewModel/AppViewModel.ManagedCerticates.cs | 4 ++-- 13 files changed, 19 insertions(+), 27 deletions(-) diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.CertificateRequest.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.CertificateRequest.cs index a8880478d..cb3eb504a 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.CertificateRequest.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.CertificateRequest.cs @@ -85,9 +85,10 @@ public async Task> PerformRenewAll(RenewalSetting _itemManager, settings, prefs, - + ReportProgress, IsManagedCertificateRunning, - (ManagedCertificate item, IProgress progress, bool isPreview, string reason) => { + (ManagedCertificate item, IProgress progress, bool isPreview, string reason) => + { return PerformCertificateRequest(null, item, progress, skipRequest: isPreview, skipTasks: isPreview, reason: reason); }, progressTrackers); diff --git a/src/Certify.Models/API/LogResult.cs b/src/Certify.Models/API/LogResult.cs index 173c95220..b74bdcc2e 100644 --- a/src/Certify.Models/API/LogResult.cs +++ b/src/Certify.Models/API/LogResult.cs @@ -1,7 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; -using Certify.CertificateAuthorities.Definitions; namespace Certify.Models.API { diff --git a/src/Certify.Models/Reporting/Summary.cs b/src/Certify.Models/Reporting/Summary.cs index 0e1e4cc48..94129011b 100644 --- a/src/Certify.Models/Reporting/Summary.cs +++ b/src/Certify.Models/Reporting/Summary.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text; -using Certify.Models; - -namespace Certify.Models.Reporting +namespace Certify.Models.Reporting { public class Summary : BindableBase { diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs index 4f945e5ce..261634bac 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs @@ -98,7 +98,7 @@ public async Task DownloadLog(string managedCertId, int maxLines var log = await _client.GetItemLog(managedCertId, maxLines); - + return new OkObjectResult(new LogResult { Items = log }); } @@ -106,6 +106,8 @@ public async Task DownloadLog(string managedCertId, int maxLines /// Get all managed certificates matching criteria /// /// + /// + /// /// [HttpGet] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] @@ -144,7 +146,7 @@ public async Task GetManagedCertificates(string keyword, int? pag return new OkObjectResult(result); } - + /// /// Get summary counts of all managed certs /// @@ -162,7 +164,7 @@ public async Task GetManagedCertificateSummary(string keyword) { Keyword = keyword }); - + return new OkObjectResult(summary); } diff --git a/src/Certify.Server/Certify.Server.Api.Public/Program.cs b/src/Certify.Server/Certify.Server.Api.Public/Program.cs index 5841eaf67..32061a7fc 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Program.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Program.cs @@ -14,6 +14,7 @@ public class Program /// public static void Main(string[] args) { + CreateHostBuilder(args).Build().Run(); } diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/ManagedCertificateController.cs b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/ManagedCertificateController.cs index 856054b0e..d59f09c6b 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/ManagedCertificateController.cs +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/ManagedCertificateController.cs @@ -2,14 +2,13 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; - using Certify.Config; using Certify.Management; using Certify.Models; using Certify.Models.API; using Certify.Models.Config; -using Certify.Models.Utils; using Certify.Models.Reporting; +using Certify.Models.Utils; using Microsoft.AspNetCore.Mvc; using Serilog; diff --git a/src/Certify.Service/Controllers/ManagedCertificateController.cs b/src/Certify.Service/Controllers/ManagedCertificateController.cs index 5cd750443..5680ab0bb 100644 --- a/src/Certify.Service/Controllers/ManagedCertificateController.cs +++ b/src/Certify.Service/Controllers/ManagedCertificateController.cs @@ -9,8 +9,8 @@ using Certify.Models; using Certify.Models.API; using Certify.Models.Config; -using Certify.Models.Utils; using Certify.Models.Reporting; +using Certify.Models.Utils; using Serilog; namespace Certify.Service.Controllers diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/MiscTests.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/MiscTests.cs index 205121e47..c382954f3 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/MiscTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/MiscTests.cs @@ -1,6 +1,6 @@ -using Certify.Models.API; using System; using System.Threading.Tasks; +using Certify.Models.API; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Certify.Core.Tests.Unit diff --git a/src/Certify.UI.Shared/Controls/ManagedCertificate/AdvancedOptions.xaml.cs b/src/Certify.UI.Shared/Controls/ManagedCertificate/AdvancedOptions.xaml.cs index 3ab8c4fc4..fa3235e86 100644 --- a/src/Certify.UI.Shared/Controls/ManagedCertificate/AdvancedOptions.xaml.cs +++ b/src/Certify.UI.Shared/Controls/ManagedCertificate/AdvancedOptions.xaml.cs @@ -6,7 +6,6 @@ using System.Windows.Controls; using Certify.Locales; using Certify.Management; -using Certify.UI.ViewModel; using Microsoft.Win32; namespace Certify.UI.Controls.ManagedCertificate diff --git a/src/Certify.UI.Shared/Controls/ManagedCertificate/Dashboard.xaml.cs b/src/Certify.UI.Shared/Controls/ManagedCertificate/Dashboard.xaml.cs index c75548ae8..14197f015 100644 --- a/src/Certify.UI.Shared/Controls/ManagedCertificate/Dashboard.xaml.cs +++ b/src/Certify.UI.Shared/Controls/ManagedCertificate/Dashboard.xaml.cs @@ -1,7 +1,4 @@ - -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Linq; +using System.ComponentModel; using System.Threading.Tasks; using System.Windows.Controls; using Certify.Models; diff --git a/src/Certify.UI.Shared/Controls/ManagedCertificate/StatusInfo.xaml.cs b/src/Certify.UI.Shared/Controls/ManagedCertificate/StatusInfo.xaml.cs index 47bea48a0..405fbd93d 100644 --- a/src/Certify.UI.Shared/Controls/ManagedCertificate/StatusInfo.xaml.cs +++ b/src/Certify.UI.Shared/Controls/ManagedCertificate/StatusInfo.xaml.cs @@ -99,7 +99,7 @@ private async void OpenLogFile_Click(object sender, System.Windows.RoutedEventAr var log = await AppViewModel.GetItemLog(ItemViewModel.SelectedItem.Id, 1000); var tempPath = System.IO.Path.GetTempFileName() + ".txt"; - System.IO.File.WriteAllLines(tempPath, log.Select(i=>i.ToString())); + System.IO.File.WriteAllLines(tempPath, log.Select(i => i.ToString())); _tempLogFilePath = tempPath; try diff --git a/src/Certify.UI.Shared/Controls/ManagedCertificates.xaml.cs b/src/Certify.UI.Shared/Controls/ManagedCertificates.xaml.cs index d9cf9ea77..354a7da39 100644 --- a/src/Certify.UI.Shared/Controls/ManagedCertificates.xaml.cs +++ b/src/Certify.UI.Shared/Controls/ManagedCertificates.xaml.cs @@ -1,8 +1,8 @@ using System; using System.ComponentModel; using System.Linq; -using System.Threading.Tasks; using System.Threading; +using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; @@ -138,7 +138,7 @@ public async Task Debounce(Func action) Cancel(lastCancellationTokenSource); var tokenSrc = lastCancellationTokenSource = new CancellationTokenSource(); - + try { await Task.Delay(new TimeSpan(milliseconds), tokenSrc.Token); diff --git a/src/Certify.UI.Shared/ViewModel/AppViewModel/AppViewModel.ManagedCerticates.cs b/src/Certify.UI.Shared/ViewModel/AppViewModel/AppViewModel.ManagedCerticates.cs index c0ae7c5c2..7444298f4 100644 --- a/src/Certify.UI.Shared/ViewModel/AppViewModel/AppViewModel.ManagedCerticates.cs +++ b/src/Certify.UI.Shared/ViewModel/AppViewModel/AppViewModel.ManagedCerticates.cs @@ -94,7 +94,7 @@ public int NumManagedCerts int _filterPageIndex = 0; int _filterPageSize = 15; - public string FilterKeyword { get; set; }= string.Empty; + public string FilterKeyword { get; set; } = string.Empty; /// /// Refresh the cached list of managed certs via the connected service @@ -108,7 +108,7 @@ public virtual async Task RefreshManagedCertificates() filter.IncludeExternal = IsFeatureEnabled(FeatureFlags.EXTERNAL_CERT_MANAGERS) && Preferences.EnableExternalCertManagers; filter.PageSize = _filterPageSize; filter.PageIndex = _filterPageIndex; - + filter.Keyword = string.IsNullOrWhiteSpace(FilterKeyword) ? null : FilterKeyword; var result = await _certifyClient.GetManagedCertificateSearchResult(filter); From 6cdbb1737ce624ce89d5d099cf3302e455c5c536 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Wed, 18 Oct 2023 15:15:06 +0800 Subject: [PATCH 063/328] Access Control: implement user/role api and bootstrap of basic access config --- src/Certify.Client/CertifyApiClient.cs | 14 +- src/Certify.Client/ICertifyClient.cs | 8 +- .../Management/Access/AccessControl.cs | 18 ++- .../Management/Access/IAccessControl.cs | 11 +- .../CertifyManager/CertifyManager.cs | 13 ++ .../CertifyManager/ICertifyManager.cs | 1 + .../Config/AccessControlConfig.cs | 3 +- .../Providers/IAccessControlStore.cs | 4 +- .../Controllers/internal/AccessController.cs | 24 +--- .../Controllers/AccessController.cs | 120 +++++++++++++++++- .../AccessControlTests.cs | 8 ++ 11 files changed, 176 insertions(+), 48 deletions(-) diff --git a/src/Certify.Client/CertifyApiClient.cs b/src/Certify.Client/CertifyApiClient.cs index ffe9d474f..efde8d88c 100644 --- a/src/Certify.Client/CertifyApiClient.cs +++ b/src/Certify.Client/CertifyApiClient.cs @@ -10,12 +10,12 @@ using Certify.Models; using Certify.Models.API; using Certify.Models.Config; -using Certify.Models.Utils; +using Certify.Models.Config.AccessControl; using Certify.Models.Reporting; +using Certify.Models.Utils; using Certify.Shared; using Newtonsoft.Json; using Polly; -using Certify.Models.Config.AccessControl; namespace Certify.Client { @@ -56,7 +56,7 @@ protected override Task SendAsync( } // This version of the client communicates with the Certify.Service instance on the local machine - public class CertifyApiClient : ICertifyInternalApiClient + public partial class CertifyApiClient : ICertifyInternalApiClient { private HttpClient _client; private readonly string _baseUri = "/api/"; @@ -716,8 +716,14 @@ public async Task RefreshAccessToken() public async Task> GetAccessSecurityPrinciples() { var result = await FetchAsync("access/securityprinciples"); - return JsonConvert.DeserializeObject>(result); + return JsonToObject>(result); } + #endregion + + private T JsonToObject(string json) + { + return JsonConvert.DeserializeObject(json); + } } } diff --git a/src/Certify.Client/ICertifyClient.cs b/src/Certify.Client/ICertifyClient.cs index 6ccc5fb89..93c0914af 100644 --- a/src/Certify.Client/ICertifyClient.cs +++ b/src/Certify.Client/ICertifyClient.cs @@ -4,10 +4,9 @@ using Certify.Config.Migration; using Certify.Models; using Certify.Models.Config; -using Certify.Models.Utils; using Certify.Models.Reporting; +using Certify.Models.Utils; using Certify.Shared; -using Certify.Models.Config.AccessControl; namespace Certify.Client { @@ -15,7 +14,7 @@ namespace Certify.Client /// /// Base API /// - public interface ICertifyInternalApiClient + public partial interface ICertifyInternalApiClient { #region System @@ -131,10 +130,7 @@ public interface ICertifyInternalApiClient Task ChangeAccountKey(string storageKey, string newKeyPEM = null); #endregion Accounts - Task> GetAccessSecurityPrinciples(); - #region Access Control - #endregion } /// diff --git a/src/Certify.Core/Management/Access/AccessControl.cs b/src/Certify.Core/Management/Access/AccessControl.cs index 6a26dafe8..9dcc74c3c 100644 --- a/src/Certify.Core/Management/Access/AccessControl.cs +++ b/src/Certify.Core/Management/Access/AccessControl.cs @@ -207,7 +207,7 @@ public async Task UpdateSecurityPrinciplePassword(string contextUserId, st { if (id != contextUserId && !await IsPrincipleInRole(contextUserId, contextUserId, StandardRoles.Administrator.Id)) { - _log?.Warning("User {contextUserId} attempted to use updated password for [{username} - {id}] without being in required role."); + _log?.Warning("User {contextUserId} attempted to use updated password for [{id}] without being in required role.", contextUserId, id); return false; } @@ -278,8 +278,6 @@ public string HashPassword(string password, string saltString = null) return hashed; } - public Task> GetSecurityPrincipleResourcePolicies(string contextUserId, string userId) => throw new NotImplementedException(); - public async Task AddRole(Role r) { await _store.Add(nameof(Role), r); @@ -294,5 +292,19 @@ public async Task AddAction(ResourceAction action) { await _store.Add(nameof(ResourceAction), action); } + + public async Task> GetAssignedRoles(string contextUserId, string id) + { + + if (id != contextUserId && !await IsPrincipleInRole(contextUserId, contextUserId, StandardRoles.Administrator.Id)) + { + _log?.Warning("User {contextUserId} attempted to read assigned role for [{id}] without being in required role.", contextUserId, id); + return new List(); + } + + var assignedRoles = await _store.GetItems(nameof(AssignedRole)); + + return assignedRoles.Where(r => r.SecurityPrincipleId == id).ToList(); + } } } diff --git a/src/Certify.Core/Management/Access/IAccessControl.cs b/src/Certify.Core/Management/Access/IAccessControl.cs index a6593f4a5..b5a4b8c2a 100644 --- a/src/Certify.Core/Management/Access/IAccessControl.cs +++ b/src/Certify.Core/Management/Access/IAccessControl.cs @@ -9,9 +9,9 @@ public interface IAccessControl Task AddResourcePolicy(string contextUserId, ResourcePolicy resourceProfile, bool bypassIntegrityCheck = false); Task AddSecurityPrinciple(string contextUserId, SecurityPrinciple principle, bool bypassIntegrityCheck = false); Task DeleteSecurityPrinciple(string contextUserId, string id, bool allowSelfDelete = false); - Task> GetSecurityPrincipleResourcePolicies(string contextUserId, string userId); Task> GetSecurityPrinciples(string contextUserId); - + Task GetSecurityPrinciple(string contextUserId, string id); + /// /// Get the list of standard roles built-in to the system /// @@ -19,7 +19,14 @@ public interface IAccessControl Task> GetSystemRoles(); Task IsAuthorised(string contextUserId, string principleId, string roleId, string resourceType, string actionId, string identifier); Task IsPrincipleInRole(string contextUserId, string id, string roleId); + Task> GetAssignedRoles(string contextUserId, string id); Task UpdateSecurityPrinciple(string contextUserId, SecurityPrinciple principle); Task UpdateSecurityPrinciplePassword(string contextUserId, string id, string oldpassword, string newpassword); + + Task AddRole(Role role); + Task AddAssignedRole(AssignedRole assignedRole); + Task AddAction(ResourceAction action); + + } } diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.cs index 81a2fb903..c9c0e05be 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using Certify.Config.Migration; using Certify.Core.Management; +using Certify.Core.Management.Access; using Certify.Core.Management.Challenges; using Certify.Datastore.SQLite; using Certify.Models; @@ -642,5 +643,17 @@ public Task ApplyPreferences() return Task.FromResult(true); } + + private IAccessControl _accessControl; + public Task GetCurrentAccessControl() + { + if (_accessControl == null) + { + var store = new SQLiteAccessControlStore(); + _accessControl = new AccessControl(_serviceLog, store); + } + + return Task.FromResult(_accessControl); + } } } diff --git a/src/Certify.Core/Management/CertifyManager/ICertifyManager.cs b/src/Certify.Core/Management/CertifyManager/ICertifyManager.cs index c4c3fac72..2327eec54 100644 --- a/src/Certify.Core/Management/CertifyManager/ICertifyManager.cs +++ b/src/Certify.Core/Management/CertifyManager/ICertifyManager.cs @@ -109,5 +109,6 @@ public interface ICertifyManager Task> RemoveDataStoreConnection(string dataStoreId); Task> TestDataStoreConnection(DataStoreConnection connection); Task TestCredentials(string storageKey); + Task GetCurrentAccessControl(); } } diff --git a/src/Certify.Models/Config/AccessControlConfig.cs b/src/Certify.Models/Config/AccessControlConfig.cs index 3e8f0cdd3..ffa269302 100644 --- a/src/Certify.Models/Config/AccessControlConfig.cs +++ b/src/Certify.Models/Config/AccessControlConfig.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; namespace Certify.Models.Config.AccessControl { diff --git a/src/Certify.Models/Providers/IAccessControlStore.cs b/src/Certify.Models/Providers/IAccessControlStore.cs index 2a2a70408..6aa6d62c3 100644 --- a/src/Certify.Models/Providers/IAccessControlStore.cs +++ b/src/Certify.Models/Providers/IAccessControlStore.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text; +using System.Collections.Generic; using System.Threading.Tasks; namespace Certify.Providers diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/AccessController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/AccessController.cs index d4beb18bb..664d6b6bd 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/AccessController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/AccessController.cs @@ -1,23 +1,16 @@ using Certify.Client; using Certify.Server.API.Controllers; -using Microsoft.AspNetCore.Authentication.JwtBearer; -using Microsoft.AspNetCore.Authorization; -using System.Collections.Generic; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; -using Certify.Models.Config.AccessControl; namespace Certify.Server.Api.Public.Controllers { /// - /// Internal API controller for access related admin + /// Internal API controller for access related admin. Some controller endpoints may be Source Generated by Certify.SourceGenerators. /// [Route("internal/v1/[controller]")] [ApiController] - - public class AccessController : ControllerBase + public partial class AccessController : ControllerBase { private readonly ILogger _logger; @@ -33,18 +26,5 @@ public AccessController(ILogger logger, ICertify _logger = logger; _client = client; } - - /// - /// Get list of Security Principles - /// - /// - [HttpGet] - [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] - [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(List))] - public async Task GetSecurityPrinciples() - { - var list = await _client.GetAccessSecurityPrinciples(); - return new OkObjectResult(list); - } } } diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccessController.cs b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccessController.cs index b3c66e1b6..758956746 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccessController.cs +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccessController.cs @@ -1,6 +1,9 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Certify.Core.Management.Access; +using Certify.Management; using Certify.Models.Config.AccessControl; using Microsoft.AspNetCore.Mvc; @@ -10,31 +13,136 @@ namespace Certify.Service.Controllers [Route("api/access")] public class AccessController : ControllerBase { - private IAccessControl _accessControl; + private ICertifyManager _certifyManager; private string GetContextUserId() { // TODO: sign passed value provided by public API using public APIs access token var contextUserId = Request.Headers["X-Context-User-Id"]; + +#if DEBUG + if (string.IsNullOrEmpty(contextUserId)) + { + // TODO: our context user has to at least come from a valid JWT claim + contextUserId = "admin_01"; + } +#endif return contextUserId; } - public AccessController(IAccessControl manager) + public AccessController(ICertifyManager certifyManager) + { + _certifyManager = certifyManager; + } + +#if DEBUG + private async Task BootstrapTestAdminUserAndRoles(IAccessControl access) { - _accessControl = manager; + + var adminSp = new SecurityPrinciple + { + Id = "admin_01", + Email = "admin@test.com", + Description = "Primary test admin", + PrincipleType = SecurityPrincipleType.User, + Username = "admin", + Provider = StandardProviders.INTERNAL + }; + + await access.AddSecurityPrinciple(adminSp.Id, adminSp, bypassIntegrityCheck: true); + + var actions = Policies.GetStandardResourceActions(); + + foreach (var action in actions) + { + await access.AddAction(action); + } + + // setup policies with actions + + var policies = Policies.GetStandardPolicies(); + + // add policies to store + foreach (var r in policies) + { + _ = await access.AddResourcePolicy(adminSp.Id, r, bypassIntegrityCheck: true); + } + + // setup roles with policies + var roles = await access.GetSystemRoles(); + + foreach (var r in roles) + { + // add roles and policy assignments to store + await access.AddRole(r); + } + + // assign security principles to roles + var assignedRoles = new List { + // administrator + new AssignedRole{ + Id= Guid.NewGuid().ToString(), + RoleId=StandardRoles.Administrator.Id, + SecurityPrincipleId=adminSp.Id + } + }; + + foreach (var r in assignedRoles) + { + // add roles and policy assignments to store + await access.AddAssignedRole(r); + } } +#endif + [HttpGet, Route("securityprinciples")] public async Task> GetSecurityPrinciples() { + var accessControl = await _certifyManager.GetCurrentAccessControl(); + + var results = await accessControl.GetSecurityPrinciples(GetContextUserId()); + +#if DEBUG + // bootstrap the default user + if (!results.Any()) + { + await BootstrapTestAdminUserAndRoles(accessControl); + results = await accessControl.GetSecurityPrinciples(GetContextUserId()); + } +#endif + foreach (var r in results) + { + r.AuthKey = ""; + r.Password = ""; + } + + return results; + } + + [HttpGet, Route("roles")] + public async Task> GetRoles() + { + var accessControl = await _certifyManager.GetCurrentAccessControl(); + var roles = await accessControl.GetSystemRoles(); + return roles; + } + + [HttpGet, Route("securityprinciple/{id}/assignedroles")] + public async Task> GetSecurityPrincipleAssignedRoles(string id) + { + var accessControl = await _certifyManager.GetCurrentAccessControl(); + + var results = await accessControl.GetAssignedRoles(GetContextUserId(), id); - return await _accessControl.GetSecurityPrinciples(GetContextUserId()); + return results; } [HttpPost, Route("updatepassword")] public async Task UpdatePassword(string id, string oldpassword, string newpassword) { - return await _accessControl.UpdateSecurityPrinciplePassword(GetContextUserId(), id: id, oldpassword: oldpassword, newpassword: newpassword); + var accessControl = await _certifyManager.GetCurrentAccessControl(); + return await accessControl.UpdateSecurityPrinciplePassword(GetContextUserId(), id: id, oldpassword: oldpassword, newpassword: newpassword); } } } diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/AccessControlTests.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/AccessControlTests.cs index 3bb72c655..138fbad82 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/AccessControlTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/AccessControlTests.cs @@ -160,6 +160,11 @@ public async Task TestAccessControlChecks() // assign security principles to roles var assignedRoles = new List { + // test administrator + new AssignedRole{ + RoleId=StandardRoles.Administrator.Id, + SecurityPrincipleId="[test]" + }, // administrator new AssignedRole{ RoleId=StandardRoles.Administrator.Id, @@ -184,6 +189,9 @@ public async Task TestAccessControlChecks() // assert + var adminAssignedRoles = await access.GetAssignedRoles(contextUserId, "admin_01"); + Assert.AreEqual(1, adminAssignedRoles.Count); + var hasAccess = await access.IsPrincipleInRole(contextUserId, "admin_01", StandardRoles.Administrator.Id); Assert.IsTrue(hasAccess, "User should be in role"); From d5a212f86dea72ea1224fb5ae8a8de04369b8d8b Mon Sep 17 00:00:00 2001 From: Justin Nelson Date: Thu, 19 Oct 2023 08:11:37 -0700 Subject: [PATCH 064/328] Created basic unit tests for the Loggy class in Certify.Shared.Core Created this basic unit tests for Loggy as an easy entry point into writing MSTest C# unit tests for a simple wrapper class, increased unit test code coverage for Loggy.cs to 100% https://github.com/webprofusion/certify-general/issues/7 --- .../Certify.Core.Tests.Unit/LoggyTests.cs | 170 ++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 src/Certify.Tests/Certify.Core.Tests.Unit/LoggyTests.cs diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/LoggyTests.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/LoggyTests.cs new file mode 100644 index 000000000..a3283b9a4 --- /dev/null +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/LoggyTests.cs @@ -0,0 +1,170 @@ +using System; +using System.IO; +using Certify.Models; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Serilog; + +namespace Certify.Core.Tests.Unit +{ + [TestClass] + public class LoggyTests + { + private string logFilePath = "C:\\ProgramData\\certify\\Tests\\test.log"; + + [TestInitialize] + public void TestInitialize() + { + File.Delete(this.logFilePath); + } + + [TestCleanup] + public void TestCleanup() + { + File.Delete(this.logFilePath); + } + + [TestMethod, Description("Test Loggy.Error() Method")] + public void TestLoggyError() + { + // Setup instance of Loggy + var logImp = new LoggerConfiguration() + .WriteTo.File(this.logFilePath) + .CreateLogger(); + var log = new Loggy(logImp); + + // Log an error message using Loggy.Error() + var logMessage = "New Loggy Error"; + log.Error(logMessage); + logImp.Dispose(); + + // Read in logged out error text + var logText = File.ReadAllText(this.logFilePath); + + // Validate logged out error text + Assert.IsTrue(logText.Contains(logMessage), $"Logged error message should contain '{logMessage}'"); + Assert.IsTrue(logText.Contains("[ERR]"), "Logged error message should contain '[ERR]'"); + } + + [TestMethod, Description("Test Loggy.Error() Method (Exception)")] + public void TestLoggyErrorException() + { + // Setup instance of Loggy + var logImp = new LoggerConfiguration() + .WriteTo.File(this.logFilePath) + .CreateLogger(); + var log = new Loggy(logImp); + + // Trigger an exception error and log it using Loggy.Error() + var logMessage = "New Loggy Exception Error"; + var exceptionError = "System.IO.FileNotFoundException: Could not find file 'C:\\ProgramData\\certify\\Tests\\test1.log'."; + try + { + var badFilePath = "C:\\ProgramData\\certify\\Tests\\test1.log"; + var nullObject = File.ReadAllBytes(badFilePath); + } + catch (Exception e) + { + log.Error(e, logMessage); + } + logImp.Dispose(); + + // Read in logged out exception error text + var logText = File.ReadAllText(this.logFilePath); + + // Validate logged out exception error text + Assert.IsTrue(logText.Contains(logMessage), $"Logged error message should contain '{logMessage}'"); + Assert.IsTrue(logText.Contains("[ERR]"), "Logged error message should contain '[ERR]'"); + Assert.IsTrue(logText.Contains(exceptionError), $"Logged error message should contain exception error '{exceptionError}'"); + } + + [TestMethod, Description("Test Loggy.Information() Method")] + public void TestLoggyInformation() + { + // Setup instance of Loggy + var logImp = new LoggerConfiguration() + .WriteTo.File(this.logFilePath) + .CreateLogger(); + var log = new Loggy(logImp); + + // Log an info message using Loggy.Information() + var logMessage = "New Loggy Information"; + log.Information(logMessage); + logImp.Dispose(); + + // Read in logged out info text + var logText = File.ReadAllText(this.logFilePath); + + // Validate logged out info text + Assert.IsTrue(logText.Contains(logMessage), $"Logged info message should contain '{logMessage}'"); + Assert.IsTrue(logText.Contains("[INF]"), "Logged info message should contain '[INF]'"); + } + + [TestMethod, Description("Test Loggy.Debug() Method")] + public void TestLoggyDebug() + { + // Setup instance of Loggy + var logImp = new LoggerConfiguration() + .MinimumLevel.Debug() + .WriteTo.File(this.logFilePath) + .CreateLogger(); + var log = new Loggy(logImp); + + // Log a debug message using Loggy.Debug() + var logMessage = "New Loggy Debug"; + log.Debug(logMessage); + logImp.Dispose(); + + // Read in logged out debug text + var logText = File.ReadAllText(this.logFilePath); + + // Validate logged out debug text + Assert.IsTrue(logText.Contains(logMessage), $"Logged debug message should contain '{logMessage}'"); + Assert.IsTrue(logText.Contains("[DBG]"), "Logged debug message should contain '[DBG]'"); + } + + [TestMethod, Description("Test Loggy.Verbose() Method")] + public void TestLoggyVerbose() + { + // Setup instance of Loggy + var logImp = new LoggerConfiguration() + .MinimumLevel.Verbose() + .WriteTo.File(this.logFilePath) + .CreateLogger(); + var log = new Loggy(logImp); + + // Log a verbose message using Loggy.Verbose() + var logMessage = "New Loggy Verbose"; + log.Verbose(logMessage); + logImp.Dispose(); + + // Read in logged out verbose text + var logText = File.ReadAllText(this.logFilePath); + + // Validate logged out verbose text + Assert.IsTrue(logText.Contains(logMessage), $"Logged verbose message should contain '{logMessage}'"); + Assert.IsTrue(logText.Contains("[VRB]"), "Logged verbose message should contain '[VRB]'"); + } + + [TestMethod, Description("Test Loggy.Warning() Method")] + public void TestLoggyWarning() + { + // Setup instance of Loggy + var logImp = new LoggerConfiguration() + .WriteTo.File(this.logFilePath) + .CreateLogger(); + var log = new Loggy(logImp); + + // Log a warning message using Loggy.Warning() + var logMessage = "New Loggy Warning"; + log.Warning(logMessage); + logImp.Dispose(); + + // Read in logged out warning text + var logText = File.ReadAllText(this.logFilePath); + + // Validate logged out warning text + Assert.IsTrue(logText.Contains(logMessage), $"Logged warning message should contain '{logMessage}'"); + Assert.IsTrue(logText.Contains("[WRN]"), "Logged warning message should contain '[WRN]'"); + } + } +} From 18172e79058c15b0c058977f6a51d4cb4aec5921 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Fri, 20 Oct 2023 17:13:42 +0800 Subject: [PATCH 065/328] DNS: add experimental dns provider caching --- .../Challenges/ChallengeResponseService.cs | 8 +-- .../Challenges/DNS/DnsChallengeHelper.cs | 49 +++++++++++++++++-- 2 files changed, 49 insertions(+), 8 deletions(-) diff --git a/src/Certify.Core/Management/Challenges/ChallengeResponseService.cs b/src/Certify.Core/Management/Challenges/ChallengeResponseService.cs index f7adadcab..4ac46c835 100644 --- a/src/Certify.Core/Management/Challenges/ChallengeResponseService.cs +++ b/src/Certify.Core/Management/Challenges/ChallengeResponseService.cs @@ -621,7 +621,7 @@ private Func PrepareChallengeResponse_TlsSni01(ILog log, ITargetWebServer private DnsChallengeHelper _dnsHelper = null; - private async Task PerformChallengeResponse_Dns01(ILog log, CertIdentifierItem domain, ManagedCertificate managedCertificate, PendingAuthorization pendingAuth, bool isTestMode, bool isCleanupOnly, ICredentialsManager credentialsManager) + private async Task PerformChallengeResponse_Dns01(ILog log, CertIdentifierItem domain, ManagedCertificate managedCertificate, PendingAuthorization pendingAuth, bool isTestMode, ICredentialsManager credentialsManager) { var dnsChallenge = pendingAuth.Challenges.FirstOrDefault(c => c.ChallengeType == SupportedChallengeTypes.CHALLENGE_TYPE_DNS); @@ -639,12 +639,12 @@ private async Task PerformChallengeResponse_Dns01(ILog } // create DNS records (manually or via automation) - if (_dnsHelper == null) - { + if (_dnsHelper == null) { _dnsHelper = new DnsChallengeHelper(credentialsManager); } - DnsChallengeHelperResult dnsResult; + var dnsResult = await _dnsHelper.CompleteDNSChallenge(log, managedCertificate, domain, dnsChallenge.Key, dnsChallenge.Value, isTestMode); + if (!isCleanupOnly) { dnsResult = await _dnsHelper.CompleteDNSChallenge(log, managedCertificate, domain, dnsChallenge.Key, dnsChallenge.Value, isTestMode); diff --git a/src/Certify.Core/Management/Challenges/DNS/DnsChallengeHelper.cs b/src/Certify.Core/Management/Challenges/DNS/DnsChallengeHelper.cs index f8fa7f26b..dca4b4be0 100644 --- a/src/Certify.Core/Management/Challenges/DNS/DnsChallengeHelper.cs +++ b/src/Certify.Core/Management/Challenges/DNS/DnsChallengeHelper.cs @@ -6,6 +6,7 @@ using Certify.Models; using Certify.Models.Config; using Certify.Models.Providers; +using Newtonsoft.Json; namespace Certify.Core.Management.Challenges { @@ -94,6 +95,46 @@ public async Task GetDnsProvider(string providerTypeId }; } + private Dictionary _dnsProviderCache = new Dictionary(); + private bool _useDnsProviderCaching = false; + + /// + /// Gets optionally cached DNS provider instance, caching may be based credentials/parameters to allow for zone query caching. TODO: log context will be first caller instead of current + /// + /// + /// + /// + /// + /// + private async Task GetDnsProvider(ILog log, string challengeProvider, Dictionary credentials, Dictionary parameters) + { + + IDnsProvider dnsAPIProvider = null; + + if (_useDnsProviderCaching) + { + // construct basic cache key for dns provider and credentials combo + var providerCacheKey = challengeProvider + (challengeProvider + JsonConvert.SerializeObject(credentials ?? new Dictionary()) + JsonConvert.SerializeObject(parameters ?? new Dictionary())).GetHashCode().ToString(); + if (_dnsProviderCache.ContainsKey(providerCacheKey)) + { + log.Warning("Developer Note: DNS provider log context will be first caller instead of current"); + + dnsAPIProvider = _dnsProviderCache[providerCacheKey]; + } + else + { + dnsAPIProvider = await ChallengeProviders.GetDnsProvider(challengeProvider, credentials, parameters, log); + _dnsProviderCache.Add(providerCacheKey, dnsAPIProvider); + } + } + else + { + dnsAPIProvider = await ChallengeProviders.GetDnsProvider(challengeProvider, credentials, parameters, log); + } + + return dnsAPIProvider; + } + public async Task CompleteDNSChallenge(ILog log, ManagedCertificate managedcertificate, CertIdentifierItem domain, string txtRecordName, string txtRecordValue, bool isTestMode) { // for a given managed site configuration, attempt to complete the required challenge by @@ -129,7 +170,7 @@ public async Task CompleteDNSChallenge(ILog log, Manag try { - dnsAPIProvider = await ChallengeProviders.GetDnsProvider(challengeConfig.ChallengeProvider, credentials, parameters, log); + dnsAPIProvider = await GetDnsProvider(log, challengeConfig.ChallengeProvider, credentials, parameters); } catch (ChallengeProviders.CredentialsRequiredException) { @@ -358,15 +399,15 @@ public async Task DeleteDNSChallenge(ILog log, Managed try { - dnsAPIProvider = await ChallengeProviders.GetDnsProvider(challengeConfig.ChallengeProvider, credentials, parameters); + dnsAPIProvider = await GetDnsProvider(log, challengeConfig.ChallengeProvider, credentials, parameters); } catch (ChallengeProviders.CredentialsRequiredException) { - return new DnsChallengeHelperResult(failureMsg: "This DNS Challenge API requires one or more credentials to be specified."); + return new DnsChallengeHelperResult("This DNS Challenge API requires one or more credentials to be specified."); } catch (Exception exp) { - return new DnsChallengeHelperResult(failureMsg: $"DNS Challenge API Provider could not be created. Check all required credentials are set. {exp.ToString()}"); + return new DnsChallengeHelperResult($"DNS Challenge API Provider could not be created. Check all required credentials are set. {exp.ToString()}"); } if (dnsAPIProvider == null) From b06d85261afdb2c913dd7e3a58209e56bf18f434 Mon Sep 17 00:00:00 2001 From: Justin Nelson Date: Thu, 19 Oct 2023 22:26:25 -0700 Subject: [PATCH 066/328] Simplify SelectCAWithFailover based on GetAccountsWithRequiredCAFeatures Filtering logic for SelectCAWithFailover() on line 323 is already handled in GetAccountsWithRequiredCAFeatures() on line 280 Change IP_SINGLE feature logic to match DOMAIN_SINGLE logic (line 252) Expanded unit testing scenarios in CAFailoverTests for RenewalManager Added in the start of a test for CAs with TnAuthList Code is commented out, as it requires addition setup info (A test CA auth token) to function properly Split up existing AccessControl unit test and added new tests The existing unit test for Access Control was testing too many things, so it was split into several tests, and new tests were added for testing methods such as GetSecurityPrinciple(), UpdateSecurityPrinciple(), and DeleteSecurityPrinciple(). Update fixing the Update() method for MemoryObjectStore The existing method did not correctly call TryUpdate() as it did not use the previous value of the key for comparison See https://learn.microsoft.com/en-us/dotnet/api/system.collections.concurrent.concurrentdictionary-2.tryupdate?view=net-7.0 Fixes for UpdateSecurityPrinciple and DeleteSecurityPrinciple failures The current logic for these methods to not report back if the data mutation methods fail, and assume they always succeed. Added test for AccessControl.UpdateSecurityPrinciplePassword() Added more negative testing for error cases of methods in AccessControl One if-logic branch of IsAuthorised() is still uncovered on line 174 of AccessControl Added TestInitialize Method to reduce duplication in AccessControlTests --- .../Management/Access/AccessControl.cs | 20 +- src/Certify.Core/Management/RenewalManager.cs | 4 +- .../AccessControlTests.cs | 741 +++++++++++++++--- .../CAFailoverTests.cs | 63 +- 4 files changed, 689 insertions(+), 139 deletions(-) diff --git a/src/Certify.Core/Management/Access/AccessControl.cs b/src/Certify.Core/Management/Access/AccessControl.cs index 9dcc74c3c..f73d9baa1 100644 --- a/src/Certify.Core/Management/Access/AccessControl.cs +++ b/src/Certify.Core/Management/Access/AccessControl.cs @@ -64,7 +64,15 @@ public async Task UpdateSecurityPrinciple(string contextUserId, SecurityPr return false; } - await _store.Update(nameof(SecurityPrinciple), principle); + try + { + var updated = _store.Update(nameof(SecurityPrinciple), principle); + } + catch + { + _log?.Warning($"User {contextUserId} attempted to use UpdateSecurityPrinciple [{principle?.Id}], but was not successful"); + return false; + } _log?.Information($"User {contextUserId} updated security principle [{principle?.Id}] {principle?.Username}"); return true; @@ -92,8 +100,13 @@ public async Task DeleteSecurityPrinciple(string contextUserId, string id, var existing = await GetSecurityPrinciple(contextUserId, id); - await _store.Delete(nameof(SecurityPrinciple), id); + var deleted = await _store.Delete(nameof(SecurityPrinciple), id); + if (deleted != true) + { + _log?.Warning($"User {contextUserId} attempted to delete security principle [{id}] {existing?.Username}, but was not successful"); + return false; + } // TODO: remove assigned roles _log?.Information($"User {contextUserId} deleted security principle [{id}] {existing?.Username}"); @@ -127,7 +140,8 @@ public async Task IsAuthorised(string contextUserId, string principleId, s if (spAssignedPolicies.Any(a => a.ResourceActions.Contains(actionId))) { - // if and of the service principles assigned roles are restricted by the type of resource type, check for identifier matches (e.g. role assignment restricted on domains ) + // if any of the service principles assigned roles are restricted by the type of resource type, + // check for identifier matches (e.g. role assignment restricted on domains ) if (spSpecificAssignedRoles.Any(a => a.IncludedResources.Any(r => r.ResourceType == resourceType))) { var allIncludedResources = spSpecificAssignedRoles.SelectMany(a => a.IncludedResources).Distinct(); diff --git a/src/Certify.Core/Management/RenewalManager.cs b/src/Certify.Core/Management/RenewalManager.cs index e93a25f4b..32a4c92c2 100644 --- a/src/Certify.Core/Management/RenewalManager.cs +++ b/src/Certify.Core/Management/RenewalManager.cs @@ -257,7 +257,7 @@ private static List GetAccountsWithRequiredCAFeatures(ManagedCer requiredCaFeatures.Add(CertAuthoritySupportedRequests.DOMAIN_MULTIPLE_SAN); } - if (identifiers.Any(i => i.IdentifierType == CertIdentifierType.Ip)) + if (identifiers.Count(i => i.IdentifierType == CertIdentifierType.Ip) == 1) { requiredCaFeatures.Add(CertAuthoritySupportedRequests.IP_SINGLE); } @@ -320,7 +320,7 @@ public static AccountDetails SelectCAWithFailover(ICollection f.CertificateAuthorityId != item.LastAttemptedCA && f.CertificateAuthorityId != defaultMatchingAccount?.CertificateAuthorityId); + var nextFallback = fallbackAccounts.FirstOrDefault(f => f.CertificateAuthorityId != item.LastAttemptedCA); if (nextFallback != null) { diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/AccessControlTests.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/AccessControlTests.cs index 138fbad82..66c6bb8bf 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/AccessControlTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/AccessControlTests.cs @@ -52,174 +52,679 @@ public Task Add(string itemType, T item) public Task Update(string itemType, T item) { var o = item as AccessStoreItem; - return Task.FromResult(_store.TryUpdate(o.Id, o, o)); + _store.TryGetValue(o.Id, out var value); + var c = Task.FromResult((T)Convert.ChangeType(value, typeof(T))).Result as AccessStoreItem; + var r = Task.FromResult(_store.TryUpdate(o.Id, o, c)); + if(r.Result == false) + { + throw new Exception("Could not store item type"); + } + + return r; } } - [TestClass] - public class AccessControlTests + public class TestAssignedRoles { - private List GetTestSecurityPrinciples() + public static AssignedRole TestAdmin { get; } = new AssignedRole + { // test administrator + RoleId = StandardRoles.Administrator.Id, + SecurityPrincipleId = "[test]" + }; + public static AssignedRole Admin { get; } = new AssignedRole + { // administrator + RoleId = StandardRoles.Administrator.Id, + SecurityPrincipleId = "admin_01" + }; + public static AssignedRole DevopsUserDomainConsumer { get; } = new AssignedRole + { // devops user in consumer role for a specific domain + RoleId = StandardRoles.CertificateConsumer.Id, + SecurityPrincipleId = "devops_user_01", + IncludedResources = new List{ + new Resource{ ResourceType=ResourceTypes.Domain, Identifier="www.example.com" }, + } + }; + public static AssignedRole DevopsUserWildcardDomainConsumer { get; } = new AssignedRole + { // devops user in consumer role for a wildcard domain + RoleId = StandardRoles.CertificateConsumer.Id, + SecurityPrincipleId = "devops_user_01", + IncludedResources = new List{ + new Resource{ ResourceType=ResourceTypes.Domain, Identifier="*.microsoft.com" }, + } + }; + } + + public class TestSecurityPrinciples + { + public static SecurityPrinciple TestAdmin() + { + return new SecurityPrinciple + { + Id = "[test]", + Username = "test administrator", + Description = "Example test administrator used as context user during test", + Email = "test_admin@test.com", + Password = "ABCDEFG", + PrincipleType = SecurityPrincipleType.User + }; + } + public static SecurityPrinciple Admin() + { + return new SecurityPrinciple + { + Id = "admin_01", + Username = "admin", + Description = "Administrator account", + Email = "info@test.com", + Password = "ABCDEFG", + PrincipleType = SecurityPrincipleType.User, + }; + } + public static SecurityPrinciple DomainOwner() + { + return new SecurityPrinciple + { + Id = "domain_owner_01", + Username = "demo_owner", + Description = "Example domain owner", + Email = "domains@test.com", + Password = "ABCDEFG", + PrincipleType = SecurityPrincipleType.User, + }; + } + public static SecurityPrinciple DevopsUser() { - return new List + return new SecurityPrinciple { - new SecurityPrinciple - { - Id = "admin_01", - Username = "admin", - Description = "Administrator account", - Email = "info@test.com", - Password = "ABCDEFG", - PrincipleType = SecurityPrincipleType.User - }, - new SecurityPrinciple - { - Id = "domain_owner_01", - Username = "demo_owner", - Description = "Example domain owner", - Email = "domains@test.com", - Password = "ABCDEFG", - PrincipleType = SecurityPrincipleType.User - }, - new SecurityPrinciple - { - Id = "devops_user_01", - Username = "devops_01", - Description = "Example devops user", - Email = "devops01@test.com", - Password = "ABCDEFG", - PrincipleType = SecurityPrincipleType.User - }, - new SecurityPrinciple - { - Id = "devops_app_01", - Username = "devapp_01", - Description = "Example devops app domain consumer", - Email = "dev_app01@test.com", - Password = "ABCDEFG", - PrincipleType = SecurityPrincipleType.User - }, - new SecurityPrinciple - { - Id = "[test]", - Username = "test administrator", - Description = "Example test administrator used as context user during test", - Email = "test_admin@test.com", - Password = "ABCDEFG", - PrincipleType = SecurityPrincipleType.User - } + Id = "devops_user_01", + Username = "devops_01", + Description = "Example devops user", + Email = "devops01@test.com", + Password = "ABCDEFG", + PrincipleType = SecurityPrincipleType.User, }; } + public static SecurityPrinciple DevopsAppDomainConsumer() + { + return new SecurityPrinciple + { + Id = "devops_app_01", + Username = "devapp_01", + Description = "Example devops app domain consumer", + Email = "dev_app01@test.com", + Password = "ABCDEFG", + PrincipleType = SecurityPrincipleType.User, + }; + } + } - [TestMethod] - public async Task TestAccessControlChecks() + [TestClass] + public class AccessControlTests + { + private Loggy loggy; + private AccessControl access; + private const string contextUserId = "[test]"; + + [TestInitialize] + public void TestInitialize() { - var log = new LoggerConfiguration() + this.loggy = new Loggy(new LoggerConfiguration() .WriteTo.Debug() - .CreateLogger(); - - var loggy = new Loggy(log); + .CreateLogger()); + this.access = new AccessControl(loggy, new MemoryObjectStore()); + } - var access = new AccessControl(loggy, new MemoryObjectStore()); + [TestMethod] + public async Task TestAccessControlAddGetSecurityPrinciples() + { + // Add test security principles + var adminSecurityPrinciples = new List { TestSecurityPrinciples.Admin(), TestSecurityPrinciples.TestAdmin() }; + adminSecurityPrinciples.ForEach(async p => await access.AddSecurityPrinciple(contextUserId, p, bypassIntegrityCheck: true)); - var contextUserId = "[test]"; + // Get stored security principles + var storedSecurityPrinciples = await access.GetSecurityPrinciples(contextUserId); - // add test security principles - var allPrinciples = GetTestSecurityPrinciples(); - foreach (var p in allPrinciples) + // Validate SecurityPrinciple list returned by AccessControl.GetSecurityPrinciples() + Assert.IsNotNull(storedSecurityPrinciples, "Expected list returned by AccessControl.GetSecurityPrinciples() to not be null"); + Assert.AreEqual(2, storedSecurityPrinciples.Count, "Expected list returned by AccessControl.GetSecurityPrinciples() to have 2 SecurityPrinciple objects"); + foreach (var passedPrinciple in adminSecurityPrinciples) { - _ = await access.AddSecurityPrinciple(contextUserId, p, bypassIntegrityCheck: true); + Assert.IsNotNull(storedSecurityPrinciples.Find(x => x.Id == passedPrinciple.Id), $"Expected a SecurityPrinciple returned by GetSecurityPrinciples() to match Id '{passedPrinciple.Id}' of SecurityPrinciple passed into AddSecurityPrinciple()"); } + } - // setup known actions - var actions = Policies.GetStandardResourceActions(); + [TestMethod] + public async Task TestAccessControlGetSecurityPrinciplesNoRoles() + { + // Add test security principles + var securityPrincipleAdded = await access.AddSecurityPrinciple(contextUserId, TestSecurityPrinciples.TestAdmin()); + + // Get stored security principles + Assert.IsFalse(securityPrincipleAdded, $"Expected AddSecurityPrinciple() to be unsuccessful without roles defined for {contextUserId}"); + } - foreach (var action in actions) + [TestMethod] + public async Task TestAccessControlAddGetSecurityPrinciple() + { + // Add test security principles + var adminSecurityPrinciples = new List { TestSecurityPrinciples.Admin(), TestSecurityPrinciples.TestAdmin() }; + adminSecurityPrinciples.ForEach(async p => await access.AddSecurityPrinciple(contextUserId, p, bypassIntegrityCheck: true)); + + foreach (var securityPrinciple in adminSecurityPrinciples) { - await access.AddAction(action); + // Get stored security principle + var storedSecurityPrinciple = await access.GetSecurityPrinciple(contextUserId, securityPrinciple.Id); + + // Validate SecurityPrinciple object returned by AccessControl.GetSecurityPrinciple() + Assert.IsNotNull(storedSecurityPrinciple, "Expected object returned by AccessControl.GetSecurityPrinciple() to not be null"); + Assert.AreEqual(storedSecurityPrinciple.Id, securityPrinciple.Id, $"Expected SecurityPrinciple returned by GetSecurityPrinciple() to match Id '{securityPrinciple.Id}' of SecurityPrinciple passed into AddSecurityPrinciple()"); } + } - // setup policies with actions + [TestMethod] + public async Task TestAccessControlAddGetAssignedRoles() + { + // Add test security principles + var adminSecurityPrinciples = new List { TestSecurityPrinciples.Admin(), TestSecurityPrinciples.TestAdmin() }; + adminSecurityPrinciples.ForEach(async p => await access.AddSecurityPrinciple(contextUserId, p, bypassIntegrityCheck: true)); - var policies = Policies.GetStandardPolicies(); + // Setup security principle actions + var actions = Policies.GetStandardResourceActions().FindAll(a => a.ResourceType == ResourceTypes.System); + actions.ForEach(async a => await access.AddAction(a)); - // add policies to store - foreach (var r in policies) - { - _ = await access.AddResourcePolicy(contextUserId, r, bypassIntegrityCheck: true); - } + // Setup policy with actions and add policy to store + var policy = Policies.GetStandardPolicies().Find(p => p.Id == "access_admin"); + _ = await access.AddResourcePolicy(contextUserId, policy, bypassIntegrityCheck: true); + + // Setup and add roles and policy assignments to store + var role = (await access.GetSystemRoles()).Find(r => r.Id == StandardRoles.Administrator.Id); + await access.AddRole(role); - // setup roles with policies - var roles = await access.GetSystemRoles(); + // Assign security principles to roles and add roles and policy assignments to store + var assignedRoles = new List { TestAssignedRoles.Admin, TestAssignedRoles.TestAdmin }; + assignedRoles.ForEach(async r => await access.AddAssignedRole(r)); - foreach (var r in roles) + // Validate AssignedRole list returned by AccessControl.GetAssignedRoles() + foreach (var assignedRole in assignedRoles) { - // add roles and policy assignments to store - await access.AddRole(r); + var adminAssignedRoles = await access.GetAssignedRoles(contextUserId, assignedRole.SecurityPrincipleId); + Assert.IsNotNull(adminAssignedRoles, "Expected list returned by AccessControl.GetAssignedRoles() to not be null"); + Assert.AreEqual(1, adminAssignedRoles.Count, "Expected list returned by AccessControl.GetAssignedRoles() to have 1 AssignedRole object"); + Assert.AreEqual(assignedRole.SecurityPrincipleId, adminAssignedRoles[0].SecurityPrincipleId, "Expected AssignedRole returned by GetAssignedRoles() to match SecurityPrincipleId of AssignedRole passed into AddAssignedRole()"); } + } - // assign security principles to roles - var assignedRoles = new List { - // test administrator - new AssignedRole{ - RoleId=StandardRoles.Administrator.Id, - SecurityPrincipleId="[test]" - }, - // administrator - new AssignedRole{ - RoleId=StandardRoles.Administrator.Id, - SecurityPrincipleId="admin_01" - }, - // devops user in consumer role for a couple of specific domains - new AssignedRole{ - RoleId=StandardRoles.CertificateConsumer.Id, - SecurityPrincipleId="devops_user_01", - IncludedResources=new List{ - new Resource{ ResourceType=ResourceTypes.Domain, Identifier="www.example.com" }, - new Resource{ ResourceType=ResourceTypes.Domain, Identifier="*.microsoft.com" } - } - } - }; + [TestMethod] + public async Task TestAccessControlGetAssignedRolesNoRoles() + { + // Add test security principles + var adminSecurityPrinciples = new List { TestSecurityPrinciples.Admin(), TestSecurityPrinciples.TestAdmin() }; + adminSecurityPrinciples.ForEach(async p => await access.AddSecurityPrinciple(contextUserId, p, bypassIntegrityCheck: true)); + + // Validate AssignedRole list returned by AccessControl.GetAssignedRoles() + var adminAssignedRoles = await access.GetAssignedRoles(contextUserId, adminSecurityPrinciples[0].Id); + Assert.IsNotNull(adminAssignedRoles, "Expected list returned by AccessControl.GetAssignedRoles() to not be null"); + Assert.AreEqual(0, adminAssignedRoles.Count, "Expected list returned by AccessControl.GetAssignedRoles() to have no AssignedRole objects"); + } - foreach (var r in assignedRoles) - { - // add roles and policy assignments to store - await access.AddAssignedRole(r); - } + [TestMethod] + public async Task TestAccessControlAddResourcePolicyNoRoles() + { + // Add test security principles + var adminSecurityPrinciples = new List { TestSecurityPrinciples.Admin(), TestSecurityPrinciples.TestAdmin() }; + adminSecurityPrinciples.ForEach(async p => await access.AddSecurityPrinciple(contextUserId, p, bypassIntegrityCheck: true)); + + // Setup security principle actions + var actions = Policies.GetStandardResourceActions().FindAll(a => a.ResourceType == ResourceTypes.System); + actions.ForEach(async a => await access.AddAction(a)); - // assert + // Setup policy with actions and add policy to store + var policy = Policies.GetStandardPolicies().Find(p => p.Id == "access_admin"); + var addedResourcePolicy = await access.AddResourcePolicy(contextUserId, policy); - var adminAssignedRoles = await access.GetAssignedRoles(contextUserId, "admin_01"); - Assert.AreEqual(1, adminAssignedRoles.Count); + // Validate that AddResourcePolicy() failed when no roles are defined + Assert.IsFalse(addedResourcePolicy, $"Unable to add a resource policy using {contextUserId} when roles are undefined"); + } + + [TestMethod] + public async Task TestAccessControlUpdateSecurityPrinciple() + { + // Add test security principles + var adminSecurityPrinciples = new List { TestSecurityPrinciples.Admin(), TestSecurityPrinciples.TestAdmin() }; + adminSecurityPrinciples.ForEach(async p => await access.AddSecurityPrinciple(contextUserId, p, bypassIntegrityCheck: true)); + + // Setup security principle actions + var actions = Policies.GetStandardResourceActions().FindAll(a => a.ResourceType == ResourceTypes.System); + actions.ForEach(async a => await access.AddAction(a)); + + // Setup policy with actions and add policy to store + var policy = Policies.GetStandardPolicies().Find(p => p.Id == "access_admin"); + _ = await access.AddResourcePolicy(contextUserId, policy, bypassIntegrityCheck: true); + + // Setup and add roles and policy assignments to store + var role = (await access.GetSystemRoles()).Find(r => r.Id == StandardRoles.Administrator.Id); + await access.AddRole(role); + + // Assign security principles to roles and add roles and policy assignments to store + var assignedRoles = new List { TestAssignedRoles.Admin, TestAssignedRoles.TestAdmin }; + assignedRoles.ForEach(async r => await access.AddAssignedRole(r)); + + // Validate email of SecurityPrinciple object returned by AccessControl.GetSecurityPrinciple() before update + var storedSecurityPrinciple = await access.GetSecurityPrinciple(contextUserId, adminSecurityPrinciples[0].Id); + Assert.AreEqual(storedSecurityPrinciple.Email, adminSecurityPrinciples[0].Email, $"Expected SecurityPrinciple returned by GetSecurityPrinciple() to match Email '{adminSecurityPrinciples[0].Email}' of SecurityPrinciple passed into AddSecurityPrinciple()"); + + // Update security principle in AccessControl with a new principle object of the same Id, but different email + var newSecurityPrinciple = TestSecurityPrinciples.Admin(); + newSecurityPrinciple.Email = "new_test_email@test.com"; + var securityPrincipleUpdated = await access.UpdateSecurityPrinciple(contextUserId, newSecurityPrinciple); + Assert.IsTrue(securityPrincipleUpdated, $"Expected security principle update for {newSecurityPrinciple.Id} to succeed"); + + // Validate email of SecurityPrinciple object returned by AccessControl.GetSecurityPrinciple() after update + storedSecurityPrinciple = await access.GetSecurityPrinciple(contextUserId, newSecurityPrinciple.Id); + Assert.AreNotEqual(storedSecurityPrinciple.Email, adminSecurityPrinciples[0].Email, $"Expected SecurityPrinciple returned by GetSecurityPrinciple() to not match previous Email '{adminSecurityPrinciples[0].Email}' of SecurityPrinciple passed into AddSecurityPrinciple()"); + Assert.AreEqual(storedSecurityPrinciple.Email, newSecurityPrinciple.Email, $"Expected SecurityPrinciple returned by GetSecurityPrinciple() to match updated Email '{newSecurityPrinciple.Email}' of SecurityPrinciple passed into AddSecurityPrinciple()"); + } - var hasAccess = await access.IsPrincipleInRole(contextUserId, "admin_01", StandardRoles.Administrator.Id); - Assert.IsTrue(hasAccess, "User should be in role"); + [TestMethod] + public async Task TestAccessControlUpdateSecurityPrincipleNoRoles() + { + // Add test security principles + var adminSecurityPrinciples = new List { TestSecurityPrinciples.Admin(), TestSecurityPrinciples.TestAdmin() }; + adminSecurityPrinciples.ForEach(async p => await access.AddSecurityPrinciple(contextUserId, p, bypassIntegrityCheck: true)); + + // Validate email of SecurityPrinciple object returned by AccessControl.GetSecurityPrinciple() before update + var storedSecurityPrinciple = await access.GetSecurityPrinciple(contextUserId, adminSecurityPrinciples[0].Id); + Assert.AreEqual(storedSecurityPrinciple.Email, adminSecurityPrinciples[0].Email, $"Expected SecurityPrinciple returned by GetSecurityPrinciple() to match Email '{adminSecurityPrinciples[0].Email}' of SecurityPrinciple passed into AddSecurityPrinciple()"); + + // Update security principle in AccessControl with a new principle object of the same Id, but different email, with roles undefined + var newSecurityPrinciple = TestSecurityPrinciples.Admin(); + newSecurityPrinciple.Email = "new_test_email@test.com"; + var securityPrincipleUpdated = await access.UpdateSecurityPrinciple(contextUserId, newSecurityPrinciple); + Assert.IsFalse(securityPrincipleUpdated, $"Expected security principle update for {newSecurityPrinciple.Id} to be unsuccessful without roles defined"); + } + [TestMethod] + public async Task TestAccessControlUpdateSecurityPrincipleBadUpdate() + { + // Add test security principles + var adminSecurityPrinciples = new List { TestSecurityPrinciples.Admin(), TestSecurityPrinciples.TestAdmin() }; + adminSecurityPrinciples.ForEach(async p => await access.AddSecurityPrinciple(contextUserId, p, bypassIntegrityCheck: true)); + + // Setup security principle actions + var actions = Policies.GetStandardResourceActions().FindAll(a => a.ResourceType == ResourceTypes.System); + actions.ForEach(async a => await access.AddAction(a)); + + // Setup policy with actions and add policy to store + var policy = Policies.GetStandardPolicies().Find(p => p.Id == "access_admin"); + _ = await access.AddResourcePolicy(contextUserId, policy, bypassIntegrityCheck: true); + + // Setup and add roles and policy assignments to store + var role = (await access.GetSystemRoles()).Find(r => r.Id == StandardRoles.Administrator.Id); + await access.AddRole(role); + + // Assign security principles to roles and add roles and policy assignments to store + var assignedRoles = new List { TestAssignedRoles.Admin, TestAssignedRoles.TestAdmin }; + assignedRoles.ForEach(async r => await access.AddAssignedRole(r)); + + // Validate email of SecurityPrinciple object returned by AccessControl.GetSecurityPrinciple() before update + var storedSecurityPrinciple = await access.GetSecurityPrinciple(contextUserId, adminSecurityPrinciples[0].Id); + Assert.AreEqual(storedSecurityPrinciple.Email, adminSecurityPrinciples[0].Email, $"Expected SecurityPrinciple returned by GetSecurityPrinciple() to match Email '{adminSecurityPrinciples[0].Email}' of SecurityPrinciple passed into AddSecurityPrinciple()"); + + // Update security principle in AccessControl with a new principle object with a bad Id name and different email + var newSecurityPrinciple = TestSecurityPrinciples.Admin(); + newSecurityPrinciple.Email = "new_test_email@test.com"; + newSecurityPrinciple.Id = "missing_username"; + var securityPrincipleUpdated = await access.UpdateSecurityPrinciple(contextUserId, newSecurityPrinciple); + Assert.IsFalse(securityPrincipleUpdated, $"Expected security principle update for {newSecurityPrinciple.Id} to be unsuccessful with bad update data (Id does not already exist in store)"); + } + + [TestMethod] + public async Task TestAccessControlUpdateSecurityPrinciplePassword() + { + // Add test security principles + var adminSecurityPrinciples = new List { TestSecurityPrinciples.Admin(), TestSecurityPrinciples.TestAdmin() }; + var firstPassword = adminSecurityPrinciples[0].Password; + adminSecurityPrinciples.ForEach(async p => await access.AddSecurityPrinciple(contextUserId, p, bypassIntegrityCheck: true)); + + // Setup security principle actions + var actions = Policies.GetStandardResourceActions().FindAll(a => a.ResourceType == ResourceTypes.System); + actions.ForEach(async a => await access.AddAction(a)); + + // Setup policy with actions and add policy to store + var policy = Policies.GetStandardPolicies().Find(p => p.Id == "access_admin"); + _ = await access.AddResourcePolicy(contextUserId, policy, bypassIntegrityCheck: true); + + // Setup and add roles and policy assignments to store + var role = (await access.GetSystemRoles()).Find(r => r.Id == StandardRoles.Administrator.Id); + await access.AddRole(role); + + // Assign security principles to roles and add roles and policy assignments to store + var assignedRoles = new List { TestAssignedRoles.Admin, TestAssignedRoles.TestAdmin }; + assignedRoles.ForEach(async r => await access.AddAssignedRole(r)); + + // Validate password of SecurityPrinciple object returned by AccessControl.GetSecurityPrinciple() before update + var storedSecurityPrinciple = await access.GetSecurityPrinciple(contextUserId, adminSecurityPrinciples[0].Id); + var firstPasswordHashed = access.HashPassword(firstPassword, storedSecurityPrinciple.Password.Split('.')[1]); + Assert.AreEqual(storedSecurityPrinciple.Password, firstPasswordHashed, $"Expected SecurityPrinciple returned by GetSecurityPrinciple() to match Password '{firstPasswordHashed}' of SecurityPrinciple passed into AddSecurityPrinciple()"); + + // Update security principle in AccessControl with a new password + var newPassword = "GFEDCBA"; + var securityPrincipleUpdated = await access.UpdateSecurityPrinciplePassword(contextUserId, adminSecurityPrinciples[0].Id, firstPassword, newPassword); + Assert.IsTrue(securityPrincipleUpdated, $"Expected security principle password update for {adminSecurityPrinciples[0].Id} to succeed"); + + // Validate password of SecurityPrinciple object returned by AccessControl.GetSecurityPrinciple() after update + storedSecurityPrinciple = await access.GetSecurityPrinciple(contextUserId, adminSecurityPrinciples[0].Id); + var newPasswordHashed = access.HashPassword(newPassword, storedSecurityPrinciple.Password.Split('.')[1]); + Assert.AreNotEqual(storedSecurityPrinciple.Password, firstPasswordHashed, $"Expected SecurityPrinciple returned by GetSecurityPrinciple() to not match previous Password '{firstPasswordHashed}' of SecurityPrinciple passed into AddSecurityPrinciple()"); + Assert.AreEqual(storedSecurityPrinciple.Password, newPasswordHashed, $"Expected SecurityPrinciple returned by GetSecurityPrinciple() to match updated Password '{newPasswordHashed}' of SecurityPrinciple passed into AddSecurityPrinciple()"); + } + + [TestMethod] + public async Task TestAccessControlUpdateSecurityPrinciplePasswordNoRoles() + { + // Add test security principles + var adminSecurityPrinciples = new List { TestSecurityPrinciples.Admin(), TestSecurityPrinciples.TestAdmin() }; + var firstPassword = adminSecurityPrinciples[0].Password; + adminSecurityPrinciples.ForEach(async p => await access.AddSecurityPrinciple(contextUserId, p, bypassIntegrityCheck: true)); + + // Update security principle in AccessControl with a new password + var newPassword = "GFEDCBA"; + var securityPrincipleUpdated = await access.UpdateSecurityPrinciplePassword(contextUserId, adminSecurityPrinciples[0].Id, firstPassword, newPassword); + Assert.IsFalse(securityPrincipleUpdated, $"Expected security principle password update for {adminSecurityPrinciples[0].Id} to fail without roles"); + + // Validate password of SecurityPrinciple object returned by AccessControl.GetSecurityPrinciple() after failed update + var storedSecurityPrinciple = await access.GetSecurityPrinciple(contextUserId, adminSecurityPrinciples[0].Id); + var firstPasswordHashed = access.HashPassword(firstPassword, storedSecurityPrinciple.Password.Split('.')[1]); + Assert.AreEqual(storedSecurityPrinciple.Password, firstPasswordHashed, $"Expected SecurityPrinciple returned by GetSecurityPrinciple() to match Password '{firstPasswordHashed}' of SecurityPrinciple passed into AddSecurityPrinciple()"); + } + + [TestMethod] + public async Task TestAccessControlUpdateSecurityPrinciplePasswordBadPassword() + { + // Add test security principles + var adminSecurityPrinciples = new List { TestSecurityPrinciples.Admin(), TestSecurityPrinciples.TestAdmin() }; + var firstPassword = adminSecurityPrinciples[0].Password; + adminSecurityPrinciples.ForEach(async p => await access.AddSecurityPrinciple(contextUserId, p, bypassIntegrityCheck: true)); + + // Setup security principle actions + var actions = Policies.GetStandardResourceActions().FindAll(a => a.ResourceType == ResourceTypes.System); + actions.ForEach(async a => await access.AddAction(a)); + + // Setup policy with actions and add policy to store + var policy = Policies.GetStandardPolicies().Find(p => p.Id == "access_admin"); + _ = await access.AddResourcePolicy(contextUserId, policy, bypassIntegrityCheck: true); + + // Setup and add roles and policy assignments to store + var role = (await access.GetSystemRoles()).Find(r => r.Id == StandardRoles.Administrator.Id); + await access.AddRole(role); + + // Assign security principles to roles and add roles and policy assignments to store + var assignedRoles = new List { TestAssignedRoles.Admin, TestAssignedRoles.TestAdmin }; + assignedRoles.ForEach(async r => await access.AddAssignedRole(r)); + + // Update security principle in AccessControl with a new password, but wrong original password + var newPassword = "GFEDCBA"; + var securityPrincipleUpdated = await access.UpdateSecurityPrinciplePassword(contextUserId, adminSecurityPrinciples[0].Id, firstPassword.ToLower(), newPassword); + Assert.IsFalse(securityPrincipleUpdated, $"Expected security principle password update for {adminSecurityPrinciples[0].Id} to fail with wrong password"); + + // Validate password of SecurityPrinciple object returned by AccessControl.GetSecurityPrinciple() after failed update + var storedSecurityPrinciple = await access.GetSecurityPrinciple(contextUserId, adminSecurityPrinciples[0].Id); + var firstPasswordHashed = access.HashPassword(firstPassword, storedSecurityPrinciple.Password.Split('.')[1]); + Assert.AreEqual(storedSecurityPrinciple.Password, firstPasswordHashed, $"Expected SecurityPrinciple returned by GetSecurityPrinciple() to match Password '{firstPasswordHashed}' of SecurityPrinciple passed into AddSecurityPrinciple()"); + } + + [TestMethod] + public async Task TestAccessControlDeleteSecurityPrinciple() + { + // Add test security principles + var adminSecurityPrinciples = new List { TestSecurityPrinciples.Admin(), TestSecurityPrinciples.TestAdmin() }; + adminSecurityPrinciples.ForEach(async p => await access.AddSecurityPrinciple(contextUserId, p, bypassIntegrityCheck: true)); + + // Setup security principle actions + var actions = Policies.GetStandardResourceActions().FindAll(a => a.ResourceType == ResourceTypes.System); + actions.ForEach(async a => await access.AddAction(a)); + + // Setup policy with actions and add policy to store + var policy = Policies.GetStandardPolicies().Find(p => p.Id == "access_admin"); + _ = await access.AddResourcePolicy(contextUserId, policy, bypassIntegrityCheck: true); + + // Setup and add roles and policy assignments to store + var role = (await access.GetSystemRoles()).Find(r => r.Id == StandardRoles.Administrator.Id); + await access.AddRole(role); + + // Assign security principles to roles and add roles and policy assignments to store + var assignedRoles = new List { TestAssignedRoles.Admin, TestAssignedRoles.TestAdmin }; + assignedRoles.ForEach(async r => await access.AddAssignedRole(r)); + + // Validate SecurityPrinciple object returned by AccessControl.GetSecurityPrinciple() before delete is not null + var storedSecurityPrinciple = await access.GetSecurityPrinciple(contextUserId, adminSecurityPrinciples[0].Id); + Assert.IsNotNull(storedSecurityPrinciple, "Expected object returned by AccessControl.GetSecurityPrinciple() to not be null"); + Assert.AreEqual(storedSecurityPrinciple.Id, adminSecurityPrinciples[0].Id, $"Expected SecurityPrinciple returned by GetSecurityPrinciple() to match Id '{adminSecurityPrinciples[0].Id}' of SecurityPrinciple passed into AddSecurityPrinciple()"); + + // Delete first security principle in adminSecurityPrinciples list from AccessControl store + var securityPrincipleDeleted = await access.DeleteSecurityPrinciple(contextUserId, adminSecurityPrinciples[0].Id); + Assert.IsTrue(securityPrincipleDeleted, $"Expected security principle deletion for {adminSecurityPrinciples[0].Id} to succeed"); + + // Validate SecurityPrinciple object returned by AccessControl.GetSecurityPrinciple() after delete is null + storedSecurityPrinciple = await access.GetSecurityPrinciple(contextUserId, adminSecurityPrinciples[0].Id); + Assert.IsNull(storedSecurityPrinciple, $"Expected SecurityPrinciple for '{adminSecurityPrinciples[0].Id}' to be null from GetSecurityPrinciple()"); + } + + [TestMethod] + public async Task TestAccessControlDeleteSecurityPrincipleNoRoles() + { + // Add test security principles + var adminSecurityPrinciples = new List { TestSecurityPrinciples.Admin(), TestSecurityPrinciples.TestAdmin() }; + adminSecurityPrinciples.ForEach(async p => await access.AddSecurityPrinciple(contextUserId, p, bypassIntegrityCheck: true)); + + // Validate SecurityPrinciple object returned by AccessControl.GetSecurityPrinciple() before delete is not null + var storedSecurityPrinciple = await access.GetSecurityPrinciple(contextUserId, adminSecurityPrinciples[0].Id); + Assert.IsNotNull(storedSecurityPrinciple, "Expected object returned by AccessControl.GetSecurityPrinciple() to not be null"); + Assert.AreEqual(storedSecurityPrinciple.Id, adminSecurityPrinciples[0].Id, $"Expected SecurityPrinciple returned by GetSecurityPrinciple() to match Id '{adminSecurityPrinciples[0].Id}' of SecurityPrinciple passed into AddSecurityPrinciple()"); + + // Try to delete first security principle in adminSecurityPrinciples list from AccessControl store + var securityPrincipleDeleted = await access.DeleteSecurityPrinciple(contextUserId, adminSecurityPrinciples[0].Id); + Assert.IsFalse(securityPrincipleDeleted, $"Expected security principle deletion for {adminSecurityPrinciples[0].Id} to fail without roles defined"); + + // Validate SecurityPrinciple object returned by AccessControl.GetSecurityPrinciple() after delete is not null + storedSecurityPrinciple = await access.GetSecurityPrinciple(contextUserId, adminSecurityPrinciples[0].Id); + Assert.IsNotNull(storedSecurityPrinciple, $"Expected SecurityPrinciple for '{adminSecurityPrinciples[0].Id}' to not be null from GetSecurityPrinciple()"); + } + + [TestMethod] + public async Task TestAccessControlDeleteSecurityPrincipleSelfDeletion() + { + // Add test security principles + var adminSecurityPrinciples = new List { TestSecurityPrinciples.Admin(), TestSecurityPrinciples.TestAdmin() }; + adminSecurityPrinciples.ForEach(async p => await access.AddSecurityPrinciple(contextUserId, p, bypassIntegrityCheck: true)); + + // Setup security principle actions + var actions = Policies.GetStandardResourceActions().FindAll(a => a.ResourceType == ResourceTypes.System); + actions.ForEach(async a => await access.AddAction(a)); + + // Setup policy with actions and add policy to store + var policy = Policies.GetStandardPolicies().Find(p => p.Id == "access_admin"); + _ = await access.AddResourcePolicy(contextUserId, policy, bypassIntegrityCheck: true); + + // Setup and add roles and policy assignments to store + var role = (await access.GetSystemRoles()).Find(r => r.Id == StandardRoles.Administrator.Id); + await access.AddRole(role); + + // Assign security principles to roles and add roles and policy assignments to store + var assignedRoles = new List { TestAssignedRoles.Admin, TestAssignedRoles.TestAdmin }; + assignedRoles.ForEach(async r => await access.AddAssignedRole(r)); + + // Validate SecurityPrinciple object returned by AccessControl.GetSecurityPrinciple() before delete is not null + var storedSecurityPrinciple = await access.GetSecurityPrinciple(contextUserId, adminSecurityPrinciples[1].Id); + Assert.IsNotNull(storedSecurityPrinciple, "Expected object returned by AccessControl.GetSecurityPrinciple() to not be null"); + Assert.AreEqual(storedSecurityPrinciple.Id, adminSecurityPrinciples[1].Id, $"Expected SecurityPrinciple returned by GetSecurityPrinciple() to match Id '{adminSecurityPrinciples[1].Id}' of SecurityPrinciple passed into AddSecurityPrinciple()"); + + // Try to delete second security principle in adminSecurityPrinciples list from AccessControl store + var securityPrincipleDeleted = await access.DeleteSecurityPrinciple(contextUserId, contextUserId); + Assert.IsFalse(securityPrincipleDeleted, $"Expected security principle self deletion for {contextUserId} to fail"); + + // Validate SecurityPrinciple object returned by AccessControl.GetSecurityPrinciple() after delete is not null + storedSecurityPrinciple = await access.GetSecurityPrinciple(contextUserId, adminSecurityPrinciples[1].Id); + Assert.IsNotNull(storedSecurityPrinciple, $"Expected SecurityPrinciple for '{adminSecurityPrinciples[1].Id}' to not be null from GetSecurityPrinciple()"); + } + + [TestMethod] + public async Task TestAccessControlDeleteSecurityPrincipleBadId() + { + // Add test security principles + var adminSecurityPrinciples = new List { TestSecurityPrinciples.Admin(), TestSecurityPrinciples.TestAdmin() }; + adminSecurityPrinciples.ForEach(async p => await access.AddSecurityPrinciple(contextUserId, p, bypassIntegrityCheck: true)); + + // Setup security principle actions + var actions = Policies.GetStandardResourceActions().FindAll(a => a.ResourceType == ResourceTypes.System); + actions.ForEach(async a => await access.AddAction(a)); + + // Setup policy with actions and add policy to store + var policy = Policies.GetStandardPolicies().Find(p => p.Id == "access_admin"); + _ = await access.AddResourcePolicy(contextUserId, policy, bypassIntegrityCheck: true); + + // Setup and add roles and policy assignments to store + var role = (await access.GetSystemRoles()).Find(r => r.Id == StandardRoles.Administrator.Id); + await access.AddRole(role); + + // Assign security principles to roles and add roles and policy assignments to store + var assignedRoles = new List { TestAssignedRoles.Admin, TestAssignedRoles.TestAdmin }; + assignedRoles.ForEach(async r => await access.AddAssignedRole(r)); + + // Validate SecurityPrinciple object returned by AccessControl.GetSecurityPrinciple() before delete is not null + var storedSecurityPrinciple = await access.GetSecurityPrinciple(contextUserId, adminSecurityPrinciples[1].Id); + Assert.IsNotNull(storedSecurityPrinciple, "Expected object returned by AccessControl.GetSecurityPrinciple() to not be null"); + Assert.AreEqual(storedSecurityPrinciple.Id, adminSecurityPrinciples[1].Id, $"Expected SecurityPrinciple returned by GetSecurityPrinciple() to match Id '{adminSecurityPrinciples[1].Id}' of SecurityPrinciple passed into AddSecurityPrinciple()"); + + // Try to delete second security principle in adminSecurityPrinciples list from AccessControl store + var securityPrincipleDeleted = await access.DeleteSecurityPrinciple(contextUserId, contextUserId.ToUpper()); + Assert.IsFalse(securityPrincipleDeleted, $"Expected security principle deletion for {contextUserId.ToUpper()} to fail"); + + // Validate SecurityPrinciple object returned by AccessControl.GetSecurityPrinciple() after delete is not null + storedSecurityPrinciple = await access.GetSecurityPrinciple(contextUserId, adminSecurityPrinciples[1].Id); + Assert.IsNotNull(storedSecurityPrinciple, $"Expected SecurityPrinciple for '{adminSecurityPrinciples[1].Id}' to not be null from GetSecurityPrinciple()"); + } + + [TestMethod] + public async Task TestAccessControlIsPrincipleInRole() + { + // Add test security principles + var adminSecurityPrinciples = new List { TestSecurityPrinciples.Admin(), TestSecurityPrinciples.TestAdmin() }; + adminSecurityPrinciples.ForEach(async p => await access.AddSecurityPrinciple(contextUserId, p, bypassIntegrityCheck: true)); + + // Setup security principle actions + var actions = Policies.GetStandardResourceActions().FindAll(a => a.ResourceType == ResourceTypes.System); + actions.ForEach(async a => await access.AddAction(a)); + + // Setup policy with actions and add policy to store + var policy = Policies.GetStandardPolicies().Find(p => p.Id == "access_admin"); + _ = await access.AddResourcePolicy(contextUserId, policy, bypassIntegrityCheck: true); + + // Setup and add roles and policy assignments to store + var role = (await access.GetSystemRoles()).Find(r => r.Id == StandardRoles.Administrator.Id); + await access.AddRole(role); + + // Assign security principles to roles and add roles and policy assignments to store + var assignedRoles = new List { TestAssignedRoles.Admin, TestAssignedRoles.TestAdmin }; + assignedRoles.ForEach(async r => await access.AddAssignedRole(r)); + + // Validate specified admin user is a principle role + bool hasAccess; + foreach (var assignedRole in assignedRoles) + { + hasAccess = await access.IsPrincipleInRole(contextUserId, assignedRole.SecurityPrincipleId, StandardRoles.Administrator.Id); + Assert.IsTrue(hasAccess, $"User '{assignedRole.SecurityPrincipleId}' should be in role"); + } + + // Validate fake admin user is not a principle role hasAccess = await access.IsPrincipleInRole(contextUserId, "admin_02", StandardRoles.Administrator.Id); Assert.IsFalse(hasAccess, "User should not be in role"); + } + + [TestMethod] + public async Task TestAccessControlDomainAuth() + { + // Add test devops user security principle + _ = await access.AddSecurityPrinciple(contextUserId, TestSecurityPrinciples.DevopsUser(), bypassIntegrityCheck: true); + + // Setup security principle actions + await access.AddAction(Policies.GetStandardResourceActions().Find(r => r.Id == "certificate_download")); + + // Setup policy with actions and add policy to store + var policy = Policies.GetStandardPolicies().Find(p => p.Id == "certificate_consumer"); + _ = await access.AddResourcePolicy(contextUserId, policy, bypassIntegrityCheck: true); + + // Setup and add roles and policy assignments to store + var role = (await access.GetSystemRoles()).Find(r => r.Id == StandardRoles.CertificateConsumer.Id); + await access.AddRole(role); + + // Assign security principles to roles and add roles and policy assignments to store + await access.AddAssignedRole(TestAssignedRoles.DevopsUserDomainConsumer); // devops user in consumer role for a specific domain - // check user can consume a cert for a given domain + // Validate user can consume a cert for a given domain var isAuthorised = await access.IsAuthorised(contextUserId, "devops_user_01", StandardRoles.CertificateConsumer.Id, ResourceTypes.Domain, "certificate_download", "www.example.com"); Assert.IsTrue(isAuthorised, "User should be a cert consumer for this domain"); - // check user can't consume a cert for a subdomain they haven't been granted + // Validate user can't consume a cert for a subdomain they haven't been granted isAuthorised = await access.IsAuthorised(contextUserId, "devops_user_01", StandardRoles.CertificateConsumer.Id, ResourceTypes.Domain, "certificate_download", "secure.example.com"); Assert.IsFalse(isAuthorised, "User should not be a cert consumer for this domain"); + } + + [TestMethod] + public async Task TestAccessControlWildcardDomainAuth() + { + // Add test devops user security principle + _ = await access.AddSecurityPrinciple(contextUserId, TestSecurityPrinciples.DevopsUser(), bypassIntegrityCheck: true); + + // Setup security principle actions + await access.AddAction(Policies.GetStandardResourceActions().Find(r => r.Id == "certificate_download")); + + // Setup policy with actions and add policy to store + var policy = Policies.GetStandardPolicies().Find(p => p.Id == "certificate_consumer"); + _ = await access.AddResourcePolicy(contextUserId, policy, bypassIntegrityCheck: true); + + // Setup and add roles and policy assignments to store + var role = (await access.GetSystemRoles()).Find(r => r.Id == StandardRoles.CertificateConsumer.Id); + await access.AddRole(role); + + // Assign security principles to roles and add roles and policy assignments to store + await access.AddAssignedRole(TestAssignedRoles.DevopsUserWildcardDomainConsumer); // devops user in consumer role for a wildcard domain - // check user can consume any subdomain via a granted wildcard - isAuthorised = await access.IsAuthorised(contextUserId, "devops_user_01", StandardRoles.CertificateConsumer.Id, ResourceTypes.Domain, "certificate_download", "random.microsoft.com"); + // Validate user can consume any subdomain via a granted wildcard + var isAuthorised = await access.IsAuthorised(contextUserId, "devops_user_01", StandardRoles.CertificateConsumer.Id, ResourceTypes.Domain, "certificate_download", "random.microsoft.com"); Assert.IsTrue(isAuthorised, "User should be a cert consumer for this subdomain via wildcard"); - // check user can't consume a random wildcard + // Validate user can't consume a random wildcard isAuthorised = await access.IsAuthorised(contextUserId, "devops_user_01", StandardRoles.CertificateConsumer.Id, ResourceTypes.Domain, "certificate_download", "* lkjhasdf98862364"); Assert.IsFalse(isAuthorised, "User should not be a cert consumer for random wildcard"); - // check user can't consume a random wildcard + // Validate user can't consume a random wildcard isAuthorised = await access.IsAuthorised(contextUserId, "devops_user_01", StandardRoles.CertificateConsumer.Id, ResourceTypes.Domain, "certificate_download", "lkjhasdf98862364.*.microsoft.com"); Assert.IsFalse(isAuthorised, "User should not be a cert consumer for random wildcard"); + } + + [TestMethod] + public async Task TestAccessControlRandomUserAuth() + { + // Add test devops user security principle + _ = await access.AddSecurityPrinciple(contextUserId, TestSecurityPrinciples.DevopsUser(), bypassIntegrityCheck: true); + + // Setup security principle actions + await access.AddAction(Policies.GetStandardResourceActions().Find(r => r.Id == "certificate_download")); + + // Setup policy with actions and add policy to store + var policy = Policies.GetStandardPolicies().Find(p => p.Id == "certificate_consumer"); + _ = await access.AddResourcePolicy(contextUserId, policy, bypassIntegrityCheck: true); + + // Setup and add roles and policy assignments to store + var role = (await access.GetSystemRoles()).Find(r => r.Id == StandardRoles.CertificateConsumer.Id); + await access.AddRole(role); + + // Assign security principles to roles and add roles and policy assignments to store + await access.AddAssignedRole(TestAssignedRoles.DevopsUserWildcardDomainConsumer); // devops user in consumer role for a wildcard domain - // random user should not be authorised - isAuthorised = await access.IsAuthorised(contextUserId, "randomuser", StandardRoles.CertificateConsumer.Id, ResourceTypes.Domain, "certificate_download", "random.microsoft.com"); + // Validate that random user should not be authorised + var isAuthorised = await access.IsAuthorised(contextUserId, "randomuser", StandardRoles.CertificateConsumer.Id, ResourceTypes.Domain, "certificate_download", "random.microsoft.com"); Assert.IsFalse(isAuthorised, "Unknown user should not be a cert consumer for this subdomain via wildcard"); } } diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/CAFailoverTests.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/CAFailoverTests.cs index 25a0cf707..da80b9e06 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/CAFailoverTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/CAFailoverTests.cs @@ -1,9 +1,11 @@ -using System; +using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; using Certify.Management; using Certify.Models; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Newtonsoft.Json; namespace Certify.Core.Tests.Unit { @@ -12,6 +14,14 @@ public class CAFailoverTests { private const string DEFAULTCA = "letscertify"; + // TODO: This requires a valid test CA auth token to run + //private Dictionary ConfigSettings = new Dictionary(); + + //public CAFailoverTests() + //{ + // ConfigSettings = JsonConvert.DeserializeObject>(System.IO.File.ReadAllText("C:\\temp\\Certify\\TestConfigSettings.json")); + //} + private List GetTestCAs() { var caList = new List { @@ -195,7 +205,7 @@ public void TestBasicNoFallbacks() var selectedAccount = RenewalManager.SelectCAWithFailover(caList, accounts.FindAll(a => a.IsStagingAccount == false), managedCertificate, defaultCAAccount); // assert result - Assert.IsTrue(selectedAccount.CertificateAuthorityId == DEFAULTCA, $"Default CA should be selected: returned CA {selectedAccount.CertificateAuthorityId}"); + Assert.IsTrue(selectedAccount.CertificateAuthorityId == DEFAULTCA, "Default CA should be selected"); Assert.IsFalse(selectedAccount.IsFailoverSelection, "Account should not be marked as a failover choice"); } @@ -203,7 +213,7 @@ public void TestBasicNoFallbacks() public void TestBasicNextFallbackNull() { // setup - var accounts = GetTestAccounts().FindAll(a => a.ID != "letsreluctantlyfallback_ABC234_staging"); + var accounts = GetTestAccounts().FindAll(a => a.ID != "letsreluctantlyfallback_ABC234_staging"); var caList = GetTestCAs(); var managedCertificate = GetBasicManagedCertificate(RequestState.Error, 3, lastCA: "letsfallback"); @@ -211,19 +221,13 @@ public void TestBasicNextFallbackNull() // perform check var defaultCAAccount = accounts.FirstOrDefault(a => a.CertificateAuthorityId == DEFAULTCA && a.IsStagingAccount == managedCertificate.UseStagingMode); - accounts.Add(new AccountDetails - { - ID = "letsfallback_ABC234_staging_isfailover", - IsStagingAccount = true, - IsFailoverSelection = true, - CertificateAuthorityId = "letsfallback", - Title = "A fallback account with is failover" - }); + accounts.Add(new AccountDetails { ID = "letsfallback_ABC234_staging_isfailover", IsStagingAccount = true, IsFailoverSelection = true, + CertificateAuthorityId = "letsfallback", Title = "A fallback account with is failover" }); var selectedAccount = RenewalManager.SelectCAWithFailover(caList, accounts, managedCertificate, defaultCAAccount); // assert result - Assert.IsTrue(selectedAccount.CertificateAuthorityId == DEFAULTCA, $"Default CA should be selected: returned CA {selectedAccount.CertificateAuthorityId}"); + Assert.IsTrue(selectedAccount.CertificateAuthorityId == DEFAULTCA, "Default CA should be selected"); Assert.IsFalse(selectedAccount.IsFailoverSelection, "Account should not be marked as a failover choice"); } @@ -234,7 +238,7 @@ public void TestBasicFailoverOccursWildcardDomainCA() var accounts = GetTestAccounts(); var caList = GetTestCAs(); - var managedCertificate = GetBasicManagedCertificate(RequestState.Error, 3, lastCA: DEFAULTCA, + var managedCertificate = GetBasicManagedCertificate(RequestState.Error, 3, lastCA: DEFAULTCA, new CertRequestConfig { SubjectAlternativeNames = new List { "test.com", "anothertest.com", "www.test.com", "*.wildtest.com" }.ToArray() }); // perform check @@ -315,9 +319,7 @@ public void TestBasicFailoverOccursOptionalLifetimeDays() var caList = GetTestCAs(); var managedCertificate = GetBasicManagedCertificate(RequestState.Error, 3, lastCA: DEFAULTCA, - new CertRequestConfig - { - SubjectAlternativeNames = new List { "test.com", "anothertest.com", "www.test.com", "*.wildtest.com" }.ToArray(), + new CertRequestConfig { SubjectAlternativeNames = new List { "test.com", "anothertest.com", "www.test.com", "*.wildtest.com" }.ToArray(), PreferredExpiryDays = 7, }); @@ -331,4 +333,33 @@ public void TestBasicFailoverOccursOptionalLifetimeDays() Assert.IsTrue(selectedAccount.IsFailoverSelection, "Account should be marked as a failover choice"); } } + + // TODO: This test requires a valid test CA auth token to run + //[TestMethod, Description("Failover to an alternate CA when an item has repeatedly failed, with TnAuthList CA")] + //public void TestBasicFailoverOccursTnAuthList() + //{ + // // setup + // var accounts = GetTestAccounts(); + // var caList = GetTestCAs(); + + // var managedCertificate = GetBasicManagedCertificate(RequestState.Error, 3, lastCA: DEFAULTCA, + // new CertRequestConfig { + // //SubjectAlternativeNames = new List { "test.com", "anothertest.com", "www.test.com" }.ToArray(), + // AuthorityTokens = new ObservableCollection { + // new TkAuthToken{ + // Token = ConfigSettings["TestAuthToken"], + // Crl =ConfigSettings["TestAuthTokenCRL"] + // } + // } + // }); + + // // perform check + // var defaultCAAccount = accounts.FirstOrDefault(a => a.CertificateAuthorityId == DEFAULTCA && a.IsStagingAccount == managedCertificate.UseStagingMode); + + // var selectedAccount = RenewalManager.SelectCAWithFailover(caList, accounts, managedCertificate, defaultCAAccount); + + // // assert result + // Assert.IsTrue(selectedAccount.CertificateAuthorityId == "letsreluctantlyfallback", "Fallback CA should be selected"); + // Assert.IsTrue(selectedAccount.IsFailoverSelection, "Account should be marked as a failover choice"); + //} } From 40a0facecab14e3dc8b40def9f41edf94424fbde Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Wed, 25 Oct 2023 13:36:16 +0800 Subject: [PATCH 067/328] Add regular GC sweep --- .../Management/CertifyManager/CertifyManager.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.cs index c9c0e05be..004e8ec78 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.cs @@ -223,6 +223,15 @@ private async void _dailyTimer_Elapsed(object sender, System.Timers.ElapsedEvent private async void _hourlyTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { await PerformCertificateMaintenanceTasks(); + + try + { + GC.Collect(GC.MaxGeneration, GCCollectionMode.Default); + } + catch + { + // failed to perform garbage collection, ignore. + } } private async void _frequentTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) From 547083ae9ea26eda3dd449a1af74d3a99af5cdbb Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Wed, 25 Oct 2023 17:27:44 +0800 Subject: [PATCH 068/328] Package updates --- src/Certify.Client/Certify.Client.csproj | 2 +- src/Certify.Core/Certify.Core.csproj | 2 +- .../DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj | 2 +- .../DNS/Azure/Plugin.DNS.Azure.csproj | 2 +- .../Certify.Server.Api.Public.Tests.csproj | 2 +- .../Certify.Server.Api.Public.csproj | 2 +- .../Certify.Server.Core/Certify.Server.Core.csproj | 2 +- .../Certify.Service.Worker.csproj | 2 +- .../Certify.Service.Worker/Dockerfile | 4 ++-- src/Certify.Service/Certify.Service.csproj | 12 ++++++------ .../Certify.Core.Tests.Integration.csproj | 2 +- .../Certify.Core.Tests.Unit.csproj | 2 +- .../Certify.Service.Tests.Integration.csproj | 2 +- 13 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/Certify.Client/Certify.Client.csproj b/src/Certify.Client/Certify.Client.csproj index f874fdbfc..e27a06f95 100644 --- a/src/Certify.Client/Certify.Client.csproj +++ b/src/Certify.Client/Certify.Client.csproj @@ -20,7 +20,7 @@ - + diff --git a/src/Certify.Core/Certify.Core.csproj b/src/Certify.Core/Certify.Core.csproj index 00d97a462..61b680a0f 100644 --- a/src/Certify.Core/Certify.Core.csproj +++ b/src/Certify.Core/Certify.Core.csproj @@ -83,7 +83,7 @@ 1701;1702;CA1068 - + diff --git a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj index 7a5ef76b5..f6fdb983e 100644 --- a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj +++ b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj b/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj index 085c1960f..b178c09dd 100644 --- a/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj +++ b/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj b/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj index be4eca0ee..758dd3b80 100644 --- a/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj +++ b/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj @@ -25,7 +25,7 @@ - + diff --git a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj index 504d52806..c9e721ceb 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj +++ b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj @@ -21,7 +21,7 @@ - + diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj index d560be7b9..15b0e512d 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Certify.Service.Worker.csproj b/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Certify.Service.Worker.csproj index 70cd9e1bf..d42e45ffc 100644 --- a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Certify.Service.Worker.csproj +++ b/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Certify.Service.Worker.csproj @@ -10,7 +10,7 @@ true - + diff --git a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Dockerfile b/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Dockerfile index c4714dbf8..53e0dff18 100644 --- a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Dockerfile +++ b/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Dockerfile @@ -1,10 +1,10 @@ #See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging. -FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base WORKDIR /app EXPOSE 80 EXPOSE 443 -FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build WORKDIR /src COPY ["Certify.Service.Worker/Certify.Service.Worker.csproj", "Certify.Service.Worker/"] RUN dotnet restore "Certify.Service.Worker/Certify.Service.Worker.csproj" diff --git a/src/Certify.Service/Certify.Service.csproj b/src/Certify.Service/Certify.Service.csproj index b587d2ff3..e8dabde64 100644 --- a/src/Certify.Service/Certify.Service.csproj +++ b/src/Certify.Service/Certify.Service.csproj @@ -49,12 +49,12 @@ - - - - - - + + + + + + diff --git a/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj b/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj index e62f834d5..d5f534591 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj @@ -96,7 +96,7 @@ - + diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj index 0c4c2be7e..a178f4522 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj @@ -136,7 +136,7 @@ - + diff --git a/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj b/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj index 0a192b307..31455ada4 100644 --- a/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj @@ -80,7 +80,7 @@ - + From d2443e78b98409e3b40ee06cfbbab8967852f649 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 26 Oct 2023 16:54:04 +0800 Subject: [PATCH 069/328] Implement challenge cleanup action by repurposing challenge test cleanup --- .../Management/Challenges/ChallengeResponseService.cs | 8 ++++---- .../Controls/ManagedCertificate/AdvancedOptions.xaml.cs | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Certify.Core/Management/Challenges/ChallengeResponseService.cs b/src/Certify.Core/Management/Challenges/ChallengeResponseService.cs index 4ac46c835..f7adadcab 100644 --- a/src/Certify.Core/Management/Challenges/ChallengeResponseService.cs +++ b/src/Certify.Core/Management/Challenges/ChallengeResponseService.cs @@ -621,7 +621,7 @@ private Func PrepareChallengeResponse_TlsSni01(ILog log, ITargetWebServer private DnsChallengeHelper _dnsHelper = null; - private async Task PerformChallengeResponse_Dns01(ILog log, CertIdentifierItem domain, ManagedCertificate managedCertificate, PendingAuthorization pendingAuth, bool isTestMode, ICredentialsManager credentialsManager) + private async Task PerformChallengeResponse_Dns01(ILog log, CertIdentifierItem domain, ManagedCertificate managedCertificate, PendingAuthorization pendingAuth, bool isTestMode, bool isCleanupOnly, ICredentialsManager credentialsManager) { var dnsChallenge = pendingAuth.Challenges.FirstOrDefault(c => c.ChallengeType == SupportedChallengeTypes.CHALLENGE_TYPE_DNS); @@ -639,12 +639,12 @@ private async Task PerformChallengeResponse_Dns01(ILog } // create DNS records (manually or via automation) - if (_dnsHelper == null) { + if (_dnsHelper == null) + { _dnsHelper = new DnsChallengeHelper(credentialsManager); } - var dnsResult = await _dnsHelper.CompleteDNSChallenge(log, managedCertificate, domain, dnsChallenge.Key, dnsChallenge.Value, isTestMode); - + DnsChallengeHelperResult dnsResult; if (!isCleanupOnly) { dnsResult = await _dnsHelper.CompleteDNSChallenge(log, managedCertificate, domain, dnsChallenge.Key, dnsChallenge.Value, isTestMode); diff --git a/src/Certify.UI.Shared/Controls/ManagedCertificate/AdvancedOptions.xaml.cs b/src/Certify.UI.Shared/Controls/ManagedCertificate/AdvancedOptions.xaml.cs index fa3235e86..3ab8c4fc4 100644 --- a/src/Certify.UI.Shared/Controls/ManagedCertificate/AdvancedOptions.xaml.cs +++ b/src/Certify.UI.Shared/Controls/ManagedCertificate/AdvancedOptions.xaml.cs @@ -6,6 +6,7 @@ using System.Windows.Controls; using Certify.Locales; using Certify.Management; +using Certify.UI.ViewModel; using Microsoft.Win32; namespace Certify.UI.Controls.ManagedCertificate From dfa6f3408fd68d05129f000711754025bdf168d6 Mon Sep 17 00:00:00 2001 From: Justin Nelson Date: Thu, 26 Oct 2023 00:02:38 -0700 Subject: [PATCH 070/328] Updates and expansions to various tests --- .../ServerManagers/IISManagerTests.cs | 7 + .../BindingMatchTests.cs | 317 +++++++++--------- .../Certify.Core.Tests.Unit.csproj | 6 + .../ChallengeConfigMatchTests.cs | 11 +- .../DomainZoneMatchTests.cs | 7 +- .../GetDnsProviderTests.cs | 140 ++++++++ 6 files changed, 328 insertions(+), 160 deletions(-) create mode 100644 src/Certify.Tests/Certify.Core.Tests.Unit/GetDnsProviderTests.cs diff --git a/src/Certify.Tests/Certify.Core.Tests.Integration/ServerManagers/IISManagerTests.cs b/src/Certify.Tests/Certify.Core.Tests.Integration/ServerManagers/IISManagerTests.cs index 0e7679732..6dc42066b 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Integration/ServerManagers/IISManagerTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Integration/ServerManagers/IISManagerTests.cs @@ -69,6 +69,13 @@ public async Task TestIISVersionCheck() Assert.IsTrue(version.Major >= 7); } + [TestMethod] + public async Task TestIISIsAvailable() + { + var isAvailable = await iisManager.IsAvailable(); + Assert.IsTrue(isAvailable); + } + [TestMethod] public async Task TestIISSiteRunning() { diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/BindingMatchTests.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/BindingMatchTests.cs index eb650ded1..8b45ca4cc 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/BindingMatchTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/BindingMatchTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; @@ -9,6 +9,7 @@ using Certify.Management; using Certify.Models; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Certify.Management; namespace Certify.Core.Tests.Unit { @@ -360,19 +361,19 @@ public async Task MixedIPBindingChecks() Assert.IsTrue(results.Any()); Assert.AreEqual(3, results.Count()); Assert.IsFalse(results[0].HasError, "This call to StoreAndDeploy() should have no errors storing certificate"); - Assert.AreEqual("CertificateStorage", results[0].Category); - Assert.IsTrue(results[0].Description.Contains("Certificate will be stored in the computer certificate store"), $"Unexpected description: '{results[0].Description}'"); - Assert.AreEqual("Certificate Storage", results[0].Title); + Assert.AreEqual(results[0].Category, "CertificateStorage"); + Assert.IsTrue(results[0].Description.Contains("Certificate will be stored in the computer certificate store")); + Assert.AreEqual(results[0].Title, "Certificate Storage"); Assert.IsTrue(results[1].HasError, "This call to StoreAndDeploy() should have an error adding binding while deploying certificate in preview"); - Assert.AreEqual("Deployment.AddBinding", results[1].Category); - Assert.IsTrue(results[1].Description.Contains("Add https binding | | ***:443:test.com SNI** Failed to add/update binding. [IIS Site Id could not be determined]"), $"Unexpected description: '{results[1].Description}'"); - Assert.AreEqual("Install Certificate For Binding", results[1].Title); + Assert.AreEqual(results[1].Category, "Deployment.AddBinding"); + Assert.IsTrue(results[1].Description.Contains("Add https binding | | ***:443:test.com SNI** Failed to add/update binding. [IIS Site Id could not be determined]")); + Assert.AreEqual(results[1].Title, "Install Certificate For Binding"); Assert.IsTrue(results[2].HasError, "This call to StoreAndDeploy() should have an error adding binding while deploying certificate in preview"); - Assert.AreEqual("Deployment.AddBinding", results[2].Category); - Assert.IsTrue(results[2].Description.Contains("Add https binding | | ***:443:test.com SNI** Failed to add/update binding. [IIS Site Id could not be determined]"), $"Unexpected description: '{results[2].Description}'"); - Assert.AreEqual("Install Certificate For Binding", results[2].Title); + Assert.AreEqual(results[2].Category, "Deployment.AddBinding"); + Assert.IsTrue(results[2].Description.Contains("Add https binding | | ***:443:test.com SNI** Failed to add/update binding. [IIS Site Id could not be determined]")); + Assert.AreEqual(results[2].Title, "Install Certificate For Binding"); } [TestMethod, Description("Test if mixed ipv4+ipv6 bindings are handled when not in preview")] @@ -385,6 +386,7 @@ public async Task MixedIPBindingChecksNoPreview() new BindingInfo{ Host="www.test.com", IP="[fe80::3c4e:11b7:fe4f:c601%31]", Port=80, Protocol="http" } }; var deployment = new BindingDeploymentManager(); + var dummyCertPath = Environment.CurrentDirectory + "\\Assets\\dummycert.pfx"; var testManagedCert = new ManagedCertificate { Id = Guid.NewGuid().ToString(), @@ -405,30 +407,30 @@ public async Task MixedIPBindingChecksNoPreview() } }, ItemType = ManagedCertificateType.SSL_ACME, - CertificatePath = _dummyCertPath + CertificatePath = dummyCertPath }; var mockTarget = new MockBindingDeploymentTarget(); mockTarget.AllBindings = bindings; - var results = await deployment.StoreAndDeploy(mockTarget, testManagedCert, _dummyCertPath, pfxPwd: "", false, Certify.Management.CertificateManager.DEFAULT_STORE_NAME); + var results = await deployment.StoreAndDeploy(mockTarget, testManagedCert, dummyCertPath, pfxPwd: "", false, Certify.Management.CertificateManager.DEFAULT_STORE_NAME); Assert.IsTrue(results.Any()); Assert.AreEqual(3, results.Count()); Assert.IsFalse(results[0].HasError, "This call to StoreAndDeploy() should have no errors storing certificate"); - Assert.AreEqual("CertificateStorage", results[0].Category); - Assert.IsTrue(results[0].Description.Contains("Certificate stored OK"), $"Unexpected description: '{results[0].Description}'"); - Assert.AreEqual("Certificate Stored", results[0].Title); + Assert.AreEqual(results[0].Category, "CertificateStorage"); + Assert.IsTrue(results[0].Description.Contains("Certificate stored OK")); + Assert.AreEqual(results[0].Title, "Certificate Stored"); Assert.IsFalse(results[1].HasError, "This call to StoreAndDeploy() should not have an error adding binding while deploying certificate"); - Assert.AreEqual("Deployment.AddBinding", results[1].Category); - Assert.IsTrue(results[1].Description.Contains("Add https binding | | ***:443:test.com SNI**"), $"Unexpected description: '{results[1].Description}'"); - Assert.AreEqual("Install Certificate For Binding", results[1].Title); + Assert.AreEqual(results[1].Category, "Deployment.AddBinding"); + Assert.IsTrue(results[1].Description.Contains("Add https binding | | ***:443:test.com SNI**")); + Assert.AreEqual(results[1].Title, "Install Certificate For Binding"); Assert.IsFalse(results[2].HasError, "This call to StoreAndDeploy() should not have an error adding binding while deploying certificate"); - Assert.AreEqual("Deployment.UpdateBinding", results[2].Category); - Assert.IsTrue(results[2].Description.Contains("Update https binding | | **\\*:443:test.com SNI**"), $"Unexpected description: '{results[2].Description}'"); - Assert.AreEqual("Install Certificate For Binding", results[2].Title); + Assert.AreEqual(results[2].Category, "Deployment.UpdateBinding"); + Assert.IsTrue(results[2].Description.Contains("Update https binding | | **\\*:443:test.com SNI**")); + Assert.AreEqual(results[2].Title, "Install Certificate For Binding"); } [TestMethod, Description("Test if mixed ipv4+ipv6 bindings are handled with blank certStoreName")] @@ -441,6 +443,7 @@ public async Task MixedIPBindingChecksBlankCertStoreName() new BindingInfo{ Host="www.test.com", IP="[fe80::3c4e:11b7:fe4f:c601%31]", Port=80, Protocol="http" } }; var deployment = new BindingDeploymentManager(); + var dummyCertPath = Environment.CurrentDirectory + "\\Assets\\dummycert.pfx"; var testManagedCert = new ManagedCertificate { Id = Guid.NewGuid().ToString(), @@ -461,30 +464,30 @@ public async Task MixedIPBindingChecksBlankCertStoreName() } }, ItemType = ManagedCertificateType.SSL_ACME, - CertificatePath = _dummyCertPath + CertificatePath = dummyCertPath }; var mockTarget = new MockBindingDeploymentTarget(); mockTarget.AllBindings = bindings; - var results = await deployment.StoreAndDeploy(mockTarget, testManagedCert, _dummyCertPath, pfxPwd: "", false, ""); + var results = await deployment.StoreAndDeploy(mockTarget, testManagedCert, dummyCertPath, pfxPwd: "", false, ""); Assert.IsTrue(results.Any()); Assert.AreEqual(3, results.Count()); Assert.IsFalse(results[0].HasError, "This call to StoreAndDeploy() should have no errors storing certificate"); - Assert.AreEqual("CertificateStorage", results[0].Category); - Assert.IsTrue(results[0].Description.Contains("Certificate stored OK"), $"Unexpected description: '{results[0].Description}'"); - Assert.AreEqual("Certificate Stored", results[0].Title); + Assert.AreEqual(results[0].Category, "CertificateStorage"); + Assert.IsTrue(results[0].Description.Contains("Certificate stored OK")); + Assert.AreEqual(results[0].Title, "Certificate Stored"); Assert.IsFalse(results[1].HasError, "This call to StoreAndDeploy() should not have an error adding binding while deploying certificate"); - Assert.AreEqual("Deployment.AddBinding", results[1].Category); - Assert.IsTrue(results[1].Description.Contains("Add https binding | | ***:443:test.com SNI**"), $"Unexpected description: '{results[1].Description}'"); - Assert.AreEqual("Install Certificate For Binding", results[1].Title); + Assert.AreEqual(results[1].Category, "Deployment.AddBinding"); + Assert.IsTrue(results[1].Description.Contains("Add https binding | | ***:443:test.com SNI**")); + Assert.AreEqual(results[1].Title, "Install Certificate For Binding"); Assert.IsFalse(results[2].HasError, "This call to StoreAndDeploy() should not have an error adding binding while deploying certificate"); - Assert.AreEqual("Deployment.UpdateBinding", results[2].Category); - Assert.IsTrue(results[2].Description.Contains("Update https binding | | **\\*:443:test.com SNI**"), $"Unexpected description: '{results[2].Description}'"); - Assert.AreEqual("Install Certificate For Binding", results[2].Title); + Assert.AreEqual(results[2].Category, "Deployment.UpdateBinding"); + Assert.IsTrue(results[2].Description.Contains("Update https binding | | **\\*:443:test.com SNI**")); + Assert.AreEqual(results[2].Title, "Install Certificate For Binding"); } [TestMethod, Description("Test if mixed ipv4+ipv6 bindings are handled when given a bad pfx file path")] @@ -497,6 +500,7 @@ public async Task MixedIPBindingChecksBadPfxPath() new BindingInfo{ Host="www.test.com", IP="[fe80::3c4e:11b7:fe4f:c601%31]", Port=80, Protocol="http" } }; var deployment = new BindingDeploymentManager(); + var dummyCertPath = Environment.CurrentDirectory + "\\Asset\\dummycert.pfx"; var testManagedCert = new ManagedCertificate { Id = Guid.NewGuid().ToString(), @@ -517,13 +521,13 @@ public async Task MixedIPBindingChecksBadPfxPath() } }, ItemType = ManagedCertificateType.SSL_ACME, - CertificatePath = _dummyCertPath + CertificatePath = dummyCertPath }; var mockTarget = new MockBindingDeploymentTarget(); mockTarget.AllBindings = bindings; - await Assert.ThrowsExceptionAsync(async () => await deployment.StoreAndDeploy(mockTarget, testManagedCert, _dummyCertPath.Replace("Assets", "Asset"), pfxPwd: "", false, Certify.Management.CertificateManager.DEFAULT_STORE_NAME)); + await Assert.ThrowsExceptionAsync(async() => await deployment.StoreAndDeploy(mockTarget, testManagedCert, dummyCertPath, pfxPwd: "", false, Certify.Management.CertificateManager.DEFAULT_STORE_NAME)); } [TestMethod, Description("Test if mixed ipv4+ipv6 bindings are handled when given a bad pfx file")] @@ -536,7 +540,7 @@ public async Task MixedIPBindingChecksBadPfxFile() new BindingInfo{ Host="www.test.com", IP="[fe80::3c4e:11b7:fe4f:c601%31]", Port=80, Protocol="http" } }; var deployment = new BindingDeploymentManager(); - var badCertPath = Path.Combine(Environment.CurrentDirectory, "Assets", "badcert.pfx"); + var dummyCertPath = Environment.CurrentDirectory + "\\Assets\\badcert.pfx"; var testManagedCert = new ManagedCertificate { Id = Guid.NewGuid().ToString(), @@ -557,13 +561,13 @@ public async Task MixedIPBindingChecksBadPfxFile() } }, ItemType = ManagedCertificateType.SSL_ACME, - CertificatePath = _dummyCertPath + CertificatePath = dummyCertPath }; var mockTarget = new MockBindingDeploymentTarget(); mockTarget.AllBindings = bindings; - await Assert.ThrowsExceptionAsync(async () => await deployment.StoreAndDeploy(mockTarget, testManagedCert, badCertPath, pfxPwd: "", false, Certify.Management.CertificateManager.DEFAULT_STORE_NAME)); + await Assert.ThrowsExceptionAsync(async () => await deployment.StoreAndDeploy(mockTarget, testManagedCert, dummyCertPath, pfxPwd: "", false, Certify.Management.CertificateManager.DEFAULT_STORE_NAME)); } [TestMethod, Description("Test if mixed ipv4+ipv6 bindings are handled when given a bad pfx password")] @@ -576,6 +580,7 @@ public async Task MixedIPBindingChecksBadPfxPassword() new BindingInfo{ Host="www.test.com", IP="[fe80::3c4e:11b7:fe4f:c601%31]", Port=80, Protocol="http" } }; var deployment = new BindingDeploymentManager(); + var dummyCertPath = Environment.CurrentDirectory + "\\Assets\\dummycert.pfx"; var testManagedCert = new ManagedCertificate { Id = Guid.NewGuid().ToString(), @@ -596,30 +601,30 @@ public async Task MixedIPBindingChecksBadPfxPassword() } }, ItemType = ManagedCertificateType.SSL_ACME, - CertificatePath = _dummyCertPath + CertificatePath = dummyCertPath }; var mockTarget = new MockBindingDeploymentTarget(); mockTarget.AllBindings = bindings; - var results = await deployment.StoreAndDeploy(mockTarget, testManagedCert, _dummyCertPath, pfxPwd: "badpass", false, Certify.Management.CertificateManager.DEFAULT_STORE_NAME); + var results = await deployment.StoreAndDeploy(mockTarget, testManagedCert, dummyCertPath, pfxPwd: "badpass", false, Certify.Management.CertificateManager.DEFAULT_STORE_NAME); Assert.IsTrue(results.Any()); Assert.AreEqual(3, results.Count()); Assert.IsFalse(results[0].HasError, "This call to StoreAndDeploy() should have no errors storing certificate"); - Assert.AreEqual("CertificateStorage", results[0].Category); - Assert.IsTrue(results[0].Description.Contains("Certificate stored OK"), $"Unexpected description: '{results[0].Description}'"); - Assert.AreEqual("Certificate Stored", results[0].Title); + Assert.AreEqual(results[0].Category, "CertificateStorage"); + Assert.IsTrue(results[0].Description.Contains("Certificate stored OK")); + Assert.AreEqual(results[0].Title, "Certificate Stored"); Assert.IsFalse(results[1].HasError, "This call to StoreAndDeploy() should not have an error adding binding while deploying certificate"); - Assert.AreEqual("Deployment.AddBinding", results[1].Category); - Assert.IsTrue(results[1].Description.Contains("Add https binding | | ***:443:test.com SNI**"), $"Unexpected description: '{results[1].Description}'"); - Assert.AreEqual("Install Certificate For Binding", results[1].Title); + Assert.AreEqual(results[1].Category, "Deployment.AddBinding"); + Assert.IsTrue(results[1].Description.Contains("Add https binding | | ***:443:test.com SNI**")); + Assert.AreEqual(results[1].Title, "Install Certificate For Binding"); Assert.IsFalse(results[2].HasError, "This call to StoreAndDeploy() should not have an error adding binding while deploying certificate"); - Assert.AreEqual("Deployment.UpdateBinding", results[2].Category); - Assert.IsTrue(results[2].Description.Contains("Update https binding | | **\\*:443:test.com SNI**"), $"Unexpected description: '{results[2].Description}'"); - Assert.AreEqual("Install Certificate For Binding", results[2].Title); + Assert.AreEqual(results[2].Category, "Deployment.UpdateBinding"); + Assert.IsTrue(results[2].Description.Contains("Update https binding | | **\\*:443:test.com SNI**")); + Assert.AreEqual(results[2].Title, "Install Certificate For Binding"); } [TestMethod, Description("Test if mixed ipv4+ipv6 bindings are handled when given a bad cert store name")] @@ -632,6 +637,7 @@ public async Task MixedIPBindingChecksBadCertStoreName() new BindingInfo{ Host="www.test.com", IP="[fe80::3c4e:11b7:fe4f:c601%31]", Port=80, Protocol="http" } }; var deployment = new BindingDeploymentManager(); + var dummyCertPath = Environment.CurrentDirectory + "\\Assets\\dummycert.pfx"; var testManagedCert = new ManagedCertificate { Id = Guid.NewGuid().ToString(), @@ -652,27 +658,19 @@ public async Task MixedIPBindingChecksBadCertStoreName() } }, ItemType = ManagedCertificateType.SSL_ACME, - CertificatePath = _dummyCertPath + CertificatePath = dummyCertPath }; var mockTarget = new MockBindingDeploymentTarget(); mockTarget.AllBindings = bindings; - var results = await deployment.StoreAndDeploy(mockTarget, testManagedCert, _dummyCertPath, pfxPwd: "", false, "BadCertStoreName"); - - Assert.AreEqual(1, results.Count); - Assert.IsTrue(results[0].HasError); - Assert.AreEqual("CertificateStorage", results[0].Category); - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - Assert.IsTrue(results[0].Description.Contains("Error storing certificate. The system cannot find the file specified."), $"Unexpected description: '{results[0].Description}'"); - } - else - { - Assert.IsTrue(results[0].Description.Contains("Error storing certificate. The specified X509 certificate store does not exist."), $"Unexpected description: '{results[0].Description}'"); - } + var results = await deployment.StoreAndDeploy(mockTarget, testManagedCert, dummyCertPath, pfxPwd: "", false, "BadCertStoreName"); - Assert.AreEqual("Certificate Storage Failed", results[0].Title); + Assert.AreEqual(results.Count, 1); + Assert.IsTrue(results[0].HasError); + Assert.AreEqual(results[0].Category, "CertificateStorage"); + Assert.IsTrue(results[0].Description.Contains("Error storing certificate. The system cannot find the file specified.")); + Assert.AreEqual(results[0].Title, "Certificate Storage Failed"); } [TestMethod, Description("Test if mixed ipv4+ipv6 bindings are handled when DeploymentBindingOption = DeploymentBindingOption.UpdateOnly")] @@ -686,6 +684,7 @@ public async Task MixedIPBindingChecksRequestConfigUpdateOnly() }; var deployment = new BindingDeploymentManager(); + var dummyCertPath = Environment.CurrentDirectory + "\\Assets\\dummycert.pfx"; var testManagedCert = new ManagedCertificate { Id = Guid.NewGuid().ToString(), @@ -707,27 +706,27 @@ public async Task MixedIPBindingChecksRequestConfigUpdateOnly() } }, ItemType = ManagedCertificateType.SSL_ACME, - CertificatePath = _dummyCertPath + CertificatePath = dummyCertPath }; var mockTarget = new MockBindingDeploymentTarget(); mockTarget.AllBindings = bindings; - var results = await deployment.StoreAndDeploy(mockTarget, testManagedCert, _dummyCertPath, pfxPwd: "", false, Certify.Management.CertificateManager.DEFAULT_STORE_NAME); + var results = await deployment.StoreAndDeploy(mockTarget, testManagedCert, dummyCertPath, pfxPwd: "", false, Certify.Management.CertificateManager.DEFAULT_STORE_NAME); - Assert.AreEqual(1, results.Count); + Assert.AreEqual(results.Count, 1); Assert.IsFalse(results[0].HasError); - Assert.AreEqual("CertificateStorage", results[0].Category); - Assert.IsTrue(results[0].Description.Contains("Certificate stored OK"), $"Unexpected description: '{results[0].Description}'"); - Assert.AreEqual("Certificate Stored", results[0].Title); + Assert.AreEqual(results[0].Category, "CertificateStorage"); + Assert.IsTrue(results[0].Description.Contains("Certificate stored OK")); + Assert.AreEqual(results[0].Title, "Certificate Stored"); } [TestMethod, Description("Test if https IP bindings are handled")] public async Task HttpsIPBindingChecks() { var bindings = new List { - new BindingInfo{ Host="test.com", IP="127.0.0.1", Port=443, Protocol="https" }, - new BindingInfo{ Host="www.test.com", IP="127.0.0.1", Port=443, Protocol="https" }, + new BindingInfo{ Host="test.com", IP="127.0.0.1", Port=80, Protocol="https" }, + new BindingInfo{ Host="www.test.com", IP="127.0.0.1", Port=80, Protocol="https" }, }; var deployment = new BindingDeploymentManager(); var testManagedCert = new ManagedCertificate @@ -760,17 +759,17 @@ public async Task HttpsIPBindingChecks() Assert.IsTrue(results.Any()); Assert.AreEqual(2, results.Count()); Assert.IsFalse(results[0].HasError, "This call to StoreAndDeploy() should have no errors storing certificate"); - Assert.AreEqual("CertificateStorage", results[0].Category); - Assert.IsTrue(results[0].Description.Contains("Certificate will be stored in the computer certificate store"), $"Unexpected description: '{results[0].Description}'"); - Assert.AreEqual("Certificate Storage", results[0].Title); + Assert.AreEqual(results[0].Category, "CertificateStorage"); + Assert.IsTrue(results[0].Description.Contains("Certificate will be stored in the computer certificate store")); + Assert.AreEqual(results[0].Title, "Certificate Storage"); - // because the existing binding uses an IP address with non-SNI the resulting update should also use the IP address and no SNI. Assert.IsFalse(results[1].HasError, "This call to StoreAndDeploy() should not have an error adding binding while deploying certificate"); - Assert.AreEqual("Deployment.UpdateBinding", results[1].Category); - Assert.IsTrue(results[1].Description.Contains("Update https binding | | **127.0.0.1:443:test.com Non-SNI**"), $"Unexpected description: '{results[1].Description}'"); - Assert.AreEqual("Install Certificate For Binding", results[1].Title); + Assert.AreEqual(results[1].Category, "Deployment.UpdateBinding"); + Assert.IsTrue(results[1].Description.Contains("Update https binding | | **127.0.0.1:80:test.com Non-SNI**")); + Assert.AreEqual(results[1].Title, "Install Certificate For Binding"); } +#if NET462 [TestMethod, Description("Test if mixed ipv4+ipv6 bindings are handled when CertificateThumbprintHash is defined")] public async Task MixedIPBindingChecksCertificateThumbprintHash() { @@ -785,6 +784,7 @@ public async Task MixedIPBindingChecksCertificateThumbprintHash() }; var deployment = new BindingDeploymentManager(); + var dummyCertPath = Environment.CurrentDirectory + "\\Assets\\dummycert.pfx"; var testManagedCert = new ManagedCertificate { Id = Guid.NewGuid().ToString(), @@ -806,32 +806,30 @@ public async Task MixedIPBindingChecksCertificateThumbprintHash() }, ItemType = ManagedCertificateType.SSL_ACME, CertificateThumbprintHash = cert.Thumbprint, - CertificatePath = _dummyCertPath + CertificatePath = dummyCertPath }; var mockTarget = new MockBindingDeploymentTarget(); mockTarget.AllBindings = bindings; - var results = await deployment.StoreAndDeploy(mockTarget, testManagedCert, _dummyCertPath, pfxPwd: "", false, Certify.Management.CertificateManager.DEFAULT_STORE_NAME); + var results = await deployment.StoreAndDeploy(mockTarget, testManagedCert, dummyCertPath, pfxPwd: "", false, Certify.Management.CertificateManager.DEFAULT_STORE_NAME); Assert.IsTrue(results.Any()); Assert.AreEqual(3, results.Count()); Assert.IsFalse(results[0].HasError, "This call to StoreAndDeploy() should have no errors storing certificate"); - Assert.AreEqual("CertificateStorage", results[0].Category); - Assert.IsTrue(results[0].Description.Contains("Certificate stored OK"), $"Unexpected description: '{results[0].Description}'"); - Assert.AreEqual("Certificate Stored", results[0].Title); + Assert.AreEqual(results[0].Category, "CertificateStorage"); + Assert.IsTrue(results[0].Description.Contains("Certificate stored OK")); + Assert.AreEqual(results[0].Title, "Certificate Stored"); Assert.IsFalse(results[1].HasError, "This call to StoreAndDeploy() should not have an error adding binding while deploying certificate"); - Assert.AreEqual("Deployment.AddBinding", results[1].Category); - Assert.IsTrue(results[1].Description.Contains("Add https binding | | ***:443:test.com SNI**"), $"Unexpected description: '{results[1].Description}'"); - Assert.AreEqual("Install Certificate For Binding", results[1].Title); + Assert.AreEqual(results[1].Category, "Deployment.AddBinding"); + Assert.IsTrue(results[1].Description.Contains("Add https binding | | ***:443:test.com SNI**")); + Assert.AreEqual(results[1].Title, "Install Certificate For Binding"); Assert.IsFalse(results[2].HasError, "This call to StoreAndDeploy() should not have an error adding binding while deploying certificate"); - Assert.AreEqual("Deployment.UpdateBinding", results[2].Category); - Assert.IsTrue(results[2].Description.Contains("Update https binding | | **\\*:443:test.com SNI**"), $"Unexpected description: '{results[2].Description}'"); - Assert.AreEqual("Install Certificate For Binding", results[2].Title); - - CertificateManager.RemoveCertificate(cert, Certify.Management.CertificateManager.DEFAULT_STORE_NAME); + Assert.AreEqual(results[2].Category, "Deployment.UpdateBinding"); + Assert.IsTrue(results[2].Description.Contains("Update https binding | | **\\*:443:test.com SNI**")); + Assert.AreEqual(results[2].Title, "Install Certificate For Binding"); } [TestMethod, Description("Test if mixed ipv4+ipv6 bindings are handled when CertificatePreviousThumbprintHash is defined")] @@ -849,6 +847,7 @@ public async Task MixedIPBindingChecksCertificatePreviousThumbprintHash() }; var deployment = new BindingDeploymentManager(); + var dummyCertPath = Environment.CurrentDirectory + "\\Assets\\dummycert.pfx"; var testManagedCert = new ManagedCertificate { Id = Guid.NewGuid().ToString(), @@ -870,33 +869,32 @@ public async Task MixedIPBindingChecksCertificatePreviousThumbprintHash() }, ItemType = ManagedCertificateType.SSL_ACME, CertificatePreviousThumbprintHash = cert.Thumbprint, - CertificatePath = _dummyCertPath + CertificatePath = dummyCertPath }; var mockTarget = new MockBindingDeploymentTarget(); mockTarget.AllBindings = bindings; - var results = await deployment.StoreAndDeploy(mockTarget, testManagedCert, _dummyCertPath, pfxPwd: "", false, Certify.Management.CertificateManager.DEFAULT_STORE_NAME); + var results = await deployment.StoreAndDeploy(mockTarget, testManagedCert, dummyCertPath, pfxPwd: "", false, Certify.Management.CertificateManager.DEFAULT_STORE_NAME); Assert.IsTrue(results.Any()); Assert.AreEqual(3, results.Count()); Assert.IsFalse(results[0].HasError, "This call to StoreAndDeploy() should have no errors storing certificate"); - Assert.AreEqual("CertificateStorage", results[0].Category); - Assert.IsTrue(results[0].Description.Contains("Certificate stored OK"), $"Unexpected description: '{results[0].Description}'"); - Assert.AreEqual("Certificate Stored", results[0].Title); + Assert.AreEqual(results[0].Category, "CertificateStorage"); + Assert.IsTrue(results[0].Description.Contains("Certificate stored OK")); + Assert.AreEqual(results[0].Title, "Certificate Stored"); Assert.IsFalse(results[1].HasError, "This call to StoreAndDeploy() should not have an error adding binding while deploying certificate"); - Assert.AreEqual("Deployment.AddBinding", results[1].Category); - Assert.IsTrue(results[1].Description.Contains("Add https binding | | ***:443:test.com SNI**"), $"Unexpected description: '{results[1].Description}'"); - Assert.AreEqual("Install Certificate For Binding", results[1].Title); + Assert.AreEqual(results[1].Category, "Deployment.AddBinding"); + Assert.IsTrue(results[1].Description.Contains("Add https binding | | ***:443:test.com SNI**")); + Assert.AreEqual(results[1].Title, "Install Certificate For Binding"); Assert.IsFalse(results[2].HasError, "This call to StoreAndDeploy() should not have an error adding binding while deploying certificate"); - Assert.AreEqual("Deployment.UpdateBinding", results[2].Category); - Assert.IsTrue(results[2].Description.Contains("Update https binding | | **\\*:443:test.com SNI**"), $"Unexpected description: '{results[2].Description}'"); - Assert.AreEqual("Install Certificate For Binding", results[2].Title); - - CertificateManager.RemoveCertificate(cert, Certify.Management.CertificateManager.DEFAULT_STORE_NAME); + Assert.AreEqual(results[2].Category, "Deployment.UpdateBinding"); + Assert.IsTrue(results[2].Description.Contains("Update https binding | | **\\*:443:test.com SNI**")); + Assert.AreEqual(results[2].Title, "Install Certificate For Binding"); } +#endif [TestMethod, Description("Test if ftp bindings are handled when not in preview")] public async Task FtpBindingChecksNoPreview() @@ -906,6 +904,7 @@ public async Task FtpBindingChecksNoPreview() new BindingInfo{ Host="ftp.test.com", IP="127.0.0.1", Port = 20, Protocol="ftp", IsFtpSite=true }, }; var deployment = new BindingDeploymentManager(); + var dummyCertPath = Environment.CurrentDirectory + "\\Assets\\dummycert.pfx"; var testManagedCert = new ManagedCertificate { Id = Guid.NewGuid().ToString(), @@ -926,30 +925,30 @@ public async Task FtpBindingChecksNoPreview() } }, ItemType = ManagedCertificateType.SSL_ACME, - CertificatePath = _dummyCertPath + CertificatePath = dummyCertPath }; var mockTarget = new MockBindingDeploymentTarget(); mockTarget.AllBindings = bindings; - var results = await deployment.StoreAndDeploy(mockTarget, testManagedCert, _dummyCertPath, pfxPwd: "", false, Certify.Management.CertificateManager.DEFAULT_STORE_NAME); + var results = await deployment.StoreAndDeploy(mockTarget, testManagedCert, dummyCertPath, pfxPwd: "", false, Certify.Management.CertificateManager.DEFAULT_STORE_NAME); Assert.IsTrue(results.Any()); Assert.AreEqual(3, results.Count()); Assert.IsFalse(results[0].HasError, "This call to StoreAndDeploy() should have no errors storing certificate"); - Assert.AreEqual("CertificateStorage", results[0].Category); - Assert.IsTrue(results[0].Description.Contains("Certificate stored OK"), $"Unexpected description: '{results[0].Description}'"); - Assert.AreEqual("Certificate Stored", results[0].Title); + Assert.AreEqual(results[0].Category, "CertificateStorage"); + Assert.IsTrue(results[0].Description.Contains("Certificate stored OK")); + Assert.AreEqual(results[0].Title, "Certificate Stored"); Assert.IsFalse(results[1].HasError, "This call to StoreAndDeploy() should not have an error adding binding while deploying certificate"); - Assert.AreEqual("Deployment.UpdateBinding", results[1].Category); - Assert.IsTrue(results[1].Description.Contains("Update ftp binding | | ***:20:ftp.test.com**"), $"Unexpected description: '{results[1].Description}'"); - Assert.AreEqual("Install Certificate For Binding", results[1].Title); + Assert.AreEqual(results[1].Category, "Deployment.AddBinding"); + Assert.IsTrue(results[1].Description.Contains("Add ftp binding | | ***:21:ftp.test.com **")); + Assert.AreEqual(results[1].Title, "Install Certificate For FTP Binding"); Assert.IsFalse(results[2].HasError, "This call to StoreAndDeploy() should not have an error adding binding while deploying certificate"); - Assert.AreEqual("Deployment.UpdateBinding", results[2].Category); - Assert.IsTrue(results[2].Description.Contains("Update ftp binding | | **127.0.0.1:20:ftp.test.com**"), $"Unexpected description: '{results[2].Description}'"); - Assert.AreEqual("Install Certificate For Binding", results[2].Title); + Assert.AreEqual(results[2].Category, "Deployment.AddBinding"); + Assert.IsTrue(results[2].Description.Contains("Add ftp binding | | ***:21:ftp.test.com **")); + Assert.AreEqual(results[2].Title, "Install Certificate For FTP Binding"); } [TestMethod, Description("Test if ftp bindings are handled when not in preview with Certificate Request that defines a BindingIPAddress")] @@ -960,6 +959,7 @@ public async Task FtpBindingChecksCertReqBindingIPAddr() new BindingInfo{ Host="ftp.test.com", IP="127.0.0.1", Port = 20, Protocol="ftp", IsFtpSite=true }, }; var deployment = new BindingDeploymentManager(); + var dummyCertPath = Environment.CurrentDirectory + "\\Assets\\dummycert.pfx"; var testManagedCert = new ManagedCertificate { Id = Guid.NewGuid().ToString(), @@ -981,30 +981,30 @@ public async Task FtpBindingChecksCertReqBindingIPAddr() } }, ItemType = ManagedCertificateType.SSL_ACME, - CertificatePath = _dummyCertPath + CertificatePath = dummyCertPath }; var mockTarget = new MockBindingDeploymentTarget(); mockTarget.AllBindings = bindings; - var results = await deployment.StoreAndDeploy(mockTarget, testManagedCert, _dummyCertPath, pfxPwd: "", false, Certify.Management.CertificateManager.DEFAULT_STORE_NAME); + var results = await deployment.StoreAndDeploy(mockTarget, testManagedCert, dummyCertPath, pfxPwd: "", false, Certify.Management.CertificateManager.DEFAULT_STORE_NAME); Assert.IsTrue(results.Any()); Assert.AreEqual(3, results.Count()); Assert.IsFalse(results[0].HasError, "This call to StoreAndDeploy() should have no errors storing certificate"); - Assert.AreEqual("CertificateStorage", results[0].Category); - Assert.IsTrue(results[0].Description.Contains("Certificate stored OK"), $"Unexpected description: {results[0].Description}"); - Assert.AreEqual("Certificate Stored", results[0].Title); + Assert.AreEqual(results[0].Category, "CertificateStorage"); + Assert.IsTrue(results[0].Description.Contains("Certificate stored OK")); + Assert.AreEqual(results[0].Title, "Certificate Stored"); Assert.IsFalse(results[1].HasError, "This call to StoreAndDeploy() should not have an error adding binding while deploying certificate"); - Assert.AreEqual("Deployment.UpdateBinding", results[1].Category); - Assert.IsTrue(results[1].Description.Contains("Update ftp binding | | ***:20:ftp.test.com**"), $"Unexpected description: {results[1].Description}"); - Assert.AreEqual("Install Certificate For Binding", results[1].Title); + Assert.AreEqual(results[1].Category, "Deployment.AddBinding"); + Assert.IsTrue(results[1].Description.Contains("Add ftp binding | | **127.0.0.1:21:ftp.test.com **")); + Assert.AreEqual(results[1].Title, "Install Certificate For FTP Binding"); Assert.IsFalse(results[2].HasError, "This call to StoreAndDeploy() should not have an error adding binding while deploying certificate"); - Assert.AreEqual("Deployment.UpdateBinding", results[2].Category); - Assert.IsTrue(results[2].Description.Contains("Update ftp binding | | **127.0.0.1:20:ftp.test.com**"), $"Unexpected description: {results[2].Description}"); - Assert.AreEqual("Install Certificate For Binding", results[2].Title); + Assert.AreEqual(results[2].Category, "Deployment.AddBinding"); + Assert.IsTrue(results[2].Description.Contains("Add ftp binding | | **127.0.0.1:21:ftp.test.com **")); + Assert.AreEqual(results[2].Title, "Install Certificate For FTP Binding"); } [TestMethod, Description("Test if ftp bindings are handled when not in preview with Certificate Request that defines a BindingPort")] @@ -1015,6 +1015,7 @@ public async Task FtpBindingChecksCertReqBindingPort() new BindingInfo{ Host="ftp.test.com", IP="127.0.0.1", Port = 20, Protocol="ftp", IsFtpSite=true }, }; var deployment = new BindingDeploymentManager(); + var dummyCertPath = Environment.CurrentDirectory + "\\Assets\\dummycert.pfx"; var testManagedCert = new ManagedCertificate { Id = Guid.NewGuid().ToString(), @@ -1036,30 +1037,30 @@ public async Task FtpBindingChecksCertReqBindingPort() } }, ItemType = ManagedCertificateType.SSL_ACME, - CertificatePath = _dummyCertPath + CertificatePath = dummyCertPath }; var mockTarget = new MockBindingDeploymentTarget(); mockTarget.AllBindings = bindings; - var results = await deployment.StoreAndDeploy(mockTarget, testManagedCert, _dummyCertPath, pfxPwd: "", false, Certify.Management.CertificateManager.DEFAULT_STORE_NAME); + var results = await deployment.StoreAndDeploy(mockTarget, testManagedCert, dummyCertPath, pfxPwd: "", false, Certify.Management.CertificateManager.DEFAULT_STORE_NAME); Assert.IsTrue(results.Any()); Assert.AreEqual(3, results.Count()); Assert.IsFalse(results[0].HasError, "This call to StoreAndDeploy() should have no errors storing certificate"); - Assert.AreEqual("CertificateStorage", results[0].Category); - Assert.IsTrue(results[0].Description.Contains("Certificate stored OK"), $"Unexpected description: '{results[0].Description}'"); - Assert.AreEqual("Certificate Stored", results[0].Title); + Assert.AreEqual(results[0].Category, "CertificateStorage"); + Assert.IsTrue(results[0].Description.Contains("Certificate stored OK")); + Assert.AreEqual(results[0].Title, "Certificate Stored"); Assert.IsFalse(results[1].HasError, "This call to StoreAndDeploy() should not have an error adding binding while deploying certificate"); - Assert.AreEqual("Deployment.UpdateBinding", results[1].Category); - Assert.IsTrue(results[1].Description.Contains("Update ftp binding | | ***:20:ftp.test.com**"), $"Unexpected description: '{results[1].Description}'"); - Assert.AreEqual("Install Certificate For Binding", results[1].Title); + Assert.AreEqual(results[1].Category, "Deployment.AddBinding"); + Assert.IsTrue(results[1].Description.Contains("Add ftp binding | | ***:22:ftp.test.com **")); + Assert.AreEqual(results[1].Title, "Install Certificate For FTP Binding"); Assert.IsFalse(results[2].HasError, "This call to StoreAndDeploy() should not have an error adding binding while deploying certificate"); - Assert.AreEqual("Deployment.UpdateBinding", results[2].Category); - Assert.IsTrue(results[2].Description.Contains("Update ftp binding | | **127.0.0.1:20:ftp.test.com**"), $"Unexpected description: '{results[2].Description}'"); - Assert.AreEqual("Install Certificate For Binding", results[2].Title); + Assert.AreEqual(results[2].Category, "Deployment.AddBinding"); + Assert.IsTrue(results[2].Description.Contains("Add ftp binding | | ***:22:ftp.test.com **")); + Assert.AreEqual(results[2].Title, "Install Certificate For FTP Binding"); } [TestMethod, Description("Test update bindings are skipped when using a protocol other than http, https, or ftp")] @@ -1069,6 +1070,7 @@ public async Task UpdateBindingSkippedUnsupportedProtocol() new BindingInfo{ Host="smtp.test.com", IP="127.0.0.1", Port = 587, Protocol="smtp" }, }; var deployment = new BindingDeploymentManager(); + var dummyCertPath = Environment.CurrentDirectory + "\\Assets\\dummycert.pfx"; var testManagedCert = new ManagedCertificate { Id = Guid.NewGuid().ToString(), @@ -1089,20 +1091,20 @@ public async Task UpdateBindingSkippedUnsupportedProtocol() } }, ItemType = ManagedCertificateType.SSL_ACME, - CertificatePath = _dummyCertPath + CertificatePath = dummyCertPath }; var mockTarget = new MockBindingDeploymentTarget(); mockTarget.AllBindings = bindings; - var results = await deployment.StoreAndDeploy(mockTarget, testManagedCert, _dummyCertPath, pfxPwd: "", false, Certify.Management.CertificateManager.DEFAULT_STORE_NAME); + var results = await deployment.StoreAndDeploy(mockTarget, testManagedCert, dummyCertPath, pfxPwd: "", false, Certify.Management.CertificateManager.DEFAULT_STORE_NAME); Assert.IsTrue(results.Any()); Assert.AreEqual(1, results.Count()); Assert.IsFalse(results[0].HasError, "This call to StoreAndDeploy() should have no errors storing certificate"); - Assert.AreEqual("CertificateStorage", results[0].Category); - Assert.IsTrue(results[0].Description.Contains("Certificate stored OK"), $"Unexpected description: '{results[0].Description}'"); - Assert.AreEqual("Certificate Stored", results[0].Title); + Assert.AreEqual(results[0].Category, "CertificateStorage"); + Assert.IsTrue(results[0].Description.Contains("Certificate stored OK")); + Assert.AreEqual(results[0].Title, "Certificate Stored"); } [TestMethod, Description("Test if ftp bindings are handled when not in preview")] @@ -1113,6 +1115,7 @@ public async Task FtpBindingChecksUpdateExisting() new BindingInfo{ Host="ftp.test.com", IP="127.0.0.1", Port = 21, Protocol="ftp", IsFtpSite=true }, }; var deployment = new BindingDeploymentManager(); + var dummyCertPath = Environment.CurrentDirectory + "\\Assets\\dummycert.pfx"; var testManagedCert = new ManagedCertificate { Id = Guid.NewGuid().ToString(), @@ -1133,40 +1136,40 @@ public async Task FtpBindingChecksUpdateExisting() } }, ItemType = ManagedCertificateType.SSL_ACME, - CertificatePath = _dummyCertPath + CertificatePath = dummyCertPath }; var mockTarget = new MockBindingDeploymentTarget(); mockTarget.AllBindings = bindings; - var results = await deployment.StoreAndDeploy(mockTarget, testManagedCert, _dummyCertPath, pfxPwd: "", false, Certify.Management.CertificateManager.DEFAULT_STORE_NAME); + var results = await deployment.StoreAndDeploy(mockTarget, testManagedCert, dummyCertPath, pfxPwd: "", false, Certify.Management.CertificateManager.DEFAULT_STORE_NAME); Assert.IsTrue(results.Any()); Assert.AreEqual(1, results.Count()); Assert.IsFalse(results[0].HasError, "This call to StoreAndDeploy() should have no errors storing certificate"); - Assert.AreEqual("CertificateStorage", results[0].Category); - Assert.IsTrue(results[0].Description.Contains("Certificate stored OK"), $"Unexpected description: '{results[0].Description}'"); - Assert.AreEqual("Certificate Stored", results[0].Title); + Assert.AreEqual(results[0].Category, "CertificateStorage"); + Assert.IsTrue(results[0].Description.Contains("Certificate stored OK")); + Assert.AreEqual(results[0].Title, "Certificate Stored"); testManagedCert.RequestConfig.DeploymentSiteOption = DeploymentOption.AllSites; - results = await deployment.StoreAndDeploy(mockTarget, testManagedCert, _dummyCertPath, pfxPwd: "", false, Certify.Management.CertificateManager.DEFAULT_STORE_NAME); + results = await deployment.StoreAndDeploy(mockTarget, testManagedCert, dummyCertPath, pfxPwd: "", false, Certify.Management.CertificateManager.DEFAULT_STORE_NAME); Assert.IsTrue(results.Any()); Assert.AreEqual(3, results.Count()); Assert.IsFalse(results[0].HasError, "This call to StoreAndDeploy() should have no errors storing certificate"); - Assert.AreEqual("CertificateStorage", results[0].Category); - Assert.IsTrue(results[0].Description.Contains("Certificate stored OK"), $"Unexpected description: '{results[0].Description}'"); - Assert.AreEqual("Certificate Stored", results[0].Title); + Assert.AreEqual(results[0].Category, "CertificateStorage"); + Assert.IsTrue(results[0].Description.Contains("Certificate stored OK")); + Assert.AreEqual(results[0].Title, "Certificate Stored"); Assert.IsFalse(results[1].HasError, "This call to StoreAndDeploy() should not have an error adding binding while deploying certificate"); - Assert.AreEqual("Deployment.UpdateBinding", results[1].Category); - Assert.IsTrue(results[1].Description.Contains("Update ftp binding | | ***:21:ftp.test.com**"), $"Unexpected description: '{results[1].Description}'"); - Assert.AreEqual("Install Certificate For Binding", results[1].Title); + Assert.AreEqual(results[1].Category, "Deployment.UpdateBinding"); + Assert.IsTrue(results[1].Description.Contains("Update ftp binding | | ***:21:ftp.test.com**")); + Assert.AreEqual(results[1].Title, "Install Certificate For Binding"); Assert.IsFalse(results[2].HasError, "This call to StoreAndDeploy() should not have an error adding binding while deploying certificate"); - Assert.AreEqual("Deployment.UpdateBinding", results[2].Category); - Assert.IsTrue(results[2].Description.Contains("Update ftp binding | | **127.0.0.1:21:ftp.test.com**"), $"Unexpected description: '{results[2].Description}'"); - Assert.AreEqual("Install Certificate For Binding", results[2].Title); + Assert.AreEqual(results[2].Category, "Deployment.UpdateBinding"); + Assert.IsTrue(results[2].Description.Contains("Update ftp binding | | ***:21:ftp.test.com**")); + Assert.AreEqual(results[2].Title, "Install Certificate For Binding"); } [TestMethod, Description("Test that duplicate https bindings are not created when multiple non-port 443 same-hostname bindings exist")] diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj index a178f4522..48a2bc52f 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj @@ -112,6 +112,12 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/ChallengeConfigMatchTests.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/ChallengeConfigMatchTests.cs index fd5b5553a..2053786f1 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/ChallengeConfigMatchTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/ChallengeConfigMatchTests.cs @@ -102,7 +102,7 @@ public void MultiChallengeConfigMatch() } [TestMethod, Description("Ensure correct challenge config selected based on domain")] - public void ChallengeDelgationRuleTests() + public void ChallengeDelegationRuleTests() { // wildcard rule tests [any subdomain source, any subdomain target] var testRule = "*.test.com:*.auth.test.co.uk"; @@ -140,5 +140,14 @@ public void ChallengeDelgationRuleTests() result = Management.Challenges.DnsChallengeHelper.ApplyChallengeDelegationRule("www.subdomain.example.com", "_acme-challenge.www.subdomain.example.com", testRule); Assert.AreEqual("_acme-challenge.www.subdomain.auth.example.co.uk", result); } + + [TestMethod, Description("Ensure correct challenge config selected when rule is blank")] + public void ChallengeDelegationRuleBlankRule() + { + // wildcard rule tests [any subdomain source, any subdomain target] + var testRule = "*.test.com:*.auth.test.co.uk"; + var result = Management.Challenges.DnsChallengeHelper.ApplyChallengeDelegationRule("test.com", "_acme-challenge.test.com", null); + Assert.AreEqual("_acme-challenge.test.com", result); + } } } diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/DomainZoneMatchTests.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/DomainZoneMatchTests.cs index 7a82f1011..0b724a6d2 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/DomainZoneMatchTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/DomainZoneMatchTests.cs @@ -18,8 +18,8 @@ public async Task DetermineRootDomainTests() new DnsZone{ Name="test.com", ZoneId="123-test.com"}, new DnsZone{ Name="subdomain.test.com", ZoneId="345-subdomain-test.com"}, new DnsZone{ Name="long-subdomain.test.com", ZoneId="345-subdomain-test.com"}, - new DnsZone{ Name="bar.co.uk", ZoneId="lengthtest-1"}, - new DnsZone{ Name="foobar.co.uk", ZoneId="lengthtest-2"} + new DnsZone{ Name="bar.co.uk", ZoneId="lengthtest-1"}, + new DnsZone{ Name="foobar.co.uk", ZoneId="lengthtest-2"} } ); @@ -32,6 +32,9 @@ public async Task DetermineRootDomainTests() domainRoot = await mockDnsProvider.Object.DetermineZoneDomainRoot("www.test.com", "123-test.com"); Assert.IsTrue(domainRoot.ZoneId == "123-test.com"); + domainRoot = await mockDnsProvider.Object.DetermineZoneDomainRoot("test.com", "bad.domain.com"); + Assert.IsTrue(domainRoot.ZoneId == "123-test.com"); + domainRoot = await mockDnsProvider.Object.DetermineZoneDomainRoot("www.test.com", null); Assert.IsTrue(domainRoot.ZoneId == "123-test.com"); diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/GetDnsProviderTests.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/GetDnsProviderTests.cs new file mode 100644 index 000000000..4dcfc9c55 --- /dev/null +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/GetDnsProviderTests.cs @@ -0,0 +1,140 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Certify.Core.Management.Challenges; +using Certify.Management; +using Certify.Models.Config; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Certify.Core.Tests.Unit +{ + [TestClass] + public class GetDnsProviderTests + { + private SQLiteCredentialStore credentialsManager; + private DnsChallengeHelper dnsHelper; + + public GetDnsProviderTests() + { + var pluginManager = new PluginManager(); + pluginManager.LoadPlugins(new List { PluginManager.PLUGINS_DNS_PROVIDERS }); + var TEST_PATH = "Tests\\credentials"; + credentialsManager = new SQLiteCredentialStore(storageSubfolder: TEST_PATH); + dnsHelper = new DnsChallengeHelper(credentialsManager); + } + + [TestMethod, Description("Test Getting DNS Provider with empty CredentialsID")] + public async Task TestGetDnsProvidersEmptyCredentialsID() + { + var providerTypeId = "DNS01.Powershell"; + var credentialsId = ""; + var result = await dnsHelper.GetDnsProvider(providerTypeId, credentialsId, null, credentialsManager); + + // Assert + Assert.AreEqual("DNS Challenge API Provider not set or could not load.", result.Result.Message); + Assert.IsFalse(result.Result.IsSuccess); + Assert.IsFalse(result.Result.IsWarning); + } + + [TestMethod, Description("Test Getting DNS Provider with empty ProviderTypeId")] + public async Task TestGetDnsProvidersEmptyProviderTypeId() + { + var providerTypeId = ""; + var secrets = new Dictionary(); + secrets.Add("zoneid", "ABC123"); + secrets.Add("secretid", "thereisnosecret"); + var testCredential = new StoredCredential + { + ProviderType = "DNS01.Manual", + Title = "A test credential", + StorageKey = Guid.NewGuid().ToString(), + Secret = Newtonsoft.Json.JsonConvert.SerializeObject(secrets) + }; + var updateResult = await credentialsManager.Update(testCredential); + + var result = await dnsHelper.GetDnsProvider(providerTypeId, testCredential.StorageKey, null, credentialsManager); + + // Assert + Assert.AreEqual("DNS Challenge API Provider not set or could not load.", result.Result.Message); + Assert.IsFalse(result.Result.IsSuccess); + Assert.IsFalse(result.Result.IsWarning); + } + + [TestMethod, Description("Test Getting DNS Provider with a bad CredentialId")] + public async Task TestGetDnsProvidersBadCredentialId() + { + var secrets = new Dictionary(); + secrets.Add("zoneid", "ABC123"); + secrets.Add("secretid", "thereisnosecret"); + var testCredential = new StoredCredential + { + ProviderType = "DNS01.Manual", + Title = "A test credential", + StorageKey = Guid.NewGuid().ToString(), + Secret = Newtonsoft.Json.JsonConvert.SerializeObject(secrets) + }; + + var updateResult = await credentialsManager.Update(testCredential); + + var result = await dnsHelper.GetDnsProvider(testCredential.ProviderType, testCredential.StorageKey.Substring(5), null, credentialsManager); + + // Assert + Assert.AreEqual("DNS Challenge API Credentials could not be decrypted or no longer exists. The original user must be used for decryption.", result.Result.Message); + Assert.IsFalse(result.Result.IsSuccess); + Assert.IsFalse(result.Result.IsWarning); + } + + [TestMethod, Description("Test Getting DNS Provider")] + public async Task TestGetDnsProviders() + { + var secrets = new Dictionary(); + secrets.Add("zoneid", "ABC123"); + secrets.Add("secretid", "thereisnosecret"); + var testCredential = new StoredCredential + { + ProviderType = "DNS01.Manual", + Title = "A test credential", + StorageKey = Guid.NewGuid().ToString(), + Secret = Newtonsoft.Json.JsonConvert.SerializeObject(secrets) + }; + + var updateResult = await credentialsManager.Update(testCredential); + + var result = await dnsHelper.GetDnsProvider(testCredential.ProviderType, testCredential.StorageKey, null, credentialsManager); + + // Assert + Assert.AreEqual("Create Provider Instance", result.Result.Message); + Assert.IsTrue(result.Result.IsSuccess); + Assert.IsFalse(result.Result.IsWarning); + Assert.AreEqual(testCredential.ProviderType, result.Provider.ProviderId); + } + + [TestMethod, Description("Test Getting Challenge API Providers")] + public async Task TestGetChallengeAPIProviders() + { + var challengeAPIProviders = await ChallengeProviders.GetChallengeAPIProviders(); + + // Assert + Assert.IsNotNull(challengeAPIProviders); + Assert.AreNotEqual(0, challengeAPIProviders.Count); + foreach (object item in challengeAPIProviders) + { + var itemType = item.GetType(); + Assert.IsTrue(itemType.GetProperty("ChallengeType") != null); + Assert.IsTrue(itemType.GetProperty("Config") != null); + Assert.IsTrue(itemType.GetProperty("Description") != null); + Assert.IsTrue(itemType.GetProperty("HandlerType") != null); + Assert.IsTrue(itemType.GetProperty("HasDynamicParameters") != null); + Assert.IsTrue(itemType.GetProperty("HelpUrl") != null); + Assert.IsTrue(itemType.GetProperty("Id") != null); + Assert.IsTrue(itemType.GetProperty("IsEnabled") != null); + Assert.IsTrue(itemType.GetProperty("IsExperimental") != null); + Assert.IsTrue(itemType.GetProperty("IsTestModeSupported") != null); + Assert.IsTrue(itemType.GetProperty("PropagationDelaySeconds") != null); + Assert.IsTrue(itemType.GetProperty("ProviderCategoryId") != null); + Assert.IsTrue(itemType.GetProperty("ProviderParameters") != null); + Assert.IsTrue(itemType.GetProperty("Title") != null); + } + } + } +} From d1a0f13c4000a91d4a81d9dc6fd308175825cd27 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Mon, 30 Oct 2023 14:20:00 +0800 Subject: [PATCH 071/328] Update docker related settings for service worker. --- src/.dockerignore | 30 +++++++++++++++++++ src/Certify.Core.Service.sln | 10 +++++++ .../Certify.Service.Worker.csproj | 4 ++- 3 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 src/.dockerignore diff --git a/src/.dockerignore b/src/.dockerignore new file mode 100644 index 000000000..fe1152bdb --- /dev/null +++ b/src/.dockerignore @@ -0,0 +1,30 @@ +**/.classpath +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/azds.yaml +**/bin +**/charts +**/docker-compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +LICENSE +README.md +!**/.gitignore +!.git/HEAD +!.git/config +!.git/packed-refs +!.git/refs/heads/** \ No newline at end of file diff --git a/src/Certify.Core.Service.sln b/src/Certify.Core.Service.sln index a6b5fb3f5..7339e860a 100644 --- a/src/Certify.Core.Service.sln +++ b/src/Certify.Core.Service.sln @@ -69,6 +69,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Certify.Providers.ACME.Anvi EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Certify.ACME.Anvil", "..\..\libs\anvil\src\Certify.ACME.Anvil\Certify.ACME.Anvil.csproj", "{443202E1-B6E5-4625-BC3E-B3CB54CF4055}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Certify.Service.Worker", "Certify.Server\Certify.Service.Worker\Certify.Service.Worker\Certify.Service.Worker.csproj", "{D786EFD1-7402-43F0-B74F-504DB7C25FF6}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -277,6 +279,14 @@ Global {443202E1-B6E5-4625-BC3E-B3CB54CF4055}.Release|Any CPU.Build.0 = Release|Any CPU {443202E1-B6E5-4625-BC3E-B3CB54CF4055}.Release|x64.ActiveCfg = Release|Any CPU {443202E1-B6E5-4625-BC3E-B3CB54CF4055}.Release|x64.Build.0 = Release|Any CPU + {D786EFD1-7402-43F0-B74F-504DB7C25FF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D786EFD1-7402-43F0-B74F-504DB7C25FF6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D786EFD1-7402-43F0-B74F-504DB7C25FF6}.Debug|x64.ActiveCfg = Debug|Any CPU + {D786EFD1-7402-43F0-B74F-504DB7C25FF6}.Debug|x64.Build.0 = Debug|Any CPU + {D786EFD1-7402-43F0-B74F-504DB7C25FF6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D786EFD1-7402-43F0-B74F-504DB7C25FF6}.Release|Any CPU.Build.0 = Release|Any CPU + {D786EFD1-7402-43F0-B74F-504DB7C25FF6}.Release|x64.ActiveCfg = Release|Any CPU + {D786EFD1-7402-43F0-B74F-504DB7C25FF6}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Certify.Service.Worker.csproj b/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Certify.Service.Worker.csproj index d42e45ffc..06f0b6c9e 100644 --- a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Certify.Service.Worker.csproj +++ b/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Certify.Service.Worker.csproj @@ -1,15 +1,17 @@ - + net8.0 dotnet-Certify.Service.Worker-347A036F-C1EA-4D32-A163-DCB38C3CA53E Linux -v certifydata:/usr/share/Certify + ..\..\.. portable true + From ceba51d619419952aebab877b78ba22c74e6a12e Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Mon, 30 Oct 2023 15:36:49 +0800 Subject: [PATCH 072/328] Update notes for docker --- .../Certify.Service.Worker/Certify.Service.Worker/readme.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/readme.md b/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/readme.md index 0d33aeb8a..aa95fc5c0 100644 --- a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/readme.md +++ b/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/readme.md @@ -73,3 +73,9 @@ dotnet publish -c Release -r linux-x64 --self-contained true Single File: dotnet publish -c Release -r win-x64 --self-contained true -p:PublishSingleFile=true + + +Docker +--------- +Requires a dockerfile to define how to build the images. certify-manager/docker for more dockerfile examples +Requries Microsoft.VisualStudio.Azure.Containers.Tools.Targets NuGet package installed in the project to hook up docker integration. From f7bf42efd785ddc317b1933e5342447b2fe7e27a Mon Sep 17 00:00:00 2001 From: Justin Nelson Date: Tue, 31 Oct 2023 13:30:19 -0700 Subject: [PATCH 073/328] Added 31 new unit tests for methods in CertifyManage.Account --- docs/testing.md | 8 +- .../CertifyManager/CertifyManager.Account.cs | 30 +- .../Certify.Core.Tests.Unit.csproj | 1 + .../CertifyManagerAccountTests.cs | 807 ++++++++++++++++++ 4 files changed, 816 insertions(+), 30 deletions(-) create mode 100644 src/Certify.Tests/Certify.Core.Tests.Unit/CertifyManagerAccountTests.cs diff --git a/docs/testing.md b/docs/testing.md index 00c00ba28..2706964c8 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -3,6 +3,11 @@ Testing Configuration - In Visual Studio (or other Test UI such as AxoCover), set execution environment to 64-bit to ensure tests load. - Units tests are for discreet function testing or limited component dependency tests. + - `CertifyManagerAccountTests` require an existing Prod and Staging ACME account for letsencrypt.org to exist. It also requires a `.env.test_accounts` file in the directory `C:\ProgramData\certify\Tests` with values for `RESTORE_KEY_PEM`, `RESTORE_ACCOUNT_URI`, and `RESTORE_ACCOUNT_EMAIL` for an existing letsencrypt.org ACME account + ```.env + RESTORE_KEY_PEM="-----BEGIN EC PRIVATE KEY-----\r\nMHcCAQEEINL5koIn4o+an+EwyDQEd4Ggnxra5j7Oro13M5klKmhaoAoGCCqGSM49\r\nAwEHoUQDQgAEPF7u1CLMe9FIBQo0MVmv7vlvqGOdSERG5nRLkNKTDUgBRxkXGqY+\r\nGbnnzXUb7j4g7VN7CuEy0SpCdFItD+63hQ==\r\n-----END EC PRIVATE KEY-----\r\n" + RESTORE_ACCOUNT_URI=https://acme-staging-v02.api.letsencrypt.org/acme/acct/123456789 + RESTORE_ACCOUNT_EMAIL=admin.8c635b@test.com - Integration tests exercise multiple components and may interact with ACME services etc. Required elements include: - IIS Installed on local machine - The debug version of the app must be configured with a contact against staging Let's Encrypt servers @@ -26,8 +31,7 @@ Testing Configuration "Cloudflare_ZoneId": "5265262gdd562s4x6xd64zxczxcv", "Cloudflare_TestDomain": "anothertest.com" } -``` -In addition, the test domain for some tests can be set using the CERTIFYSSLDOMAIN environment variable. +- In addition, the test domain for some tests can be set using the CERTIFYSSLDOMAIN environment variable. diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.Account.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.Account.cs index c93efeb16..c9186e3fb 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.Account.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.Account.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -670,33 +670,7 @@ public async Task RemoveCertificateAuthority(string id) } } - /// - /// Load cached set of ACME Certificate authorities - /// - private void LoadCertificateAuthorities() - { - _certificateAuthorities.Clear(); - - // load core CAs and custom CAs - foreach (var ca in CertificateAuthority.CoreCertificateAuthorities) - { - _certificateAuthorities.TryAdd(ca.Id, ca); - } - - try - { - var customCAs = SettingsManager.GetCustomCertificateAuthorities(); - - foreach (var ca in customCAs) - { - _certificateAuthorities.TryAdd(ca.Id, ca); - } - } - catch (Exception exp) - { - // failed to load custom CAs - _serviceLog?.Error(exp.Message); - } + return await Task.FromResult(new ActionResult("An error occurred removing the indicated Custom CA from the Certificate Authorities list.", false)); } } } diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj index 48a2bc52f..d694a6b47 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj @@ -136,6 +136,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/CertifyManagerAccountTests.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/CertifyManagerAccountTests.cs new file mode 100644 index 000000000..a0a75fa02 --- /dev/null +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/CertifyManagerAccountTests.cs @@ -0,0 +1,807 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Certify.ACME.Anvil; +using Certify.Management; +using Certify.Models; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Certify.Core.Tests.Unit +{ + [TestClass] + public class CertifyManagerAccountTests + { + private readonly CertifyManager _certifyManager; + + public CertifyManagerAccountTests() + { + _certifyManager = new CertifyManager(); + _certifyManager.Init().Wait(); + var testCredentialsPath = Path.Combine(EnvironmentUtil.GetAppDataFolder(), "Tests", ".env.test_accounts"); + DotNetEnv.Env.Load(testCredentialsPath); + } + + [TestMethod, Description("Happy path test for using CertifyManager.GetAccountDetails()")] + public async Task TestCertifyManagerGetAccountDetails() + { + var testUrl = "test.com"; + var dummyManagedCert = (new ManagedCertificate { CurrentOrderUri = testUrl, UseStagingMode = true }); + var caAccount = await _certifyManager.GetAccountDetails(dummyManagedCert); + Assert.IsNotNull(caAccount, "Expected result of CertifyManager.GetAccountDetails() to not be null"); + } + + [TestMethod, Description("Test for using CertifyManager.GetAccountDetails() when passed in managed certificate is null")] + public async Task TestCertifyManagerGetAccountDetailsNullItem() + { + var caAccount = await _certifyManager.GetAccountDetails(null); + Assert.IsNotNull(caAccount, "Expected result of CertifyManager.GetAccountDetails() to not be null"); + } + + [TestMethod, Description("Test for using CertifyManager.GetAccountDetails() when allowCache is false")] + public async Task TestCertifyManagerGetAccountDetailsAllowCacheFalse() + { + var testUrl = "test.com"; + var dummyManagedCert = (new ManagedCertificate { CurrentOrderUri = testUrl, UseStagingMode = true }); + var caAccount = await _certifyManager.GetAccountDetails(dummyManagedCert, false); + Assert.IsNotNull(caAccount, "Expected result of CertifyManager.GetAccountDetails() to not be null"); + } + + [TestMethod, Description("Test for using CertifyManager.GetAccountDetails() when CertificateAuthorityId is defined in passed ManagedCertificate")] + public async Task TestCertifyManagerGetAccountDetailsDefinedCertificateAuthorityId() + { + var testUrl = "test.com"; + var dummyManagedCert = (new ManagedCertificate { CurrentOrderUri = testUrl, UseStagingMode = true, CertificateAuthorityId = "letsencrypt.org" }); + var caAccount = await _certifyManager.GetAccountDetails(dummyManagedCert); + Assert.IsNotNull(caAccount, "Expected result of CertifyManager.GetAccountDetails() to not be null"); + Assert.AreEqual("letsencrypt.org", caAccount.CertificateAuthorityId, $"Unexpected certificate authority id '{caAccount.CertificateAuthorityId}'"); + } + + [TestMethod, Description("Test for using CertifyManager.GetAccountDetails() when OverrideAccountDetails is defined in CertifyManager")] + public async Task TestCertifyManagerGetAccountDetailsDefinedOverrideAccountDetails() + { + var testUrl = "test.com"; + var account = new AccountDetails + { + AccountKey = "", + AccountURI = "", + Title = "Dev", + Email = "test@certifytheweb.com", + CertificateAuthorityId = "letsencrypt.org", + StorageKey = "dev", + IsStagingAccount = true, + }; + _certifyManager.OverrideAccountDetails = account; + + var dummyManagedCert = (new ManagedCertificate { CurrentOrderUri = testUrl, UseStagingMode = true }); + var caAccount = await _certifyManager.GetAccountDetails(dummyManagedCert); + Assert.IsNotNull(caAccount, "Expected result of CertifyManager.GetAccountDetails() to not be null"); + Assert.AreEqual("test@certifytheweb.com", caAccount.Email); + + _certifyManager.OverrideAccountDetails = null; + } + + [TestMethod, Description("Test for using CertifyManager.GetAccountDetails() when there is no matching account")] + public async Task TestCertifyManagerGetAccountDetailsNoMatches() + { + var testUrl = "test.com"; + var dummyManagedCert = (new ManagedCertificate { CurrentOrderUri = testUrl, UseStagingMode = true, CertificateAuthorityId = "sectigo-ev" }); + var caAccount = await _certifyManager.GetAccountDetails(dummyManagedCert); + Assert.IsNull(caAccount, "Expected result of CertifyManager.GetAccountDetails() to be null"); + } + + [TestMethod, Description("Test for using CertifyManager.GetAccountDetails() when there is no matching account")] + public async Task TestCertifyManagerGetAccountDetailsIsResumeOrder() + { + var testUrl = "test.com"; + var dummyManagedCert = (new ManagedCertificate { CurrentOrderUri = testUrl, UseStagingMode = true, CertificateAuthorityId = "letsencrypt.org", LastAttemptedCA = "zerossl.com" }); + var caAccount = await _certifyManager.GetAccountDetails(dummyManagedCert, true, false, true); + Assert.IsNotNull(caAccount, "Expected result of CertifyManager.GetAccountDetails() to not be null"); + } + + [TestMethod, Description("Test for using CertifyManager.GetAccountDetails() when allowFailover is true")] + public async Task TestCertifyManagerGetAccountDetailsAllowFailover() + { + var testUrl = "test.com"; + var dummyManagedCert = (new ManagedCertificate { CurrentOrderUri = testUrl, UseStagingMode = true }); + var caAccount = await _certifyManager.GetAccountDetails(dummyManagedCert, true, true); + Assert.IsNotNull(caAccount, "Expected result of CertifyManager.GetAccountDetails() to not be null"); + } + + [TestMethod, Description("Happy path test for using CertifyManager.AddAccount()")] + public async Task TestCertifyManagerAddAccount() + { + AccountDetails accountDetails = null; + try + { + // Setup account registration info + var contactRegEmail = "admin."+ Guid.NewGuid().ToString().Substring(0, 6) + "@test.com"; + var contactRegistration = new ContactRegistration + { + AgreedToTermsAndConditions = true, + CertificateAuthorityId = "letsencrypt.org", + EmailAddress = contactRegEmail, + ImportedAccountKey = "", + ImportedAccountURI = "", + IsStaging = true + }; + + // Add account + var addAccountRes = await _certifyManager.AddAccount(contactRegistration); + Assert.IsTrue(addAccountRes.IsSuccess, $"Expected account creation to be successful for {contactRegEmail}"); + accountDetails = (await _certifyManager.GetAccountRegistrations()).Find(a => a.Email == contactRegEmail); + Assert.IsNotNull(accountDetails, $"Expected one of the accounts returned by CertifyManager.GetAccountRegistrations() to be for {contactRegEmail}"); + } + finally + { + // Cleanup added account + if (accountDetails != null) + { + await _certifyManager.RemoveAccount(accountDetails.StorageKey, true); + } + } + } + + [TestMethod, Description("Happy path test for using CertifyManager.RemoveAccount()")] + public async Task TestCertifyManagerRemoveAccount() + { + // Setup account registration info + var contactRegEmail = "admin."+ Guid.NewGuid().ToString().Substring(0, 6) + "@test.com"; + var contactRegistration = new ContactRegistration + { + AgreedToTermsAndConditions = true, + CertificateAuthorityId = "letsencrypt.org", + EmailAddress = contactRegEmail, + ImportedAccountKey = "", + ImportedAccountURI = "", + IsStaging = true + }; + + // Add account + var addAccountRes = await _certifyManager.AddAccount(contactRegistration); + Assert.IsTrue(addAccountRes.IsSuccess, $"Expected account creation to be successful for {contactRegEmail}"); + var accountDetails = (await _certifyManager.GetAccountRegistrations()).Find(a => a.Email == contactRegEmail); + Assert.IsNotNull(accountDetails, $"Expected one of the accounts returned by CertifyManager.GetAccountRegistrations() to be for {contactRegEmail}"); + + // Remove account + var removeAccountRes = await _certifyManager.RemoveAccount(accountDetails.StorageKey, true); + Assert.IsTrue(removeAccountRes.IsSuccess, $"Expected account removal to be successful for {contactRegEmail}"); + accountDetails = (await _certifyManager.GetAccountRegistrations()).Find(a => a.Email == contactRegEmail); + Assert.IsNull(accountDetails, $"Did not expect an account for {contactRegEmail} to be returned by CertifyManager.GetAccountRegistrations()"); + } + + [TestMethod, Description("Test for CertifyManager.AddAccount() when AgreedToTermsAndConditions is false")] + public async Task TestCertifyManagerAddAccountDidNotAgree() + { + // Setup account registration info + var contactRegEmail = "admin." + Guid.NewGuid().ToString().Substring(0, 6) + "@test.com"; + var contactRegistration = new ContactRegistration + { + AgreedToTermsAndConditions = false, + CertificateAuthorityId = "letsencrypt.org", + EmailAddress = contactRegEmail, + ImportedAccountKey = "", + ImportedAccountURI = "", + IsStaging = true + }; + + // Attempt to add account + var addAccountRes = await _certifyManager.AddAccount(contactRegistration); + Assert.IsFalse(addAccountRes.IsSuccess, $"Expected account creation to be unsuccessful for {contactRegEmail}"); + Assert.AreEqual(addAccountRes.Message, "You must agree to the terms and conditions of the Certificate Authority to register with them.", "Unexpected error message"); + var accountDetails = (await _certifyManager.GetAccountRegistrations()).Find(a => a.Email == contactRegEmail); + Assert.IsNull(accountDetails, $"Did not expect an account for {contactRegEmail} to be returned by CertifyManager.GetAccountRegistrations()"); + } + + [TestMethod, Description("Test for CertifyManager.AddAccount() when CertificateAuthorityId is a bad value")] + public async Task TestCertifyManagerAddAccountBadCaId() + { + // Setup account registration info + var contactRegEmail = "admin." + Guid.NewGuid().ToString().Substring(0, 6) + "@test.com"; + var contactRegistration = new ContactRegistration + { + AgreedToTermsAndConditions = true, + CertificateAuthorityId = "bad_ca.org", + EmailAddress = contactRegEmail, + ImportedAccountKey = "", + ImportedAccountURI = "", + IsStaging = true + }; + + // Attempt to add account + var addAccountRes = await _certifyManager.AddAccount(contactRegistration); + Assert.IsFalse(addAccountRes.IsSuccess, $"Expected account creation to be unsuccessful for {contactRegEmail}"); + Assert.AreEqual(addAccountRes.Message, "Invalid Certificate Authority specified.", "Unexpected error message"); + var accountDetails = (await _certifyManager.GetAccountRegistrations()).Find(a => a.Email == contactRegEmail); + Assert.IsNull(accountDetails, $"Did not expect an account for {contactRegEmail} to be returned by CertifyManager.GetAccountRegistrations()"); + } + + [TestMethod, Description("Test for CertifyManager.AddAccount() when ImportedAccountKey is a blank value")] + public async Task TestCertifyManagerAddAccountMissingAccountKey() + { + // Setup account registration info + var contactRegEmail = "admin." + Guid.NewGuid().ToString().Substring(0, 6) + "@test.com"; + var contactRegistration = new ContactRegistration + { + AgreedToTermsAndConditions = true, + CertificateAuthorityId = "letsencrypt.org", + EmailAddress = contactRegEmail, + ImportedAccountKey = "", + ImportedAccountURI = "https://acme-staging-v02.api.letsencrypt.org/acme/acct/123403114", + IsStaging = true + }; + + // Attempt to add account + var addAccountRes = await _certifyManager.AddAccount(contactRegistration); + Assert.IsFalse(addAccountRes.IsSuccess, $"Expected account creation to be unsuccessful for {contactRegEmail}"); + Assert.AreEqual(addAccountRes.Message, "To import account details both the existing account URI and account key in PEM format are required. ", "Unexpected error message"); + var accountDetails = (await _certifyManager.GetAccountRegistrations()).Find(a => a.Email == contactRegEmail); + Assert.IsNull(accountDetails, $"Did not expect an account for {contactRegEmail} to be returned by CertifyManager.GetAccountRegistrations()"); + } + + [TestMethod, Description("Test for CertifyManager.AddAccount() when ImportedAccountURI is a blank value")] + public async Task TestCertifyManagerAddAccountMissingAccountUri() + { + // Setup account registration info + var contactRegEmail = "admin." + Guid.NewGuid().ToString().Substring(0, 6) + "@test.com"; + var contactRegistration = new ContactRegistration + { + AgreedToTermsAndConditions = true, + CertificateAuthorityId = "letsencrypt.org", + EmailAddress = contactRegEmail, + ImportedAccountKey = DotNetEnv.Env.GetString("RESTORE_KEY_PEM"), + ImportedAccountURI = "", + IsStaging = true + }; + + // Attempt to add account + var addAccountRes = await _certifyManager.AddAccount(contactRegistration); + Assert.IsFalse(addAccountRes.IsSuccess, $"Expected account creation to be unsuccessful for {contactRegEmail}"); + Assert.AreEqual(addAccountRes.Message, "To import account details both the existing account URI and account key in PEM format are required. ", "Unexpected error message"); + var accountDetails = (await _certifyManager.GetAccountRegistrations()).Find(a => a.Email == contactRegEmail); + Assert.IsNull(accountDetails, $"Did not expect an account for {contactRegEmail} to be returned by CertifyManager.GetAccountRegistrations()"); + } + + [TestMethod, Description("Test for CertifyManager.AddAccount() when ImportedAccountKey is a bad value")] + public async Task TestCertifyManagerAddAccountBadAccountKey() + { + // Setup account registration info + var contactRegEmail = "admin." + Guid.NewGuid().ToString().Substring(0, 6) + "@test.com"; + var contactRegistration = new ContactRegistration + { + AgreedToTermsAndConditions = true, + CertificateAuthorityId = "letsencrypt.org", + EmailAddress = contactRegEmail, + ImportedAccountKey = "tHiSiSnOtApEm", + ImportedAccountURI = DotNetEnv.Env.GetString("RESTORE_ACCOUNT_URI"), + IsStaging = true + }; + + // Attempt to add account + var addAccountRes = await _certifyManager.AddAccount(contactRegistration); + Assert.IsFalse(addAccountRes.IsSuccess, $"Expected account creation to be unsuccessful for {contactRegEmail}"); + Assert.AreEqual(addAccountRes.Message, "The provided account key was invalid or not supported for import. A PEM (text) format RSA or ECDA private key is required.", "Unexpected error message"); + var accountDetails = (await _certifyManager.GetAccountRegistrations()).Find(a => a.Email == contactRegEmail); + Assert.IsNull(accountDetails, $"Did not expect an account for {contactRegEmail} to be returned by CertifyManager.GetAccountRegistrations()"); + } + + [TestMethod, Description("Test for CertifyManager.AddAccount() when ImportedAccountKey and ImportedAccountURI are valid")] + public async Task TestCertifyManagerAddAccountImport() + { + // Setup account registration info + //var contactRegEmail = "admin.98b9a6@test.com"; + var contactRegEmail = DotNetEnv.Env.GetString("RESTORE_ACCOUNT_EMAIL"); + var contactRegistration = new ContactRegistration + { + AgreedToTermsAndConditions = true, + CertificateAuthorityId = "letsencrypt.org", + EmailAddress = contactRegEmail, + ImportedAccountKey = DotNetEnv.Env.GetString("RESTORE_KEY_PEM"), + ImportedAccountURI = DotNetEnv.Env.GetString("RESTORE_ACCOUNT_URI"), + IsStaging = true + }; + + // Add account + var addAccountRes = await _certifyManager.AddAccount(contactRegistration); + Assert.IsTrue(addAccountRes.IsSuccess, $"Expected account creation to be successful for {contactRegEmail}"); + var accountDetails = (await _certifyManager.GetAccountRegistrations()).Find(a => a.Email == contactRegEmail); + Assert.IsNotNull(accountDetails, $"Expected one of the accounts returned by CertifyManager.GetAccountRegistrations() to be for {contactRegEmail}"); + + // Remove account + var removeAccountRes = await _certifyManager.RemoveAccount(accountDetails.StorageKey); + Assert.IsTrue(removeAccountRes.IsSuccess, $"Expected account removal to be successful for {contactRegEmail}"); + accountDetails = (await _certifyManager.GetAccountRegistrations()).Find(a => a.Email == contactRegEmail); + Assert.IsNull(accountDetails, $"Did not expect an account for {contactRegEmail} to be returned by CertifyManager.GetAccountRegistrations()"); + } + + [TestMethod, Description("Test for using CertifyManager.RemoveAccount() with a bad storage key")] + public async Task TestCertifyManagerRemoveAccountBadKey() + { + // Attempt to remove account with bad storage key + var badStorageKey = "8da1a662-18ed-4787-a0b1-dc36db5a866b"; + var removeAccountRes = await _certifyManager.RemoveAccount(badStorageKey, true); + Assert.IsFalse(removeAccountRes.IsSuccess, $"Expected account removal to be unsuccessful for storage key {badStorageKey}"); + Assert.AreEqual(removeAccountRes.Message, "Account not found.", "Unexpected error message"); + } + + [TestMethod, Description("Happy path test for using CertifyManager.GetAccountAndACMEProvider()")] + public async Task TestCertifyManagerGetAccountAndAcmeProvider() + { + AccountDetails accountDetails = null; + try + { + // Setup account registration info + var contactRegEmail = "admin." + Guid.NewGuid().ToString().Substring(0, 6) + "@test.com"; + var contactRegistration = new ContactRegistration + { + AgreedToTermsAndConditions = true, + CertificateAuthorityId = "letsencrypt.org", + EmailAddress = contactRegEmail, + ImportedAccountKey = "", + ImportedAccountURI = "", + IsStaging = true + }; + + // Add account + var addAccountRes = await _certifyManager.AddAccount(contactRegistration); + Assert.IsTrue(addAccountRes.IsSuccess, $"Expected account creation to be successful for {contactRegEmail}"); + accountDetails = (await _certifyManager.GetAccountRegistrations()).Find(a => a.Email == contactRegEmail); + Assert.IsNotNull(accountDetails, $"Expected one of the accounts returned by CertifyManager.GetAccountRegistrations() to be for {contactRegEmail}"); + + var (account, certAuthority, acmeProvider) = await _certifyManager.GetAccountAndACMEProvider(accountDetails.StorageKey); + Assert.IsNotNull(account, $"Expected account returned by GetAccountAndACMEProvider() to not be null for storage key {accountDetails.StorageKey}"); + Assert.IsNotNull(certAuthority, $"Expected certAuthority returned by GetAccountAndACMEProvider() to not be null for storage key {accountDetails.StorageKey}"); + Assert.IsNotNull(acmeProvider, $"Expected acmeProvider returned by GetAccountAndACMEProvider() to not be null for storage key {accountDetails.StorageKey}"); + } + finally + { + // Cleanup added account + if (accountDetails != null) + { + await _certifyManager.RemoveAccount(accountDetails.StorageKey, true); + } + } + } + + [TestMethod, Description("Test for using CertifyManager.GetAccountAndACMEProvider() with a bad storage key")] + public async Task TestCertifyManagerGetAccountAndAcmeProviderBadKey() + { + // Attempt to retrieve account with bad storage key + var badStorageKey = "8da1a662-18ed-4787-a0b1-dc36db5a866b"; + var (account, certAuthority, acmeProvider) = await _certifyManager.GetAccountAndACMEProvider(badStorageKey); + Assert.IsNull(account, $"Expected account returned by GetAccountAndACMEProvider() to be null for storage key {badStorageKey}"); + Assert.IsNull(certAuthority, $"Expected certAuthority returned by GetAccountAndACMEProvider() to be null for storage key {badStorageKey}"); + Assert.IsNull(acmeProvider, $"Expected acmeProvider returned by GetAccountAndACMEProvider() to be null for storage key {badStorageKey}"); + } + + [TestMethod, Description("Happy path test for using CertifyManager.UpdateAccountContact()")] + public async Task TestCertifyManagerUpdateAccountContact() + { + // Setup account registration info + var contactRegEmail = "admin." + Guid.NewGuid().ToString().Substring(0, 6) + "@test.com"; + var contactRegistration = new ContactRegistration + { + AgreedToTermsAndConditions = true, + CertificateAuthorityId = "letsencrypt.org", + EmailAddress = contactRegEmail, + ImportedAccountKey = "", + ImportedAccountURI = "", + IsStaging = true + }; + + // Add account + var addAccountRes = await _certifyManager.AddAccount(contactRegistration); + Assert.IsTrue(addAccountRes.IsSuccess, $"Expected account creation to be successful for {contactRegEmail}"); + var accountDetails = (await _certifyManager.GetAccountRegistrations()).Find(a => a.Email == contactRegEmail); + Assert.IsNotNull(accountDetails, $"Expected one of the accounts returned by CertifyManager.GetAccountRegistrations() to be for {contactRegEmail}"); + + // Update account + var newContactRegEmail = "admin." + Guid.NewGuid().ToString().Substring(0, 6) + "@test.com"; + var newContactRegistration = new ContactRegistration + { + AgreedToTermsAndConditions = true, + CertificateAuthorityId = "letsencrypt.org", + EmailAddress = newContactRegEmail, + ImportedAccountKey = "", + ImportedAccountURI = "", + IsStaging = true + }; + var updateAccountRes = await _certifyManager.UpdateAccountContact(accountDetails.StorageKey, newContactRegistration); + Assert.IsTrue(updateAccountRes.IsSuccess, $"Expected account creation to be successful for {newContactRegEmail}"); + accountDetails = (await _certifyManager.GetAccountRegistrations()).Find(a => a.Email == newContactRegEmail); + Assert.IsNotNull(accountDetails, $"Expected one of the accounts returned by CertifyManager.GetAccountRegistrations() to be for {newContactRegEmail}"); + + // Cleanup account + await _certifyManager.RemoveAccount(accountDetails.StorageKey, true); + } + + [TestMethod, Description("Test for using CertifyManager.UpdateAccountContact() when AgreedToTermsAndConditions is false")] + public async Task TestCertifyManagerUpdateAccountContactNoAgreement() + { + // Setup account registration info + var contactRegEmail = "admin." + Guid.NewGuid().ToString().Substring(0, 6) + "@test.com"; + var contactRegistration = new ContactRegistration + { + AgreedToTermsAndConditions = true, + CertificateAuthorityId = "letsencrypt.org", + EmailAddress = contactRegEmail, + ImportedAccountKey = "", + ImportedAccountURI = "", + IsStaging = true + }; + + // Add account + var addAccountRes = await _certifyManager.AddAccount(contactRegistration); + Assert.IsTrue(addAccountRes.IsSuccess, $"Expected account creation to be successful for {contactRegEmail}"); + var accountDetails = (await _certifyManager.GetAccountRegistrations()).Find(a => a.Email == contactRegEmail); + Assert.IsNotNull(accountDetails, $"Expected one of the accounts returned by CertifyManager.GetAccountRegistrations() to be for {contactRegEmail}"); + + // Update account + var newContactRegEmail = "admin." + Guid.NewGuid().ToString().Substring(0, 6) + "@test.com"; + var newContactRegistration = new ContactRegistration + { + AgreedToTermsAndConditions = false, + CertificateAuthorityId = "letsencrypt.org", + EmailAddress = newContactRegEmail, + ImportedAccountKey = "", + ImportedAccountURI = "", + IsStaging = true + }; + var updateAccountRes = await _certifyManager.UpdateAccountContact(accountDetails.StorageKey, newContactRegistration); + Assert.IsFalse(updateAccountRes.IsSuccess, $"Expected account creation to not be successful for {newContactRegEmail}"); + Assert.AreEqual(updateAccountRes.Message, "You must agree to the terms and conditions of the Certificate Authority to register with them.", "Unexpected error message"); + var newAccountDetails = (await _certifyManager.GetAccountRegistrations()).Find(a => a.Email == newContactRegEmail); + Assert.IsNull(newAccountDetails, $"Expected none of the accounts returned by CertifyManager.GetAccountRegistrations() to be for {newContactRegEmail}"); + accountDetails = (await _certifyManager.GetAccountRegistrations()).Find(a => a.Email == contactRegEmail); + Assert.IsNotNull(accountDetails, $"Expected one of the accounts returned by CertifyManager.GetAccountRegistrations() to be for {contactRegEmail}"); + + // Cleanup account + await _certifyManager.RemoveAccount(accountDetails.StorageKey, true); + } + + [TestMethod, Description("Test for using CertifyManager.UpdateAccountContact() when passed storage key doesn't exist")] + public async Task TestCertifyManagerUpdateAccountContactBadKey() + { + // Setup account registration info + var contactRegEmail = "admin." + Guid.NewGuid().ToString().Substring(0, 6) + "@test.com"; + var contactRegistration = new ContactRegistration + { + AgreedToTermsAndConditions = true, + CertificateAuthorityId = "letsencrypt.org", + EmailAddress = contactRegEmail, + ImportedAccountKey = "", + ImportedAccountURI = "", + IsStaging = true + }; + + // Add account + var addAccountRes = await _certifyManager.AddAccount(contactRegistration); + Assert.IsTrue(addAccountRes.IsSuccess, $"Expected account creation to be successful for {contactRegEmail}"); + var accountDetails = (await _certifyManager.GetAccountRegistrations()).Find(a => a.Email == contactRegEmail); + Assert.IsNotNull(accountDetails, $"Expected one of the accounts returned by CertifyManager.GetAccountRegistrations() to be for {contactRegEmail}"); + + // Update account + var newContactRegEmail = "admin." + Guid.NewGuid().ToString().Substring(0, 6) + "@test.com"; + var newContactRegistration = new ContactRegistration + { + AgreedToTermsAndConditions = true, + CertificateAuthorityId = "letsencrypt.org", + EmailAddress = newContactRegEmail, + ImportedAccountKey = "", + ImportedAccountURI = "", + IsStaging = true + }; + var badStorageKey = Guid.NewGuid().ToString(); + var updateAccountRes = await _certifyManager.UpdateAccountContact(badStorageKey, newContactRegistration); + Assert.IsFalse(updateAccountRes.IsSuccess, $"Expected account creation to not be successful for {newContactRegEmail}"); + Assert.AreEqual(updateAccountRes.Message, "Account not found.", "Unexpected error message"); + var newAccountDetails = (await _certifyManager.GetAccountRegistrations()).Find(a => a.Email == newContactRegEmail); + Assert.IsNull(newAccountDetails, $"Expected none of the accounts returned by CertifyManager.GetAccountRegistrations() to be for {newContactRegEmail}"); + accountDetails = (await _certifyManager.GetAccountRegistrations()).Find(a => a.Email == contactRegEmail); + Assert.IsNotNull(accountDetails, $"Expected one of the accounts returned by CertifyManager.GetAccountRegistrations() to be for {contactRegEmail}"); + + // Cleanup account + await _certifyManager.RemoveAccount(accountDetails.StorageKey, true); + } + + [TestMethod, Description("Happy path test for using CertifyManager.ChangeAccountKey()")] + public async Task TestCertifyManagerChangeAccountKey() + { + // Setup account registration info + var contactRegEmail = "admin." + Guid.NewGuid().ToString().Substring(0, 6) + "@test.com"; + var contactRegistration = new ContactRegistration + { + AgreedToTermsAndConditions = true, + CertificateAuthorityId = "letsencrypt.org", + EmailAddress = contactRegEmail, + ImportedAccountKey = "", + ImportedAccountURI = "", + IsStaging = true + }; + + // Add account + var addAccountRes = await _certifyManager.AddAccount(contactRegistration); + Assert.IsTrue(addAccountRes.IsSuccess, $"Expected account creation to be successful for {contactRegEmail}"); + var accountDetails = (await _certifyManager.GetAccountRegistrations()).Find(a => a.Email == contactRegEmail); + Assert.IsNotNull(accountDetails, $"Expected one of the accounts returned by CertifyManager.GetAccountRegistrations() to be for {contactRegEmail}"); + var firstAccountKey = accountDetails.AccountKey; + + // Update account key + var newKeyPem = KeyFactory.NewKey(KeyAlgorithm.ES256).ToPem(); + var changeAccountKeyRes = await _certifyManager.ChangeAccountKey(accountDetails.StorageKey, newKeyPem); + Assert.IsTrue(changeAccountKeyRes.IsSuccess, $"Expected account creation to be successful for {contactRegEmail}"); + Assert.AreEqual(changeAccountKeyRes.Message, "Completed account key rollover", "Unexpected message for CertifyManager.GetAccountRegistrations() success"); + accountDetails = (await _certifyManager.GetAccountRegistrations()).Find(a => a.Email == contactRegEmail); + Assert.IsNotNull(accountDetails, $"Expected one of the accounts returned by CertifyManager.GetAccountRegistrations() to be for {contactRegEmail}"); + Assert.AreNotEqual(firstAccountKey, accountDetails.AccountKey, $"Expected account key for {contactRegEmail} to have changed after successful CertifyManager.ChangeAccountKey()"); + + // Cleanup account + await _certifyManager.RemoveAccount(accountDetails.StorageKey, true); + } + + [TestMethod, Description("Happy path test for using CertifyManager.ChangeAccountKey() with no passed in new account key")] + public async Task TestCertifyManagerChangeAccountKeyNull() + { + // Setup account registration info + var contactRegEmail = "admin." + Guid.NewGuid().ToString().Substring(0, 6) + "@test.com"; + var contactRegistration = new ContactRegistration + { + AgreedToTermsAndConditions = true, + CertificateAuthorityId = "letsencrypt.org", + EmailAddress = contactRegEmail, + ImportedAccountKey = "", + ImportedAccountURI = "", + IsStaging = true + }; + + // Add account + var addAccountRes = await _certifyManager.AddAccount(contactRegistration); + Assert.IsTrue(addAccountRes.IsSuccess, $"Expected account creation to be successful for {contactRegEmail}"); + var accountDetails = (await _certifyManager.GetAccountRegistrations()).Find(a => a.Email == contactRegEmail); + Assert.IsNotNull(accountDetails, $"Expected one of the accounts returned by CertifyManager.GetAccountRegistrations() to be for {contactRegEmail}"); + var firstAccountKey = accountDetails.AccountKey; + + // Update account key + var changeAccountKeyRes = await _certifyManager.ChangeAccountKey(accountDetails.StorageKey); + Assert.IsTrue(changeAccountKeyRes.IsSuccess, $"Expected account creation to be successful for {contactRegEmail}"); + Assert.AreEqual(changeAccountKeyRes.Message, "Completed account key rollover", "Unexpected message for CertifyManager.GetAccountRegistrations() success"); + accountDetails = (await _certifyManager.GetAccountRegistrations()).Find(a => a.Email == contactRegEmail); + Assert.IsNotNull(accountDetails, $"Expected one of the accounts returned by CertifyManager.GetAccountRegistrations() to be for {contactRegEmail}"); + Assert.AreNotEqual(firstAccountKey, accountDetails.AccountKey, $"Expected account key for {contactRegEmail} to have changed after successful CertifyManager.ChangeAccountKey()"); + + // Cleanup account + await _certifyManager.RemoveAccount(accountDetails.StorageKey, true); + } + + [TestMethod, Description("Test for using CertifyManager.ChangeAccountKey() when passed an invalid storage key")] + public async Task TestCertifyManagerChangeAccountKeyBadStorageKey() + { + // Setup account registration info + var contactRegEmail = "admin." + Guid.NewGuid().ToString().Substring(0, 6) + "@test.com"; + var contactRegistration = new ContactRegistration + { + AgreedToTermsAndConditions = true, + CertificateAuthorityId = "letsencrypt.org", + EmailAddress = contactRegEmail, + ImportedAccountKey = "", + ImportedAccountURI = "", + IsStaging = true + }; + + // Add account + var addAccountRes = await _certifyManager.AddAccount(contactRegistration); + Assert.IsTrue(addAccountRes.IsSuccess, $"Expected account key update to be successful for {contactRegEmail}"); + var accountDetails = (await _certifyManager.GetAccountRegistrations()).Find(a => a.Email == contactRegEmail); + Assert.IsNotNull(accountDetails, $"Expected one of the accounts returned by CertifyManager.GetAccountRegistrations() to be for {contactRegEmail}"); + var firstAccountKey = accountDetails.AccountKey; + + // Attempt to update account key + var newKeyPem = KeyFactory.NewKey(KeyAlgorithm.ES256).ToPem(); + var badStorageKey = Guid.NewGuid().ToString(); + var changeAccountKeyRes = await _certifyManager.ChangeAccountKey(badStorageKey, newKeyPem); + Assert.IsFalse(changeAccountKeyRes.IsSuccess, $"Expected account key update to be unsuccessful for {contactRegEmail}"); + Assert.AreEqual(changeAccountKeyRes.Message, "Failed to match account to known ACME provider", "Unexpected error message for CertifyManager.GetAccountRegistrations() failure"); + accountDetails = (await _certifyManager.GetAccountRegistrations()).Find(a => a.Email == contactRegEmail); + Assert.IsNotNull(accountDetails, $"Expected one of the accounts returned by CertifyManager.GetAccountRegistrations() to be for {contactRegEmail}"); + Assert.AreEqual(firstAccountKey, accountDetails.AccountKey, $"Expected account key for {contactRegEmail} not to have changed after unsuccessful CertifyManager.ChangeAccountKey()"); + + // Cleanup account + await _certifyManager.RemoveAccount(accountDetails.StorageKey, true); + } + + [TestMethod, Description("Test for using CertifyManager.ChangeAccountKey() when passed an invalid new account key")] + public async Task TestCertifyManagerChangeAccountKeyBadAccountKey() + { + // Setup account registration info + var contactRegEmail = "admin." + Guid.NewGuid().ToString().Substring(0, 6) + "@test.com"; + var contactRegistration = new ContactRegistration + { + AgreedToTermsAndConditions = true, + CertificateAuthorityId = "letsencrypt.org", + EmailAddress = contactRegEmail, + ImportedAccountKey = "", + ImportedAccountURI = "", + IsStaging = true + }; + + // Add account + var addAccountRes = await _certifyManager.AddAccount(contactRegistration); + Assert.IsTrue(addAccountRes.IsSuccess, $"Expected account key update to be successful for {contactRegEmail}"); + var accountDetails = (await _certifyManager.GetAccountRegistrations()).Find(a => a.Email == contactRegEmail); + Assert.IsNotNull(accountDetails, $"Expected one of the accounts returned by CertifyManager.GetAccountRegistrations() to be for {contactRegEmail}"); + var firstAccountKey = accountDetails.AccountKey; + + // Attempt to update account key + var badKeyPem = KeyFactory.NewKey(KeyAlgorithm.ES256).ToPem().Substring(20); + var changeAccountKeyRes = await _certifyManager.ChangeAccountKey(accountDetails.StorageKey, badKeyPem); + Assert.IsFalse(changeAccountKeyRes.IsSuccess, $"Expected account key update to be unsuccessful for {contactRegEmail}"); + Assert.AreEqual(changeAccountKeyRes.Message, "Failed to use provide key for account rollover", "Unexpected error message for CertifyManager.GetAccountRegistrations() failure"); + accountDetails = (await _certifyManager.GetAccountRegistrations()).Find(a => a.Email == contactRegEmail); + Assert.IsNotNull(accountDetails, $"Expected one of the accounts returned by CertifyManager.GetAccountRegistrations() to be for {contactRegEmail}"); + Assert.AreEqual(firstAccountKey, accountDetails.AccountKey, $"Expected account key for {contactRegEmail} not to have changed after unsuccessful CertifyManager.ChangeAccountKey()"); + + // Cleanup account + await _certifyManager.RemoveAccount(accountDetails.StorageKey, true); + } + + [TestMethod, Description("Happy path test for using CertifyManager.UpdateCertificateAuthority() to add a new custom CA")] + public async Task TestCertifyManagerUpdateCertificateAuthorityAdd() + { + CertificateAuthority newCustomCa = null; + try + { + newCustomCa = new CertificateAuthority + { + Id = Guid.NewGuid().ToString(), + Title = "Test Custom CA", + IsCustom = true, + IsEnabled = true, + SupportedFeatures = new List + { + CertAuthoritySupportedRequests.DOMAIN_SINGLE.ToString(), + } + }; + var updateCaRes = await _certifyManager.UpdateCertificateAuthority(newCustomCa); + Assert.IsTrue(updateCaRes.IsSuccess, $"Expected Custom CA creation for CA with ID {newCustomCa.Id} to be successful"); + Assert.AreEqual(updateCaRes.Message, "OK", "Unexpected result message for CertifyManager.UpdateCertificateAuthority() success"); + var certificateAuthorities = await _certifyManager.GetCertificateAuthorities(); + var newCaDetails = certificateAuthorities.Find(c => c.Id == newCustomCa.Id); + Assert.IsNotNull(newCaDetails, $"Expected one of the CAs returned by CertifyManager.GetCertificateAuthorities() to have an ID of {newCustomCa.Id}"); + } + finally + { + if (newCustomCa != null) + { + await _certifyManager.RemoveCertificateAuthority(newCustomCa.Id); + } + } + } + + [TestMethod, Description("Happy path test for using CertifyManager.UpdateCertificateAuthority() to update an existing custom CA")] + public async Task TestCertifyManagerUpdateCertificateAuthorityUpdate() + { + CertificateAuthority newCustomCa = null; + try + { + newCustomCa = new CertificateAuthority + { + Id = Guid.NewGuid().ToString(), + Title = "Test Custom CA", + IsCustom = true, + IsEnabled = true, + AllowInternalHostnames = false, + SupportedFeatures = new List + { + CertAuthoritySupportedRequests.DOMAIN_SINGLE.ToString(), + } + }; + + // Add new CA + var addCaRes = await _certifyManager.UpdateCertificateAuthority(newCustomCa); + Assert.IsTrue(addCaRes.IsSuccess, $"Expected Custom CA creation for CA with ID {newCustomCa.Id} to be successful"); + Assert.AreEqual(addCaRes.Message, "OK", "Unexpected result message for CertifyManager.UpdateCertificateAuthority() success"); + var certificateAuthorities = await _certifyManager.GetCertificateAuthorities(); + var newCaDetails = certificateAuthorities.Find(c => c.Id == newCustomCa.Id); + Assert.IsNotNull(newCaDetails, $"Expected one of the CAs returned by CertifyManager.GetCertificateAuthorities() to have an ID of {newCustomCa.Id}"); + Assert.IsFalse(newCaDetails.AllowInternalHostnames); + + var updatedCustomCa = new CertificateAuthority + { + Id = newCustomCa.Id, + Title = "Test Custom CA", + IsCustom = true, + IsEnabled = true, + AllowInternalHostnames = true, + SupportedFeatures = new List + { + CertAuthoritySupportedRequests.DOMAIN_SINGLE.ToString(), + } + }; + + // Update existing CA + var updateCaRes = await _certifyManager.UpdateCertificateAuthority(updatedCustomCa); + Assert.IsTrue(updateCaRes.IsSuccess, $"Expected Custom CA update for CA with ID {updatedCustomCa.Id} to be successful"); + Assert.AreEqual(updateCaRes.Message, "OK", "Unexpected result message for CertifyManager.UpdateCertificateAuthority() success"); + certificateAuthorities = await _certifyManager.GetCertificateAuthorities(); + newCaDetails = certificateAuthorities.Find(c => c.Id == updatedCustomCa.Id); + Assert.IsNotNull(newCaDetails, $"Expected one of the CAs returned by CertifyManager.GetCertificateAuthorities() to have an ID of {updatedCustomCa.Id}"); + Assert.IsTrue(newCaDetails.AllowInternalHostnames); + } + finally + { + if (newCustomCa != null) + { + await _certifyManager.RemoveCertificateAuthority(newCustomCa.Id); + } + } + } + + [TestMethod, Description("Test for using CertifyManager.UpdateCertificateAuthority() on a default CA")] + public async Task TestCertifyManagerUpdateCertificateAuthorityDefaultCa() + { + var certificateAuthorities = await _certifyManager.GetCertificateAuthorities(); + var defaultCa = certificateAuthorities.First(); + var newCustomCa = new CertificateAuthority + { + Id = defaultCa.Id, + Title = "Test Custom CA", + IsCustom = true, + IsEnabled = true, + AllowInternalHostnames = false, + SupportedFeatures = new List + { + CertAuthoritySupportedRequests.DOMAIN_SINGLE.ToString(), + } + }; + + // Attempt to update default CA + var updateCaRes = await _certifyManager.UpdateCertificateAuthority(newCustomCa); + Assert.IsFalse(updateCaRes.IsSuccess, $"Expected CA update for default CA with ID {defaultCa.Id} to be unsuccessful"); + Assert.AreEqual(updateCaRes.Message, "Default Certificate Authorities cannot be modified.", "Unexpected result message for CertifyManager.UpdateCertificateAuthority() failure"); + } + + [TestMethod, Description("Happy path test for using CertifyManager.RemoveCertificateAuthority()")] + public async Task TestCertifyManagerRemoveCertificateAuthority() + { + var newCustomCa = new CertificateAuthority + { + Id = Guid.NewGuid().ToString(), + Title = "Test Custom CA", + IsCustom = true, + IsEnabled = true, + SupportedFeatures = new List + { + CertAuthoritySupportedRequests.DOMAIN_SINGLE.ToString(), + } + }; + + // Add custom CA + var updateCaRes = await _certifyManager.UpdateCertificateAuthority(newCustomCa); + Assert.IsTrue(updateCaRes.IsSuccess, $"Expected Custom CA creation for CA with ID {newCustomCa.Id} to be successful"); + Assert.AreEqual(updateCaRes.Message, "OK", "Unexpected result message for CertifyManager.UpdateCertificateAuthority() success"); + var certificateAuthorities = await _certifyManager.GetCertificateAuthorities(); + var newCaDetails = certificateAuthorities.Find(c => c.Id == newCustomCa.Id); + Assert.IsNotNull(newCaDetails, $"Expected one of the CAs returned by CertifyManager.GetCertificateAuthorities() to have an ID of {newCustomCa.Id}"); + + // Delete custom CA + var deleteCaRes = await _certifyManager.RemoveCertificateAuthority(newCustomCa.Id); + Assert.IsTrue(deleteCaRes.IsSuccess, $"Expected Custom CA deletion for CA with ID {newCustomCa.Id} to be successful"); + Assert.AreEqual(deleteCaRes.Message, "OK", "Unexpected result message for CertifyManager.RemoveCertificateAuthority() success"); + certificateAuthorities = await _certifyManager.GetCertificateAuthorities(); + newCaDetails = certificateAuthorities.Find(c => c.Id == newCustomCa.Id); + Assert.IsNull(newCaDetails, $"Expected none of the CAs returned by CertifyManager.GetCertificateAuthorities() to have an ID of {newCustomCa.Id}"); + } + + [TestMethod, Description("Test for using CertifyManager.RemoveCertificateAuthority() when passed a bad custom CA ID")] + public async Task TestCertifyManagerRemoveCertificateAuthorityBadId() + { + var badId = Guid.NewGuid().ToString(); + + // Delete custom CA + var deleteCaRes = await _certifyManager.RemoveCertificateAuthority(badId); + Assert.IsFalse(deleteCaRes.IsSuccess, $"Expected Custom CA deletion for CA with ID {badId} to be unsuccessful"); + Assert.AreEqual(deleteCaRes.Message, "An error occurred removing the indicated Custom CA from the Certificate Authorities list.", "Unexpected result message for CertifyManager.RemoveCertificateAuthority() failure"); + } + } +} From 5803c44aa8d07251f0b8c8731f9b2b2d4ddd93b9 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 2 Nov 2023 11:59:57 +0800 Subject: [PATCH 074/328] Automated code cleanup --- .../AccessControlTests.cs | 8 ++++---- .../BindingMatchTests.cs | 5 ++--- .../CAFailoverTests.cs | 20 ++++++++++++------- .../CertifyManagerAccountTests.cs | 20 +++++++++---------- .../GetDnsProviderTests.cs | 2 +- 5 files changed, 30 insertions(+), 25 deletions(-) diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/AccessControlTests.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/AccessControlTests.cs index 66c6bb8bf..b4bf9786f 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/AccessControlTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/AccessControlTests.cs @@ -55,7 +55,7 @@ public Task Update(string itemType, T item) _store.TryGetValue(o.Id, out var value); var c = Task.FromResult((T)Convert.ChangeType(value, typeof(T))).Result as AccessStoreItem; var r = Task.FromResult(_store.TryUpdate(o.Id, o, c)); - if(r.Result == false) + if (r.Result == false) { throw new Exception("Could not store item type"); } @@ -96,8 +96,8 @@ public class TestAssignedRoles public class TestSecurityPrinciples { - public static SecurityPrinciple TestAdmin() - { + public static SecurityPrinciple TestAdmin() + { return new SecurityPrinciple { Id = "[test]", @@ -106,7 +106,7 @@ public static SecurityPrinciple TestAdmin() Email = "test_admin@test.com", Password = "ABCDEFG", PrincipleType = SecurityPrincipleType.User - }; + }; } public static SecurityPrinciple Admin() { diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/BindingMatchTests.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/BindingMatchTests.cs index 8b45ca4cc..b492f8874 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/BindingMatchTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/BindingMatchTests.cs @@ -9,7 +9,6 @@ using Certify.Management; using Certify.Models; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Certify.Management; namespace Certify.Core.Tests.Unit { @@ -527,7 +526,7 @@ public async Task MixedIPBindingChecksBadPfxPath() var mockTarget = new MockBindingDeploymentTarget(); mockTarget.AllBindings = bindings; - await Assert.ThrowsExceptionAsync(async() => await deployment.StoreAndDeploy(mockTarget, testManagedCert, dummyCertPath, pfxPwd: "", false, Certify.Management.CertificateManager.DEFAULT_STORE_NAME)); + await Assert.ThrowsExceptionAsync(async () => await deployment.StoreAndDeploy(mockTarget, testManagedCert, dummyCertPath, pfxPwd: "", false, Certify.Management.CertificateManager.DEFAULT_STORE_NAME)); } [TestMethod, Description("Test if mixed ipv4+ipv6 bindings are handled when given a bad pfx file")] @@ -667,7 +666,7 @@ public async Task MixedIPBindingChecksBadCertStoreName() var results = await deployment.StoreAndDeploy(mockTarget, testManagedCert, dummyCertPath, pfxPwd: "", false, "BadCertStoreName"); Assert.AreEqual(results.Count, 1); - Assert.IsTrue(results[0].HasError); + Assert.IsTrue(results[0].HasError); Assert.AreEqual(results[0].Category, "CertificateStorage"); Assert.IsTrue(results[0].Description.Contains("Error storing certificate. The system cannot find the file specified.")); Assert.AreEqual(results[0].Title, "Certificate Storage Failed"); diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/CAFailoverTests.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/CAFailoverTests.cs index da80b9e06..c163e6d27 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/CAFailoverTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/CAFailoverTests.cs @@ -1,11 +1,9 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Linq; using Certify.Management; using Certify.Models; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Newtonsoft.Json; namespace Certify.Core.Tests.Unit { @@ -213,7 +211,7 @@ public void TestBasicNoFallbacks() public void TestBasicNextFallbackNull() { // setup - var accounts = GetTestAccounts().FindAll(a => a.ID != "letsreluctantlyfallback_ABC234_staging"); + var accounts = GetTestAccounts().FindAll(a => a.ID != "letsreluctantlyfallback_ABC234_staging"); var caList = GetTestCAs(); var managedCertificate = GetBasicManagedCertificate(RequestState.Error, 3, lastCA: "letsfallback"); @@ -221,8 +219,14 @@ public void TestBasicNextFallbackNull() // perform check var defaultCAAccount = accounts.FirstOrDefault(a => a.CertificateAuthorityId == DEFAULTCA && a.IsStagingAccount == managedCertificate.UseStagingMode); - accounts.Add(new AccountDetails { ID = "letsfallback_ABC234_staging_isfailover", IsStagingAccount = true, IsFailoverSelection = true, - CertificateAuthorityId = "letsfallback", Title = "A fallback account with is failover" }); + accounts.Add(new AccountDetails + { + ID = "letsfallback_ABC234_staging_isfailover", + IsStagingAccount = true, + IsFailoverSelection = true, + CertificateAuthorityId = "letsfallback", + Title = "A fallback account with is failover" + }); var selectedAccount = RenewalManager.SelectCAWithFailover(caList, accounts, managedCertificate, defaultCAAccount); @@ -238,7 +242,7 @@ public void TestBasicFailoverOccursWildcardDomainCA() var accounts = GetTestAccounts(); var caList = GetTestCAs(); - var managedCertificate = GetBasicManagedCertificate(RequestState.Error, 3, lastCA: DEFAULTCA, + var managedCertificate = GetBasicManagedCertificate(RequestState.Error, 3, lastCA: DEFAULTCA, new CertRequestConfig { SubjectAlternativeNames = new List { "test.com", "anothertest.com", "www.test.com", "*.wildtest.com" }.ToArray() }); // perform check @@ -319,7 +323,9 @@ public void TestBasicFailoverOccursOptionalLifetimeDays() var caList = GetTestCAs(); var managedCertificate = GetBasicManagedCertificate(RequestState.Error, 3, lastCA: DEFAULTCA, - new CertRequestConfig { SubjectAlternativeNames = new List { "test.com", "anothertest.com", "www.test.com", "*.wildtest.com" }.ToArray(), + new CertRequestConfig + { + SubjectAlternativeNames = new List { "test.com", "anothertest.com", "www.test.com", "*.wildtest.com" }.ToArray(), PreferredExpiryDays = 7, }); diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/CertifyManagerAccountTests.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/CertifyManagerAccountTests.cs index a0a75fa02..6f59667d3 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/CertifyManagerAccountTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/CertifyManagerAccountTests.cs @@ -57,7 +57,7 @@ public async Task TestCertifyManagerGetAccountDetailsDefinedCertificateAuthority Assert.IsNotNull(caAccount, "Expected result of CertifyManager.GetAccountDetails() to not be null"); Assert.AreEqual("letsencrypt.org", caAccount.CertificateAuthorityId, $"Unexpected certificate authority id '{caAccount.CertificateAuthorityId}'"); } - + [TestMethod, Description("Test for using CertifyManager.GetAccountDetails() when OverrideAccountDetails is defined in CertifyManager")] public async Task TestCertifyManagerGetAccountDetailsDefinedOverrideAccountDetails() { @@ -116,7 +116,7 @@ public async Task TestCertifyManagerAddAccount() try { // Setup account registration info - var contactRegEmail = "admin."+ Guid.NewGuid().ToString().Substring(0, 6) + "@test.com"; + var contactRegEmail = "admin." + Guid.NewGuid().ToString().Substring(0, 6) + "@test.com"; var contactRegistration = new ContactRegistration { AgreedToTermsAndConditions = true, @@ -126,7 +126,7 @@ public async Task TestCertifyManagerAddAccount() ImportedAccountURI = "", IsStaging = true }; - + // Add account var addAccountRes = await _certifyManager.AddAccount(contactRegistration); Assert.IsTrue(addAccountRes.IsSuccess, $"Expected account creation to be successful for {contactRegEmail}"); @@ -142,12 +142,12 @@ public async Task TestCertifyManagerAddAccount() } } } - + [TestMethod, Description("Happy path test for using CertifyManager.RemoveAccount()")] public async Task TestCertifyManagerRemoveAccount() { // Setup account registration info - var contactRegEmail = "admin."+ Guid.NewGuid().ToString().Substring(0, 6) + "@test.com"; + var contactRegEmail = "admin." + Guid.NewGuid().ToString().Substring(0, 6) + "@test.com"; var contactRegistration = new ContactRegistration { AgreedToTermsAndConditions = true, @@ -157,7 +157,7 @@ public async Task TestCertifyManagerRemoveAccount() ImportedAccountURI = "", IsStaging = true }; - + // Add account var addAccountRes = await _certifyManager.AddAccount(contactRegistration); Assert.IsTrue(addAccountRes.IsSuccess, $"Expected account creation to be successful for {contactRegEmail}"); @@ -696,7 +696,7 @@ public async Task TestCertifyManagerUpdateCertificateAuthorityUpdate() CertAuthoritySupportedRequests.DOMAIN_SINGLE.ToString(), } }; - + // Add new CA var addCaRes = await _certifyManager.UpdateCertificateAuthority(newCustomCa); Assert.IsTrue(addCaRes.IsSuccess, $"Expected Custom CA creation for CA with ID {newCustomCa.Id} to be successful"); @@ -718,7 +718,7 @@ public async Task TestCertifyManagerUpdateCertificateAuthorityUpdate() CertAuthoritySupportedRequests.DOMAIN_SINGLE.ToString(), } }; - + // Update existing CA var updateCaRes = await _certifyManager.UpdateCertificateAuthority(updatedCustomCa); Assert.IsTrue(updateCaRes.IsSuccess, $"Expected Custom CA update for CA with ID {updatedCustomCa.Id} to be successful"); @@ -783,9 +783,9 @@ public async Task TestCertifyManagerRemoveCertificateAuthority() var certificateAuthorities = await _certifyManager.GetCertificateAuthorities(); var newCaDetails = certificateAuthorities.Find(c => c.Id == newCustomCa.Id); Assert.IsNotNull(newCaDetails, $"Expected one of the CAs returned by CertifyManager.GetCertificateAuthorities() to have an ID of {newCustomCa.Id}"); - + // Delete custom CA - var deleteCaRes = await _certifyManager.RemoveCertificateAuthority(newCustomCa.Id); + var deleteCaRes = await _certifyManager.RemoveCertificateAuthority(newCustomCa.Id); Assert.IsTrue(deleteCaRes.IsSuccess, $"Expected Custom CA deletion for CA with ID {newCustomCa.Id} to be successful"); Assert.AreEqual(deleteCaRes.Message, "OK", "Unexpected result message for CertifyManager.RemoveCertificateAuthority() success"); certificateAuthorities = await _certifyManager.GetCertificateAuthorities(); diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/GetDnsProviderTests.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/GetDnsProviderTests.cs index 4dcfc9c55..cc5e74f9d 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/GetDnsProviderTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/GetDnsProviderTests.cs @@ -14,7 +14,7 @@ public class GetDnsProviderTests private SQLiteCredentialStore credentialsManager; private DnsChallengeHelper dnsHelper; - public GetDnsProviderTests() + public GetDnsProviderTests() { var pluginManager = new PluginManager(); pluginManager.LoadPlugins(new List { PluginManager.PLUGINS_DNS_PROVIDERS }); From 2044a0cb8ab552f7440377368350bce6a0a2746e Mon Sep 17 00:00:00 2001 From: Justin Nelson Date: Thu, 2 Nov 2023 13:07:36 -0700 Subject: [PATCH 075/328] Added Integration Tests for CertifyManager.ServerType partial class Adds full line coverage for the file CertifyManager.ServerType.cs Will require changes once an Apache plugin is created --- docs/testing.md | 2 + .../CertifyManagerServerTypeTests.cs | 280 ++++++++++++++++++ 2 files changed, 282 insertions(+) create mode 100644 src/Certify.Tests/Certify.Core.Tests.Integration/CertifyManagerServerTypeTests.cs diff --git a/docs/testing.md b/docs/testing.md index 2706964c8..0f1a666e9 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -10,6 +10,8 @@ Testing Configuration RESTORE_ACCOUNT_EMAIL=admin.8c635b@test.com - Integration tests exercise multiple components and may interact with ACME services etc. Required elements include: - IIS Installed on local machine + * A non-enabled site in IIS is needed for TestCertifyManagerGetPrimaryWebSitesIncludeStoppedSites() in CertifyManagerServerTypeTests.cs + - Must set IncludeExternalPlugins to true in C:\ProgramData\certify\appsettings.json and run copy-plugins.bat from certify-internal - The debug version of the app must be configured with a contact against staging Let's Encrypt servers - Completing HTTP challenges requires that the machine can respond to port 80 requests from the internet (such as the Let's Encrypt staging server checks) - DNS API Credentials test and DNS Challenges require the respective DNS credentials by configured as saved credentials in the UI (see config below) diff --git a/src/Certify.Tests/Certify.Core.Tests.Integration/CertifyManagerServerTypeTests.cs b/src/Certify.Tests/Certify.Core.Tests.Integration/CertifyManagerServerTypeTests.cs new file mode 100644 index 000000000..74b62fe62 --- /dev/null +++ b/src/Certify.Tests/Certify.Core.Tests.Integration/CertifyManagerServerTypeTests.cs @@ -0,0 +1,280 @@ +using System; +using System.Threading.Tasks; +using Certify.Management; +using Certify.Management.Servers; +using Certify.Models; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Certify.Core.Tests +{ + [TestClass] + public class CertifyManagerServerTypeTests : IntegrationTestBase, IDisposable + { + private readonly CertifyManager _certifyManager; + private readonly ServerProviderIIS _iisManager; + private readonly string _testSiteName = "Test1ServerTypes"; + private readonly string _testSiteDomain = "integration1.anothertest.com"; + private readonly string _testSiteIp = "192.168.68.20"; + private readonly int _testSiteHttpPort = 80; + private string _testSiteId = ""; + + public CertifyManagerServerTypeTests() + { + // Must set IncludeExternalPlugins to true in C:\ProgramData\certify\appsettings.json and run copy-plugins.bat from certify-internal + _certifyManager = new CertifyManager(); + _certifyManager.Init().Wait(); + + _iisManager = new ServerProviderIIS(); + SetupIIS().Wait(); + } + + public void Dispose() => TeardownIIS().Wait(); + + public async Task SetupIIS() + { + if (await _iisManager.SiteExists(_testSiteName)) + { + await _iisManager.DeleteSite(_testSiteName); + } + + var site = await _iisManager.CreateSite(_testSiteName, _testSiteDomain, _primaryWebRoot, "DefaultAppPool", ipAddress: _testSiteIp, port: _testSiteHttpPort); + Assert.IsTrue(await _iisManager.SiteExists(_testSiteName)); + _testSiteId = site.Id.ToString(); + } + + public async Task TeardownIIS() + { + await _iisManager.DeleteSite(_testSiteName); + Assert.IsFalse(await _iisManager.SiteExists(_testSiteName)); + } + + [TestMethod, Description("Happy path test for using CertifyManager.GetPrimaryWebSites() for IIS")] + public async Task TestCertifyManagerGetPrimaryWebSitesIIS() + { + // Request websites from CertifyManager.GetPrimaryWebSites() for IIS + var primaryWebsites = await _certifyManager.GetPrimaryWebSites(StandardServerTypes.IIS, true); + + // Evaluate return from CertifyManager.GetPrimaryWebSites() for IIS + Assert.IsNotNull(primaryWebsites, "Expected website list returned by CertifyManager.GetPrimaryWebSites() for IIS sites to not be null"); + Assert.IsTrue(primaryWebsites.Count > 0, "Expected website list returned by CertifyManager.GetPrimaryWebSites() for IIS sites to not be empty"); + Assert.IsTrue(primaryWebsites.Exists(s => s.IsEnabled), "Expected website list returned by CertifyManager.GetPrimaryWebSites() for IIS sites to have at least one enabled site"); + } + + [TestMethod, Description("Happy path test for using CertifyManager.GetPrimaryWebSites() for Apache")] + [Ignore] + public async Task TestCertifyManagerGetPrimaryWebSitesApache() + { + // TODO: Support for Apache via plugin must be added + // This test requires at least one website in Apache to be active + var primaryWebsites = await _certifyManager.GetPrimaryWebSites(StandardServerTypes.Apache, true); + + // Evaluate return from CertifyManager.GetPrimaryWebSites() for Apache + Assert.IsNotNull(primaryWebsites, "Expected website list returned by CertifyManager.GetPrimaryWebSites() for Apache sites to not be null"); + Assert.IsTrue(primaryWebsites.Count > 0, "Expected website list returned by CertifyManager.GetPrimaryWebSites() for Apache sites to not be empty"); + Assert.IsTrue(primaryWebsites.Exists(s => s.IsEnabled), "Expected website list returned by CertifyManager.GetPrimaryWebSites() for Apache sites to have at least one enabled site"); + } + + [TestMethod, Description("Happy path test for using CertifyManager.GetPrimaryWebSites() for Nginx")] + public async Task TestCertifyManagerGetPrimaryWebSitesNginx() + { + // This test requires at least one website in Nginx conf to be defined + var primaryWebsites = await _certifyManager.GetPrimaryWebSites(StandardServerTypes.Nginx, true); + + // Evaluate return from CertifyManager.GetPrimaryWebSites() for Nginx + Assert.IsNotNull(primaryWebsites, "Expected website list returned by CertifyManager.GetPrimaryWebSites() for Nginx sites to not be null"); + Assert.IsTrue(primaryWebsites.Count > 0, "Expected website list returned by CertifyManager.GetPrimaryWebSites() to not be empty"); + Assert.IsTrue(primaryWebsites.Exists(s => s.IsEnabled), "Expected website list returned by CertifyManager.GetPrimaryWebSites() for Nginx sites to have at least one enabled site"); + } + + [TestMethod, Description("Happy path test for using CertifyManager.GetPrimaryWebSites() for IIS using an item id")] + public async Task TestCertifyManagerGetPrimaryWebSitesItemId() + { + // Request website info from CertifyManager.GetPrimaryWebSites() for IIS using Item ID + var itemIdWebsite = await _certifyManager.GetPrimaryWebSites(StandardServerTypes.IIS, true, _testSiteId); + + // Evaluate return from CertifyManager.GetPrimaryWebSites() for IIS using item id + Assert.IsNotNull(itemIdWebsite, "Expected website list returned by CertifyManager.GetPrimaryWebSites() for IIS sites to not be null"); + Assert.AreEqual(1, itemIdWebsite.Count, "Expected website list returned by CertifyManager.GetPrimaryWebSites() for IIS sites to not be empty"); + Assert.AreEqual(_testSiteId, itemIdWebsite[0].Id, "Expected the same Item Id for SiteInfo objects returned by CertifyManager.GetPrimaryWebSites() for IIS sites"); + Assert.AreEqual(_testSiteName, itemIdWebsite[0].Name, "Expected the same Name for SiteInfo objects returned by CertifyManager.GetPrimaryWebSites() for IIS sites"); + } + + [TestMethod, Description("Test for using CertifyManager.GetPrimaryWebSites() for IIS using a bad item id")] + public async Task TestCertifyManagerGetPrimaryWebSitesBadItemId() + { + // Request website from CertifyManager.GetPrimaryWebSites() using a non-existent Item ID + var itemIdWebsite = await _certifyManager.GetPrimaryWebSites(StandardServerTypes.IIS, true, "bad_id"); + + // Evaluate return from CertifyManager.GetPrimaryWebSites() for IIS using a non-existent Item ID + Assert.IsNotNull(itemIdWebsite, "Expected website list returned by CertifyManager.GetPrimaryWebSites() for IIS sites to not be null"); + Assert.AreEqual(1, itemIdWebsite.Count, "Expected website list returned by CertifyManager.GetPrimaryWebSites() for IIS sites to not be empty"); + Assert.IsNull(itemIdWebsite[0], "Expected website list object returned by CertifyManager.GetPrimaryWebSites() for IIS with a bad itemId to be null"); + } + + [TestMethod, Description("Happy path test for using CertifyManager.GetPrimaryWebSites() for IIS including stopped sites")] + public async Task TestCertifyManagerGetPrimaryWebSitesIncludeStoppedSites() + { + // This test requires at least one website in IIS that is stopped + var primaryWebsites = await _certifyManager.GetPrimaryWebSites(StandardServerTypes.IIS, false); + + // Evaluate return from CertifyManager.GetPrimaryWebSites() for IIS + Assert.IsNotNull(primaryWebsites, "Expected website list returned by CertifyManager.GetPrimaryWebSites() for IIS sites to not be null"); + Assert.IsTrue(primaryWebsites.Count > 0, "Expected website list returned by CertifyManager.GetPrimaryWebSites() for IIS sites to not be empty"); + Assert.IsTrue(primaryWebsites.Exists(s => s.IsEnabled), "Expected website list returned by CertifyManager.GetPrimaryWebSites() for IIS sites to have at least one enabled site"); + Assert.IsTrue(primaryWebsites.Exists(s => s.IsEnabled == false), "Expected website list returned by CertifyManager.GetPrimaryWebSites() for IIS sites to have at least one disabled site"); + } + + [TestMethod, Description("Test for using CertifyManager.GetPrimaryWebSites() when server type is not found")] + public async Task TestCertifyManagerGetPrimaryWebSitesServerTypeNotFound() + { + // Request websites from CertifyManager.GetPrimaryWebSites() using StandardServerTypes.Other + var primaryWebsites = await _certifyManager.GetPrimaryWebSites(StandardServerTypes.Other, true); + + // Evaluate return from CertifyManager.GetPrimaryWebSites() for StandardServerTypes.Other + Assert.IsNotNull(primaryWebsites, "Expected website list returned by CertifyManager.GetPrimaryWebSites() for StandardServerTypes.Other to not be null"); + Assert.AreEqual(0, primaryWebsites.Count, "Expected website list returned by CertifyManager.GetPrimaryWebSites() for StandardServerTypes.Other to be empty"); + } + + [TestMethod, Description("Happy path test for using CertifyManager.GetDomainOptionsFromSite() for IIS")] + public async Task TestCertifyManagerGetDomainOptionsFromSite() + { + // Request website Domain Options using Item ID + var siteDomainOptions = await _certifyManager.GetDomainOptionsFromSite(StandardServerTypes.IIS, _testSiteId); + + // Evaluate return from CertifyManager.GetDomainOptionsFromSite() for IIS + Assert.IsNotNull(siteDomainOptions, "Expected domain options list returned by CertifyManager.GetDomainOptionsFromSite() for IIS to not be null"); + Assert.AreEqual(1, siteDomainOptions.Count, "Expected domain options list returned by CertifyManager.GetDomainOptionsFromSite() for IIS to not be empty"); + } + + [TestMethod, Description("Happy path test for using CertifyManager.GetDomainOptionsFromSite() for IIS site with no defined domain")] + public async Task TestCertifyManagerGetDomainOptionsFromSiteNoDomain() + { + // Verify no domain site does not exist from previous test run + var noDomainSiteName = "NoDomainSite"; + if (await _iisManager.SiteExists(noDomainSiteName)) + { + await _iisManager.DeleteSite(noDomainSiteName); + } + + // Add no domain site + var noDomainSite = await _iisManager.CreateSite(noDomainSiteName, "", _primaryWebRoot, "DefaultAppPool", port: 81); + Assert.IsTrue(await _iisManager.SiteExists(_testSiteName), "Expected no domain site to be created"); + var noDomainSiteId = noDomainSite.Id.ToString(); + + // Request website Domain Options using Item ID + var siteDomainOptions = await _certifyManager.GetDomainOptionsFromSite(StandardServerTypes.IIS, noDomainSiteId); + + // Evaluate return from CertifyManager.GetDomainOptionsFromSite() for IIS + Assert.IsNotNull(siteDomainOptions, "Expected domain options list returned by CertifyManager.GetDomainOptionsFromSite() for IIS to not be null"); + Assert.AreEqual(0, siteDomainOptions.Count, "Expected domain options list returned by CertifyManager.GetDomainOptionsFromSite() for IIS to be empty"); + + // Remove no domain site + await _iisManager.DeleteSite(noDomainSiteName); + Assert.IsFalse(await _iisManager.SiteExists(noDomainSiteName), "Expected no domain site to be deleted"); + } + + [TestMethod, Description("Test for using CertifyManager.GetDomainOptionsFromSite() when server type is not found")] + public async Task TestCertifyManagerGetDomainOptionsFromSiteServerTypeNotFound() + { + // Request website Domain Options for a non-initialized server type (StandardServerTypes.Other) + var siteDomainOptions = await _certifyManager.GetDomainOptionsFromSite(StandardServerTypes.Other, "1"); + + // Evaluate return from CertifyManager.GetDomainOptionsFromSite() for StandardServerTypes.Other + Assert.IsNotNull(siteDomainOptions, "Expected domain options list returned by CertifyManager.GetDomainOptionsFromSite() for StandardServerTypes.Other to not be null"); + Assert.AreEqual(0, siteDomainOptions.Count, "Expected domain options list returned by CertifyManager.GetDomainOptionsFromSite() for StandardServerTypes.Other to be empty"); + } + + [TestMethod, Description("Test for using CertifyManager.GetDomainOptionsFromSite() for IIS using a bad item id")] + public async Task TestCertifyManagerGetDomainOptionsFromSiteBadItemId() + { + // Request website Domain Options using a non-existent Item ID for IIS + var siteDomainOptions = await _certifyManager.GetDomainOptionsFromSite(StandardServerTypes.IIS, "bad_id"); + + // Evaluate return from CertifyManager.GetDomainOptionsFromSite() using a non-existent Item ID + Assert.IsNotNull(siteDomainOptions, "Expected domain options list returned by CertifyManager.GetDomainOptionsFromSite() to not be null"); + Assert.AreEqual(0, siteDomainOptions.Count, "Expected domain options list returned by CertifyManager.GetDomainOptionsFromSite() for a non-existent Item ID to be empty"); + } + + [TestMethod, Description("Happy path test for using CertifyManager.IsServerTypeAvailable()")] + public async Task TestCertifyManagerIsServerTypeAvailable() + { + // This test requires at least one website in Nginx conf to be defined + var isIisAvailable = await _certifyManager.IsServerTypeAvailable(StandardServerTypes.IIS); + var isNginxAvailable = await _certifyManager.IsServerTypeAvailable(StandardServerTypes.Nginx); + var isApacheAvailable = await _certifyManager.IsServerTypeAvailable(StandardServerTypes.Apache); + var isOtherAvailable = await _certifyManager.IsServerTypeAvailable(StandardServerTypes.Other); + + // Evaluate returns from CertifyManager.IsServerTypeAvailable() + Assert.IsTrue(isIisAvailable, "Expected return from CertifyManager.IsServerTypeAvailable() to be true when at least one IIS site is active"); + + Assert.IsTrue(isNginxAvailable, "Expected return from CertifyManager.IsServerTypeAvailable() to be true when at least one Nginx site is active"); + + Assert.IsFalse(isApacheAvailable, "Expected return from CertifyManager.IsServerTypeAvailable() to be false when Apache plugin does not exist"); + // TODO: Support for Apache via plugin must be added to enable the next assert + //Assert.IsTrue(isApacheAvailable, "Expected return from CertifyManager.IsServerTypeAvailable() to be true when at least one Apache site is active"); + + Assert.IsFalse(isOtherAvailable, "Expected return from CertifyManager.IsServerTypeAvailable() to be false for StandardServerTypes.Other"); + } + + [TestMethod, Description("Happy path test for using CertifyManager.GetServerTypeVersion()")] + public async Task TestCertifyManagerGetServerTypeVersion() + { + // This test requires at least one website in Nginx conf to be defined + var iisServerVersion = await _certifyManager.GetServerTypeVersion(StandardServerTypes.IIS); + var nginxServerVersion = await _certifyManager.GetServerTypeVersion(StandardServerTypes.Nginx); + var apacheServerVersion = await _certifyManager.GetServerTypeVersion(StandardServerTypes.Apache); + var otherServerVersion = await _certifyManager.GetServerTypeVersion(StandardServerTypes.Other); + + var unknownVersion = new Version(0, 0); + + // Evaluate returns from CertifyManager.GetServerTypeVersion() + Assert.AreNotEqual(unknownVersion, iisServerVersion, "Expected return from CertifyManager.GetServerTypeVersion() to be known when at least one IIS site is active"); + Assert.IsTrue(iisServerVersion.Major > 0); + + Assert.AreNotEqual(unknownVersion, nginxServerVersion, "Expected return from CertifyManager.GetServerTypeVersion() to be known when at least one Nginx site is active"); + Assert.IsTrue(nginxServerVersion.Major > 0); + + Assert.AreEqual(unknownVersion, apacheServerVersion, "Expected return from CertifyManager.GetServerTypeVersion() to be unknown when Apache plugin does not exist"); + // TODO: Support for Apache via plugin must be added to enable the next assert + //Assert.AreNotEqual(unknownVersion, isApacheAvailable, "Expected return from CertifyManager.GetServerTypeVersion() to be known when at least one Apache site is active"); + + Assert.AreEqual(unknownVersion, otherServerVersion, "Expected return from CertifyManager.GetServerTypeVersion() to be unknown for StandardServerTypes.Other"); + } + + [TestMethod, Description("Happy path test for using CertifyManager.RunServerDiagnostics() for IIS")] + public async Task TestCertifyManagerRunServerDiagnostics() + { + // Run diagnostics on the IIS site using Item ID + var siteDiagnostics = await _certifyManager.RunServerDiagnostics(StandardServerTypes.IIS, _testSiteId); + + // Evaluate return from CertifyManager.GetPrimaryWebSites() for IIS + Assert.IsNotNull(siteDiagnostics, "Expected diagnostics list returned by CertifyManager.RunServerDiagnostics() for IIS site to not be null"); + Assert.AreEqual(1, siteDiagnostics.Count, "Expected diagnostics list returned by CertifyManager.RunServerDiagnostics() for IIS site to not be empty"); + } + + [TestMethod, Description("Test for using CertifyManager.RunServerDiagnostics() when server type is not found")] + public async Task TestCertifyManagerRunServerDiagnosticsServerTypeNotFound() + { + // Run diagnostics for a non-initialized server type (StandardServerTypes.Other) + var siteDiagnostics = await _certifyManager.RunServerDiagnostics(StandardServerTypes.Other, _testSiteId); + + // Evaluate return from CertifyManager.GetPrimaryWebSites() for StandardServerTypes.Other + Assert.IsNotNull(siteDiagnostics, "Expected diagnostics list returned by CertifyManager.RunServerDiagnostics() for StandardServerTypes.Other to not be null"); + Assert.AreEqual(0, siteDiagnostics.Count, "Expected diagnostics list returned by CertifyManager.RunServerDiagnostics() for StandardServerTypes.Other to be empty"); + } + + [TestMethod, Description("Test for using CertifyManager.RunServerDiagnostics() using a bad item id")] + public async Task TestCertifyManagerRunServerDiagnosticsBadItemId() + { + // Run diagnostics on the IIS site using bad Item ID + var siteDiagnostics = await _certifyManager.RunServerDiagnostics(StandardServerTypes.IIS, "bad_id"); + + // Evaluate return from CertifyManager.GetPrimaryWebSites() for IIS with bad Item ID + Assert.IsNotNull(siteDiagnostics, "Expected diagnostics list returned by CertifyManager.RunServerDiagnostics() for IIS site to not be null"); + + // Note: There seems to be no difference at the moment as to whether the Item ID passed in is valid or not, + // as RunServerDiagnostics() for IIS never uses the passed siteId string (is this intentional?) + Assert.AreEqual(1, siteDiagnostics.Count, "Expected diagnostics list returned by CertifyManager.RunServerDiagnostics() for IIS site to be empty"); + } + } +} From f1f9a4ce836f8cb6f7ad476bac1a8dd093a329cf Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Fri, 3 Nov 2023 15:47:59 +0800 Subject: [PATCH 076/328] Settings > Certificate Authorities, convert some text to resources --- src/Certify.Locales/SR.Designer.cs | 18 ++++++++++++++++++ src/Certify.Locales/SR.resx | 8 +++++++- .../Settings/CertificateAuthorities.xaml | 11 +++++------ src/Certify.UI/App.xaml | 14 +++++--------- 4 files changed, 35 insertions(+), 16 deletions(-) diff --git a/src/Certify.Locales/SR.Designer.cs b/src/Certify.Locales/SR.Designer.cs index cc8630df7..4883a5b90 100644 --- a/src/Certify.Locales/SR.Designer.cs +++ b/src/Certify.Locales/SR.Designer.cs @@ -1619,6 +1619,24 @@ public static string Settings_AutoRenewalRequestLimit { } } + /// + /// Looks up a localized string similar to If you register with multiple authorities this may enable you to use automatic Certificate Authority Failover, so if your preferred Certificate Authority can't issue a new certificate an alternative compatible provider can be used automatically.. + /// + public static string Settings_CA_Fallback { + get { + return ResourceManager.GetString("Settings_CA_Fallback", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Certificate Authorities are the organisations who can issue trusted certificates. You need to register an account for each (ACME) Certificate Authority you wish to use. Accounts can either be Production (live, trusted certificates) or Staging (test, non-trusted).. + /// + public static string Settings_CA_Intro { + get { + return ResourceManager.GetString("Settings_CA_Intro", resourceCulture); + } + } + /// /// Looks up a localized string similar to Check for updates automatically. /// diff --git a/src/Certify.Locales/SR.resx b/src/Certify.Locales/SR.resx index 6dff6f222..6147e1a88 100644 --- a/src/Certify.Locales/SR.resx +++ b/src/Certify.Locales/SR.resx @@ -1,4 +1,4 @@ - + - - Certificate Authorities are the organisations who can issue trusted certificates. You need to register an account for each (ACME) Certificate Authority you wish to use. Accounts can either be Production (live, trusted certificates) or Staging (test, non-trusted). - + - - If you register with multiple authorities this may enable you to use automatic Certificate Authority Failover, so if your preferred Certificate Authority can't issue a new certificate an alternative compatible provider can be used automatically. - + diff --git a/src/Certify.UI.Shared/Windows/EditAccountDialog.xaml b/src/Certify.UI.Shared/Windows/EditAccountDialog.xaml index 87b4137bc..348009eb4 100644 --- a/src/Certify.UI.Shared/Windows/EditAccountDialog.xaml +++ b/src/Certify.UI.Shared/Windows/EditAccountDialog.xaml @@ -7,9 +7,9 @@ xmlns:local="clr-namespace:Certify.UI.Windows" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:res="clr-namespace:Certify.Locales;assembly=Certify.Locales" - Title="Edit ACME Account" Width="592" Height="466" + Title="{x:Static res:SR.Account_Edit_SectionTitle}" ResizeMode="CanResizeWithGrip" TitleCharacterCasing="Normal" WindowStartupLocation="CenterOwner" @@ -40,7 +40,7 @@ VerticalAlignment="Top" DockPanel.Dock="Top" Style="{StaticResource Instructions}" - TextWrapping="Wrap"> + TextWrapping="Wrap"> + TextWrapping="Wrap"> diff --git a/src/Certify.UI.Shared/Windows/EditAccountDialog.xaml.cs b/src/Certify.UI.Shared/Windows/EditAccountDialog.xaml.cs index 080f736cb..18f1e05cf 100644 --- a/src/Certify.UI.Shared/Windows/EditAccountDialog.xaml.cs +++ b/src/Certify.UI.Shared/Windows/EditAccountDialog.xaml.cs @@ -149,7 +149,7 @@ private async void Save_Click(object sender, RoutedEventArgs e) } else { - MessageBox.Show(Certify.Locales.SR.New_Contact_NeedAgree); + MessageBox.Show(Certify.Locales.SR.Account_Edit_AgreeConditions); } } diff --git a/src/Certify.UI.Shared/Windows/EditCertificateAuthority.xaml b/src/Certify.UI.Shared/Windows/EditCertificateAuthority.xaml index 3fc7669e9..1adac373d 100644 --- a/src/Certify.UI.Shared/Windows/EditCertificateAuthority.xaml +++ b/src/Certify.UI.Shared/Windows/EditCertificateAuthority.xaml @@ -51,7 +51,7 @@ Height="23" HorizontalAlignment="Left" VerticalAlignment="Top" - Controls:TextBoxHelper.Watermark="(Display Name for the Certificate Authority)" + Controls:TextBoxHelper.Watermark="{x:Static res:SR.EditCertificateAuthority_TitleHelp}" Text="{Binding Model.Item.Title}" TextWrapping="Wrap" /> @@ -88,7 +88,7 @@ Height="23" HorizontalAlignment="Left" VerticalAlignment="Top" - Controls:TextBoxHelper.Watermark="(Url for the production directory endpoint)" + Controls:TextBoxHelper.Watermark="{x:Static res:SR.EditCertificateAuthority_ProductionDirectoryHelp}" Text="{Binding Model.Item.ProductionAPIEndpoint}" TextWrapping="Wrap" /> From 9e6dd1c43fca49974ecef881696f89769b25587b Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Tue, 7 Nov 2023 18:00:01 +0800 Subject: [PATCH 080/328] Make CA controller partial for extending with source generators --- .../Controllers/internal/CertificateAuthorityController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/CertificateAuthorityController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/CertificateAuthorityController.cs index e845bab3f..a9b3b675a 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/CertificateAuthorityController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/CertificateAuthorityController.cs @@ -7,14 +7,14 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; -namespace Certify.Server.API.Controllers +namespace Certify.Server.Api.Public.Controllers { /// /// Internal API for extended certificate management. Not intended for general use. /// [ApiController] [Route("internal/v1/[controller]")] - public class CertificateAuthorityController : ControllerBase + public partial class CertificateAuthorityController : ControllerBase { private readonly ILogger _logger; From 349221ea895cca58c2fa452c9a06752f3859d12a Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 9 Nov 2023 09:08:33 +0800 Subject: [PATCH 081/328] Core: update to error messages when saving ca or account --- .../Management/CertifyManager/CertifyManager.Account.cs | 3 --- src/Certify.Server/Certify.Server.Api.Public/Startup.cs | 2 ++ 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.Account.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.Account.cs index c9186e3fb..1b3137c62 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.Account.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.Account.cs @@ -669,8 +669,5 @@ public async Task RemoveCertificateAuthority(string id) return new ActionResult($"The certificate authority {id} was not found in the list of custom CAs and could not be removed.", false); } } - - return await Task.FromResult(new ActionResult("An error occurred removing the indicated Custom CA from the Certificate Authorities list.", false)); - } } } diff --git a/src/Certify.Server/Certify.Server.Api.Public/Startup.cs b/src/Certify.Server/Certify.Server.Api.Public/Startup.cs index e623a8ee2..60bc7053d 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Startup.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Startup.cs @@ -72,6 +72,8 @@ public void ConfigureServices(IServiceCollection services) services.AddSwaggerGen(c => { + // docs UI will be available at /docs + c.SwaggerDoc("v1", new OpenApiInfo { Title = "Certify Server API", From eaaa194882a6e69810f04a9cd839c4e37a3f81f2 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Thu, 9 Nov 2023 12:16:34 +0800 Subject: [PATCH 082/328] Package updates --- src/Certify.Client/Certify.Client.csproj | 2 +- src/Certify.Core/Certify.Core.csproj | 2 +- .../DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj | 2 +- src/Certify.Providers/DNS/MSDNS/Plugin.DNS.MSDNS.csproj | 2 +- .../Certify.Server.Api.Public.Tests.csproj | 2 +- .../Certify.Server.Core/Certify.Server.Core.csproj | 2 +- .../Certify.Service.Worker/Certify.Service.Worker.csproj | 2 +- .../Certify.Shared.Extensions.csproj | 2 +- .../Certify.Core.Tests.Integration.csproj | 4 ++-- .../Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj | 6 +++--- .../Certify.Service.Tests.Integration.csproj | 4 ++-- .../Certify.UI.Tests.Integration.csproj | 2 +- 12 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/Certify.Client/Certify.Client.csproj b/src/Certify.Client/Certify.Client.csproj index e27a06f95..5ffab76aa 100644 --- a/src/Certify.Client/Certify.Client.csproj +++ b/src/Certify.Client/Certify.Client.csproj @@ -20,7 +20,7 @@ - + diff --git a/src/Certify.Core/Certify.Core.csproj b/src/Certify.Core/Certify.Core.csproj index 61b680a0f..7737f607e 100644 --- a/src/Certify.Core/Certify.Core.csproj +++ b/src/Certify.Core/Certify.Core.csproj @@ -83,7 +83,7 @@ 1701;1702;CA1068 - + diff --git a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj index f6fdb983e..424015779 100644 --- a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj +++ b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/Certify.Providers/DNS/MSDNS/Plugin.DNS.MSDNS.csproj b/src/Certify.Providers/DNS/MSDNS/Plugin.DNS.MSDNS.csproj index c2f045250..57f33b801 100644 --- a/src/Certify.Providers/DNS/MSDNS/Plugin.DNS.MSDNS.csproj +++ b/src/Certify.Providers/DNS/MSDNS/Plugin.DNS.MSDNS.csproj @@ -7,7 +7,7 @@ - + all diff --git a/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj b/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj index 758dd3b80..6a3571ca1 100644 --- a/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj +++ b/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj @@ -26,7 +26,7 @@ - + diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj index 15b0e512d..c50431f4f 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Certify.Service.Worker.csproj b/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Certify.Service.Worker.csproj index 06f0b6c9e..f3cb3e98e 100644 --- a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Certify.Service.Worker.csproj +++ b/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Certify.Service.Worker.csproj @@ -12,7 +12,7 @@ - + diff --git a/src/Certify.Shared.Extensions/Certify.Shared.Extensions.csproj b/src/Certify.Shared.Extensions/Certify.Shared.Extensions.csproj index a05eb2590..e7fe84833 100644 --- a/src/Certify.Shared.Extensions/Certify.Shared.Extensions.csproj +++ b/src/Certify.Shared.Extensions/Certify.Shared.Extensions.csproj @@ -23,7 +23,7 @@ - + diff --git a/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj b/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj index d5f534591..23f31d95f 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj @@ -88,7 +88,7 @@ - + @@ -96,7 +96,7 @@ - + diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj index d694a6b47..9f7c7fe11 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj @@ -136,14 +136,14 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + - + - + diff --git a/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj b/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj index 31455ada4..ab0db9dbe 100644 --- a/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj @@ -77,10 +77,10 @@ - + - + diff --git a/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj b/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj index 09aded4a4..618dabff8 100644 --- a/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj @@ -73,7 +73,7 @@ - + From daca26b8cd0b10b4c36467b2a81c5513c8379cf5 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 9 Nov 2023 16:45:27 +0800 Subject: [PATCH 083/328] Docker: update debug config and allow write to app settings for non-privileged app user --- .../Certify.Server.Api.Public.csproj | 37 +++++++------------ .../Certify.Server.Api.Public/Dockerfile | 33 +++++++++++++++++ .../Properties/launchSettings.json | 11 ++++++ .../Certify.Service.Worker/Dockerfile | 24 ++++++++---- .../Properties/launchSettings.json | 6 +-- 5 files changed, 74 insertions(+), 37 deletions(-) create mode 100644 src/Certify.Server/Certify.Server.Api.Public/Dockerfile diff --git a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj index c9e721ceb..54d1d9632 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj +++ b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj @@ -1,43 +1,32 @@  - net8.0 Linux - ..\..\..\..\certify-general + ..\.. 8793068b-aa98-48a5-807b-962b5b3e1aea - True + True - DEBUG;TRACE - - - - - + - - - - + + + + + + + + + - - - - - - - - - - - + \ No newline at end of file diff --git a/src/Certify.Server/Certify.Server.Api.Public/Dockerfile b/src/Certify.Server/Certify.Server.Api.Public/Dockerfile new file mode 100644 index 000000000..a9b4d78e0 --- /dev/null +++ b/src/Certify.Server/Certify.Server.Api.Public/Dockerfile @@ -0,0 +1,33 @@ +#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging. + +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base + +# grant write to store settings path before switching to app user +RUN mkdir /usr/share/certify && chown -R app:app /usr/share/certify + +USER app +WORKDIR /app +EXPOSE 32768 +EXPOSE 44360 + +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +ARG BUILD_CONFIGURATION=Release +WORKDIR /src +COPY ["Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj", "Certify.Server/Certify.Server.Api.Public/"] +COPY ["Certify.Client/Certify.Client.csproj", "Certify.Client/"] +COPY ["Certify.Locales/Certify.Locales.csproj", "Certify.Locales/"] +COPY ["Certify.Models/Certify.Models.csproj", "Certify.Models/"] +COPY ["Certify.Shared/Certify.Shared.Core.csproj", "Certify.Shared/"] +RUN dotnet restore "./Certify.Server/Certify.Server.Api.Public/./Certify.Server.Api.Public.csproj" +COPY . . +WORKDIR "/src/Certify.Server/Certify.Server.Api.Public" +RUN dotnet build "./Certify.Server.Api.Public.csproj" -c $BUILD_CONFIGURATION -o /app/build + +FROM build AS publish +ARG BUILD_CONFIGURATION=Release +RUN dotnet publish "./Certify.Server.Api.Public.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "Certify.Server.Api.Public.dll"] diff --git a/src/Certify.Server/Certify.Server.Api.Public/Properties/launchSettings.json b/src/Certify.Server/Certify.Server.Api.Public/Properties/launchSettings.json index 9db54dd53..4529b8901 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Properties/launchSettings.json +++ b/src/Certify.Server/Certify.Server.Api.Public/Properties/launchSettings.json @@ -26,6 +26,17 @@ "CERTIFY_SERVER_PORT": "9695" }, "distributionName": "" + }, + "Docker": { + "commandName": "Docker", + "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/docs", + "environmentVariables": { + "ASPNETCORE_URLS": "https://localhost:44361;http://localhost:44360", + "CERTIFY_SERVER_HOST": "localhost", + "CERTIFY_SERVER_PORT": "9695" + }, + "publishAllPorts": true, + "useSSL": true } }, "iisSettings": { diff --git a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Dockerfile b/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Dockerfile index 53e0dff18..78f967792 100644 --- a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Dockerfile +++ b/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Dockerfile @@ -1,19 +1,27 @@ -#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging. +#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging. + FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base + +# grant write to store settings path before switching to app user +RUN mkdir /usr/share/certify && chown -R app:app /usr/share/certify + +USER app WORKDIR /app -EXPOSE 80 -EXPOSE 443 +EXPOSE 8080 +EXPOSE 8081 FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +ARG BUILD_CONFIGURATION=Release WORKDIR /src -COPY ["Certify.Service.Worker/Certify.Service.Worker.csproj", "Certify.Service.Worker/"] -RUN dotnet restore "Certify.Service.Worker/Certify.Service.Worker.csproj" +COPY ["Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Certify.Service.Worker.csproj", "Certify.Server/Certify.Service.Worker/Certify.Service.Worker/"] +RUN dotnet restore "./Certify.Server/Certify.Service.Worker/Certify.Service.Worker/./Certify.Service.Worker.csproj" COPY . . -WORKDIR "/src/Certify.Service.Worker" -RUN dotnet build "Certify.Service.Worker.csproj" -c Release -o /app/build +WORKDIR "/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker" +RUN dotnet build "./Certify.Service.Worker.csproj" -c $BUILD_CONFIGURATION -o /app/build FROM build AS publish -RUN dotnet publish "Certify.Service.Worker.csproj" -c Release -o /app/publish +ARG BUILD_CONFIGURATION=Release +RUN dotnet publish "./Certify.Service.Worker.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false FROM base AS final WORKDIR /app diff --git a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Properties/launchSettings.json b/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Properties/launchSettings.json index e9093ef61..6ab245a4f 100644 --- a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Properties/launchSettings.json +++ b/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Properties/launchSettings.json @@ -30,11 +30,7 @@ "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }, - "sslPort": 44360, - "applicationUrl": "http://localhost:32768;https://localhost:44360", - "httpPort": 32768, - "publishAllPorts": true, - "useSSL": true + "publishAllPorts": true }, "WSL": { "commandName": "WSL2", From b40a9ffdc69850cad1effba0181817fedac4f542 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 9 Nov 2023 16:45:48 +0800 Subject: [PATCH 084/328] Add public API to service solution --- src/Certify.Core.Service.sln | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Certify.Core.Service.sln b/src/Certify.Core.Service.sln index 7339e860a..6af14c6ef 100644 --- a/src/Certify.Core.Service.sln +++ b/src/Certify.Core.Service.sln @@ -71,6 +71,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Certify.ACME.Anvil", "..\.. EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Certify.Service.Worker", "Certify.Server\Certify.Service.Worker\Certify.Service.Worker\Certify.Service.Worker.csproj", "{D786EFD1-7402-43F0-B74F-504DB7C25FF6}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Certify.Server.Api.Public", "Certify.Server\Certify.Server.Api.Public\Certify.Server.Api.Public.csproj", "{2DB50C13-7535-4D01-8EA5-1839F1472D7B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -287,6 +289,14 @@ Global {D786EFD1-7402-43F0-B74F-504DB7C25FF6}.Release|Any CPU.Build.0 = Release|Any CPU {D786EFD1-7402-43F0-B74F-504DB7C25FF6}.Release|x64.ActiveCfg = Release|Any CPU {D786EFD1-7402-43F0-B74F-504DB7C25FF6}.Release|x64.Build.0 = Release|Any CPU + {2DB50C13-7535-4D01-8EA5-1839F1472D7B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2DB50C13-7535-4D01-8EA5-1839F1472D7B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2DB50C13-7535-4D01-8EA5-1839F1472D7B}.Debug|x64.ActiveCfg = Debug|Any CPU + {2DB50C13-7535-4D01-8EA5-1839F1472D7B}.Debug|x64.Build.0 = Debug|Any CPU + {2DB50C13-7535-4D01-8EA5-1839F1472D7B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2DB50C13-7535-4D01-8EA5-1839F1472D7B}.Release|Any CPU.Build.0 = Release|Any CPU + {2DB50C13-7535-4D01-8EA5-1839F1472D7B}.Release|x64.ActiveCfg = Release|Any CPU + {2DB50C13-7535-4D01-8EA5-1839F1472D7B}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From f2f1524dcc0dae38e40d2e1a1405f33a9b2d3a38 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Fri, 10 Nov 2023 15:36:20 +0800 Subject: [PATCH 085/328] Fix migration and public API namespaces --- src/Certify.CLI/CertifyCLI.Backup.cs | 3 +-- src/Certify.Client/CertifyApiClient.cs | 6 +++--- src/Certify.Client/ICertifyClient.cs | 5 ++--- .../Management/CertifyManager/CertifyManager.cs | 2 +- .../Management/CertifyManager/ICertifyManager.cs | 2 +- src/Certify.Core/Management/MigrationManager.cs | 2 +- src/Certify.Models/Config/Migration.cs | 2 +- .../Certify.Server.Api.Public.Tests/APITestBase.cs | 2 +- .../Controllers/internal/AccessController.cs | 1 - .../Controllers/internal/ChallengeProviderController.cs | 2 +- .../Controllers/internal/DeploymentTaskController.cs | 2 +- .../Controllers/internal/PreviewController.cs | 2 +- .../Controllers/internal/StoredCredentialController.cs | 4 ++-- .../Controllers/internal/TargetController.cs | 2 +- .../Controllers/v1/AuthController.cs | 2 +- .../Controllers/v1/CertificateController.cs | 3 +-- .../Controllers/v1/SystemController.cs | 4 ++-- .../Controllers/v1/ValidationController.cs | 2 +- .../Certify.Server.Core/Controllers/SystemController.cs | 2 +- src/Certify.Service/Controllers/SystemController.cs | 2 +- .../Certify.Core.Tests.Integration/CertifyManagerTests.cs | 2 +- .../Certify.Core.Tests.Integration/MigrationManagerTests.cs | 2 +- .../ViewModel/AppViewModel/AppViewModel.Settings.cs | 2 +- src/Certify.UI.Shared/Windows/ImportExport.xaml.cs | 3 +-- 24 files changed, 28 insertions(+), 33 deletions(-) diff --git a/src/Certify.CLI/CertifyCLI.Backup.cs b/src/Certify.CLI/CertifyCLI.Backup.cs index c17faf9cb..bdba39de3 100644 --- a/src/Certify.CLI/CertifyCLI.Backup.cs +++ b/src/Certify.CLI/CertifyCLI.Backup.cs @@ -2,8 +2,7 @@ using System.IO; using System.Linq; using System.Threading.Tasks; - -using Certify.Config.Migration; +using Certify.Models.Config.Migration; using Newtonsoft.Json; namespace Certify.CLI diff --git a/src/Certify.Client/CertifyApiClient.cs b/src/Certify.Client/CertifyApiClient.cs index efde8d88c..6d904e026 100644 --- a/src/Certify.Client/CertifyApiClient.cs +++ b/src/Certify.Client/CertifyApiClient.cs @@ -6,11 +6,11 @@ using System.Net.Http.Headers; using System.Threading; using System.Threading.Tasks; -using Certify.Config.Migration; using Certify.Models; using Certify.Models.API; using Certify.Models.Config; using Certify.Models.Config.AccessControl; +using Certify.Models.Config.Migration; using Certify.Models.Reporting; using Certify.Models.Utils; using Certify.Shared; @@ -256,7 +256,7 @@ public async Task> PerformServiceDiagnostics() return JsonConvert.DeserializeObject>(result); } - public async Task PerformExport(ExportRequest exportRequest) + /*public async Task PerformExport(ExportRequest exportRequest) { var result = await PostAsync("system/migration/export", exportRequest); return JsonConvert.DeserializeObject(await result.Content.ReadAsStringAsync()); @@ -266,7 +266,7 @@ public async Task> PerformImport(ImportRequest importRequest) { var result = await PostAsync("system/migration/import", importRequest); return JsonConvert.DeserializeObject>(await result.Content.ReadAsStringAsync()); - } + }*/ public async Task> SetDefaultDataStore(string dataStoreId) { var result = await PostAsync($"system/datastores/setdefault/{dataStoreId}", null); diff --git a/src/Certify.Client/ICertifyClient.cs b/src/Certify.Client/ICertifyClient.cs index 93c0914af..e4f913811 100644 --- a/src/Certify.Client/ICertifyClient.cs +++ b/src/Certify.Client/ICertifyClient.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -using Certify.Config.Migration; using Certify.Models; using Certify.Models.Config; using Certify.Models.Reporting; @@ -26,8 +25,8 @@ public partial interface ICertifyInternalApiClient Task> PerformServiceDiagnostics(); Task> PerformManagedCertMaintenance(string id = null); - Task PerformExport(ExportRequest exportRequest); - Task> PerformImport(ImportRequest importRequest); + // Task PerformExport(ExportRequest exportRequest); + // Task> PerformImport(ImportRequest importRequest); Task> SetDefaultDataStore(string dataStoreId); Task> GetDataStoreProviders(); diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.cs index 004e8ec78..1fd40af2f 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.cs @@ -6,12 +6,12 @@ using System.Linq; using System.Runtime.InteropServices; using System.Threading.Tasks; -using Certify.Config.Migration; using Certify.Core.Management; using Certify.Core.Management.Access; using Certify.Core.Management.Challenges; using Certify.Datastore.SQLite; using Certify.Models; +using Certify.Models.Config.Migration; using Certify.Models.Providers; using Certify.Providers; using Certify.Providers.ACME.Anvil; diff --git a/src/Certify.Core/Management/CertifyManager/ICertifyManager.cs b/src/Certify.Core/Management/CertifyManager/ICertifyManager.cs index 2327eec54..08191c91d 100644 --- a/src/Certify.Core/Management/CertifyManager/ICertifyManager.cs +++ b/src/Certify.Core/Management/CertifyManager/ICertifyManager.cs @@ -3,10 +3,10 @@ using System.Collections.Generic; using System.Threading.Tasks; using Certify.Config; -using Certify.Config.Migration; using Certify.Models; using Certify.Models.API; using Certify.Models.Config; +using Certify.Models.Config.Migration; using Certify.Models.Providers; using Certify.Providers; using Certify.Shared; diff --git a/src/Certify.Core/Management/MigrationManager.cs b/src/Certify.Core/Management/MigrationManager.cs index 215d31160..ed2aad3fb 100644 --- a/src/Certify.Core/Management/MigrationManager.cs +++ b/src/Certify.Core/Management/MigrationManager.cs @@ -8,10 +8,10 @@ using System.Text; using System.Threading.Tasks; using Certify.Config; -using Certify.Config.Migration; using Certify.Management; using Certify.Models; using Certify.Models.Config; +using Certify.Models.Config.Migration; using Certify.Models.Providers; using Certify.Providers; diff --git a/src/Certify.Models/Config/Migration.cs b/src/Certify.Models/Config/Migration.cs index 9bcfa2045..4f8243b19 100644 --- a/src/Certify.Models/Config/Migration.cs +++ b/src/Certify.Models/Config/Migration.cs @@ -3,7 +3,7 @@ using Certify.Models; using Certify.Models.Config; -namespace Certify.Config.Migration +namespace Certify.Models.Config.Migration { public class ImportExportContent { diff --git a/src/Certify.Server/Certify.Server.Api.Public.Tests/APITestBase.cs b/src/Certify.Server/Certify.Server.Api.Public.Tests/APITestBase.cs index 7cca8db97..586e0c34b 100644 --- a/src/Certify.Server/Certify.Server.Api.Public.Tests/APITestBase.cs +++ b/src/Certify.Server/Certify.Server.Api.Public.Tests/APITestBase.cs @@ -3,7 +3,7 @@ using System.Net.Http; using System.Threading.Tasks; using Certify.Models.API; -using Certify.Server.API.Controllers; +using Certify.Server.Api.Public.Controllers; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.Configuration; diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/AccessController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/AccessController.cs index 664d6b6bd..eb8738c17 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/AccessController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/AccessController.cs @@ -1,5 +1,4 @@ using Certify.Client; -using Certify.Server.API.Controllers; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/ChallengeProviderController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/ChallengeProviderController.cs index ff3234c7e..d1b7cf026 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/ChallengeProviderController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/ChallengeProviderController.cs @@ -8,7 +8,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; -namespace Certify.Server.API.Controllers +namespace Certify.Server.Api.Public.Controllers { /// /// Internal API for extended certificate management. Not intended for general use. diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/DeploymentTaskController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/DeploymentTaskController.cs index c8db0ad71..a5f8278dd 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/DeploymentTaskController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/DeploymentTaskController.cs @@ -7,7 +7,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; -namespace Certify.Server.API.Controllers +namespace Certify.Server.Api.Public.Controllers { /// /// Internal API for extended certificate management. Not intended for general use. diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/PreviewController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/PreviewController.cs index 27bcc3a64..70ebed9fe 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/PreviewController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/PreviewController.cs @@ -8,7 +8,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; -namespace Certify.Server.API.Controllers +namespace Certify.Server.Api.Public.Controllers { /// /// Internal API for extended certificate management. Not intended for general use. diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/StoredCredentialController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/StoredCredentialController.cs index 843116c2f..023b3d581 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/StoredCredentialController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/StoredCredentialController.cs @@ -7,14 +7,14 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; -namespace Certify.Server.API.Controllers +namespace Certify.Server.Api.Public.Controllers { /// /// Internal API for extended certificate management. Not intended for general use. /// [ApiController] [Route("internal/v1/[controller]")] - public class StoredCredentialController : ControllerBase + public partial class StoredCredentialController : ControllerBase { private readonly ILogger _logger; diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/TargetController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/TargetController.cs index 296fb8bc9..f3c8bc83d 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/TargetController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/TargetController.cs @@ -9,7 +9,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; -namespace Certify.Server.API.Controllers +namespace Certify.Server.Api.Public.Controllers { /// /// Internal API for extended certificate management. Not intended for general use. diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/AuthController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/AuthController.cs index 59a35233d..4e81051e7 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/AuthController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/AuthController.cs @@ -8,7 +8,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; -namespace Certify.Server.API.Controllers +namespace Certify.Server.Api.Public.Controllers { /// /// Provides auth related operations diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs index 261634bac..8c1b63f87 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs @@ -11,7 +11,7 @@ using Microsoft.Extensions.Logging; using Swashbuckle.AspNetCore.Annotations; -namespace Certify.Server.API.Controllers +namespace Certify.Server.Api.Public.Controllers { /// /// Provides managed certificate related operations @@ -98,7 +98,6 @@ public async Task DownloadLog(string managedCertId, int maxLines var log = await _client.GetItemLog(managedCertId, maxLines); - return new OkObjectResult(new LogResult { Items = log }); } diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/SystemController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/SystemController.cs index da0203190..c3e2eb018 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/SystemController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/SystemController.cs @@ -4,14 +4,14 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; -namespace Certify.Server.API.Controllers +namespace Certify.Server.Api.Public.Controllers { /// /// Provides general system level information (version etc) /// [ApiController] [Route("api/v1/[controller]")] - public class SystemController : ControllerBase + public partial class SystemController : ControllerBase { private readonly ILogger _logger; diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/ValidationController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/ValidationController.cs index bd8326eb7..27df4e2b7 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/ValidationController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/ValidationController.cs @@ -7,7 +7,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; -namespace Certify.Server.API.Controllers +namespace Certify.Server.Api.Public.Controllers { /// /// Provides operations related to identifier validation challenges (proof of domain control etc) diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/SystemController.cs b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/SystemController.cs index 1f3e47430..91917727a 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/SystemController.cs +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/SystemController.cs @@ -1,9 +1,9 @@ using System.Collections.Generic; using System.Threading.Tasks; -using Certify.Config.Migration; using Certify.Management; using Certify.Models; using Certify.Models.Config; +using Certify.Models.Config.Migration; using Certify.Shared; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Cors; diff --git a/src/Certify.Service/Controllers/SystemController.cs b/src/Certify.Service/Controllers/SystemController.cs index e408d0900..35de239b3 100644 --- a/src/Certify.Service/Controllers/SystemController.cs +++ b/src/Certify.Service/Controllers/SystemController.cs @@ -2,10 +2,10 @@ using System.Threading.Tasks; using System.Web.Http; using System.Web.Http.Cors; -using Certify.Config.Migration; using Certify.Management; using Certify.Models; using Certify.Models.Config; +using Certify.Models.Config.Migration; using Certify.Shared; namespace Certify.Service.Controllers diff --git a/src/Certify.Tests/Certify.Core.Tests.Integration/CertifyManagerTests.cs b/src/Certify.Tests/Certify.Core.Tests.Integration/CertifyManagerTests.cs index 8b9af1fb6..405da925e 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Integration/CertifyManagerTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Integration/CertifyManagerTests.cs @@ -1,6 +1,6 @@ using System; using System.Threading.Tasks; -using Certify.Config.Migration; +using Certify.Models.Config.Migration; using Certify.Management; using Certify.Models; using Certify.Service; diff --git a/src/Certify.Tests/Certify.Core.Tests.Integration/MigrationManagerTests.cs b/src/Certify.Tests/Certify.Core.Tests.Integration/MigrationManagerTests.cs index 47e115dbc..a7d1daed5 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Integration/MigrationManagerTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Integration/MigrationManagerTests.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; using System.Threading.Tasks; -using Certify.Config.Migration; +using Certify.Models.Config.Migration; using Certify.Core.Management; using Certify.Datastore.SQLite; using Certify.Management; diff --git a/src/Certify.UI.Shared/ViewModel/AppViewModel/AppViewModel.Settings.cs b/src/Certify.UI.Shared/ViewModel/AppViewModel/AppViewModel.Settings.cs index 0b352fc28..a6ff83306 100644 --- a/src/Certify.UI.Shared/ViewModel/AppViewModel/AppViewModel.Settings.cs +++ b/src/Certify.UI.Shared/ViewModel/AppViewModel/AppViewModel.Settings.cs @@ -4,8 +4,8 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using Certify.Config.Migration; using Certify.Models; +using Certify.Models.Config.Migration; using Certify.UI.Settings; namespace Certify.UI.ViewModel diff --git a/src/Certify.UI.Shared/Windows/ImportExport.xaml.cs b/src/Certify.UI.Shared/Windows/ImportExport.xaml.cs index 3c4b3ae03..c6c229066 100644 --- a/src/Certify.UI.Shared/Windows/ImportExport.xaml.cs +++ b/src/Certify.UI.Shared/Windows/ImportExport.xaml.cs @@ -3,9 +3,8 @@ using System.Linq; using System.Text; using System.Windows; - -using Certify.Config.Migration; using Certify.Models; +using Certify.Models.Config.Migration; using Certify.UI.Shared; using Microsoft.Win32; using Newtonsoft.Json; From 2e3d52f7bc90efb388fa04af887a1ef8f58abc61 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Tue, 14 Nov 2023 15:40:45 +0800 Subject: [PATCH 086/328] UI: use resource for string in import/export --- src/Certify.Locales/SR.Designer.cs | 9 +++++++++ src/Certify.Locales/SR.resx | 3 +++ src/Certify.UI.Shared/Windows/ImportExport.xaml | 2 +- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/Certify.Locales/SR.Designer.cs b/src/Certify.Locales/SR.Designer.cs index 870662383..3c9241850 100644 --- a/src/Certify.Locales/SR.Designer.cs +++ b/src/Certify.Locales/SR.Designer.cs @@ -1754,6 +1754,15 @@ public static string Settings_EnableTelemetry { } } + /// + /// Looks up a localized string similar to You can create an export file to bundle all of the related settings and file for this instance together. Note: sensitive content is encrypted but you should not share this file with untrusted sources or use unsecured storage.. + /// + public static string Settings_Export_Intro { + get { + return ResourceManager.GetString("Settings_Export_Intro", resourceCulture); + } + } + /// /// Looks up a localized string similar to Ignore stopped IIS sites for new certificates and renewals. /// diff --git a/src/Certify.Locales/SR.resx b/src/Certify.Locales/SR.resx index 006e59ec0..826d63ad5 100644 --- a/src/Certify.Locales/SR.resx +++ b/src/Certify.Locales/SR.resx @@ -716,4 +716,7 @@ (Url for the production directory endpoint) + + You can create an export file to bundle all of the related settings and file for this instance together. Note: sensitive content is encrypted but you should not share this file with untrusted sources or use unsecured storage. + \ No newline at end of file diff --git a/src/Certify.UI.Shared/Windows/ImportExport.xaml b/src/Certify.UI.Shared/Windows/ImportExport.xaml index 12073af34..67b953e83 100644 --- a/src/Certify.UI.Shared/Windows/ImportExport.xaml +++ b/src/Certify.UI.Shared/Windows/ImportExport.xaml @@ -17,7 +17,7 @@ Import/Export Settings - You can create an export file to bundle all of the related settings and file for this instance together. Note: sensitive content is encrypted but you should not share this file with untrusted sources or use unsecured storage. + "{x:Static properties:SR.Settings_Export_Intro}" To import or export, you should specify a password to use for encryption/decryption: Date: Wed, 15 Nov 2023 16:15:56 +0800 Subject: [PATCH 087/328] Package updates --- src/Certify.Client/Certify.Client.csproj | 6 +++--- src/Certify.Core/Certify.Core.csproj | 2 +- src/Certify.Models/Certify.Models.csproj | 2 +- .../DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj | 2 +- .../DNS/Azure/Plugin.DNS.Azure.csproj | 6 +++--- .../Certify.Server.Api.Public.Tests.csproj | 2 +- .../Certify.Server.Api.Public.csproj | 8 ++++---- .../Certify.Server.Core/Certify.Server.Core.csproj | 12 ++++++------ .../Certify.Service.Worker.csproj | 8 ++++---- src/Certify.Service/App.config | 8 ++++---- src/Certify.Service/Certify.Service.csproj | 7 ++++--- src/Certify.Shared/Certify.Shared.Core.csproj | 4 ++-- .../Certify.Core.Tests.Integration.csproj | 4 ++-- .../Certify.Core.Tests.Unit.csproj | 8 ++++---- .../Certify.Service.Tests.Integration.csproj | 4 ++-- src/Certify.UI.Shared/Certify.UI.Shared.csproj | 4 ++-- src/Certify.UI/Certify.UI.csproj | 4 ++-- 17 files changed, 46 insertions(+), 45 deletions(-) diff --git a/src/Certify.Client/Certify.Client.csproj b/src/Certify.Client/Certify.Client.csproj index 5ffab76aa..d05161e55 100644 --- a/src/Certify.Client/Certify.Client.csproj +++ b/src/Certify.Client/Certify.Client.csproj @@ -16,11 +16,11 @@ - - + + - + diff --git a/src/Certify.Core/Certify.Core.csproj b/src/Certify.Core/Certify.Core.csproj index 7737f607e..815d2c69b 100644 --- a/src/Certify.Core/Certify.Core.csproj +++ b/src/Certify.Core/Certify.Core.csproj @@ -83,7 +83,7 @@ 1701;1702;CA1068 - + diff --git a/src/Certify.Models/Certify.Models.csproj b/src/Certify.Models/Certify.Models.csproj index 1aa1612ce..a918579d1 100644 --- a/src/Certify.Models/Certify.Models.csproj +++ b/src/Certify.Models/Certify.Models.csproj @@ -30,7 +30,7 @@ all - + diff --git a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj index 424015779..6cc8ed027 100644 --- a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj +++ b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj b/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj index b178c09dd..26de95501 100644 --- a/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj +++ b/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj @@ -7,10 +7,10 @@ - - + + - + diff --git a/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj b/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj index 6a3571ca1..6854064ce 100644 --- a/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj +++ b/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj @@ -25,7 +25,7 @@ - + diff --git a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj index 54d1d9632..978ee4a26 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj +++ b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj @@ -16,10 +16,10 @@ - - - - + + + + diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj index c50431f4f..24c804572 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj @@ -7,13 +7,13 @@ - - + + - - - - + + + + diff --git a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Certify.Service.Worker.csproj b/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Certify.Service.Worker.csproj index f3cb3e98e..98a7f08a9 100644 --- a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Certify.Service.Worker.csproj +++ b/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Certify.Service.Worker.csproj @@ -12,10 +12,10 @@ - - - - + + + + diff --git a/src/Certify.Service/App.config b/src/Certify.Service/App.config index a8ae0f42f..ec7fc9391 100644 --- a/src/Certify.Service/App.config +++ b/src/Certify.Service/App.config @@ -8,20 +8,20 @@ - + - + - + @@ -33,7 +33,7 @@ - + diff --git a/src/Certify.Service/Certify.Service.csproj b/src/Certify.Service/Certify.Service.csproj index e8dabde64..25b0969b0 100644 --- a/src/Certify.Service/Certify.Service.csproj +++ b/src/Certify.Service/Certify.Service.csproj @@ -64,11 +64,12 @@ - + - - + + + diff --git a/src/Certify.Shared/Certify.Shared.Core.csproj b/src/Certify.Shared/Certify.Shared.Core.csproj index 31d03bdd2..bd8ee3afc 100644 --- a/src/Certify.Shared/Certify.Shared.Core.csproj +++ b/src/Certify.Shared/Certify.Shared.Core.csproj @@ -12,13 +12,13 @@ - + - + diff --git a/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj b/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj index 23f31d95f..74d0ec454 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj @@ -96,14 +96,14 @@ - + - + diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj index 9f7c7fe11..262d6e2de 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj @@ -137,15 +137,15 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + - - - + + + diff --git a/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj b/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj index ab0db9dbe..d155c9add 100644 --- a/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj @@ -80,8 +80,8 @@ - - + + diff --git a/src/Certify.UI.Shared/Certify.UI.Shared.csproj b/src/Certify.UI.Shared/Certify.UI.Shared.csproj index 49b0f8756..1dfec6cde 100644 --- a/src/Certify.UI.Shared/Certify.UI.Shared.csproj +++ b/src/Certify.UI.Shared/Certify.UI.Shared.csproj @@ -41,8 +41,8 @@ - - + + NU1701 diff --git a/src/Certify.UI/Certify.UI.csproj b/src/Certify.UI/Certify.UI.csproj index 4abb7cb30..6f6198df1 100644 --- a/src/Certify.UI/Certify.UI.csproj +++ b/src/Certify.UI/Certify.UI.csproj @@ -177,10 +177,10 @@ all - 3.0.1 + 3.1.1 - 8.0.0-rc.2.23479.6 + 8.0.0 4.3.4 From 613acc396ebc918b728bdb14d763f68079e1abee Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Thu, 16 Nov 2023 15:09:35 +0800 Subject: [PATCH 088/328] aspire experiment --- .../Certify.Server.Api.Public.csproj | 23 +++----- .../Controllers/v1/CertificateController.cs | 7 --- .../Certify.Server.Api.Public/Program.cs | 58 +++++++++---------- .../Properties/launchSettings.json | 2 +- .../Certify.Server.Api.Public/Startup.cs | 2 +- .../Certify.Service.Worker.csproj | 1 + 6 files changed, 38 insertions(+), 55 deletions(-) diff --git a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj index 978ee4a26..104053b36 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj +++ b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj @@ -1,32 +1,25 @@  net8.0 + enable + enable + true Linux ..\.. 8793068b-aa98-48a5-807b-962b5b3e1aea True - - DEBUG;TRACE - + - - - - - - - - - + + + - - - + \ No newline at end of file diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs index 8c1b63f87..c60ecc529 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs @@ -9,7 +9,6 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; -using Swashbuckle.AspNetCore.Annotations; namespace Certify.Server.Api.Public.Controllers { @@ -113,7 +112,6 @@ public async Task DownloadLog(string managedCertId, int maxLines [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(ManagedCertificateSummaryResult))] public async Task GetManagedCertificates(string keyword, int? page = null, int? pageSize = null) { - var managedCertResult = await _client.GetManagedCertificateSearchResult( new Models.ManagedCertificateFilter { @@ -176,7 +174,6 @@ public async Task GetManagedCertificateSummary(string keyword) [Route("settings/{managedCertId}")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Models.ManagedCertificate))] - [SwaggerOperation(OperationId = nameof(GetManagedCertificateDetails))] public async Task GetManagedCertificateDetails(string managedCertId) { @@ -194,7 +191,6 @@ public async Task GetManagedCertificateDetails(string managedCert [Route("settings/update")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Models.ManagedCertificate))] - [SwaggerOperation(OperationId = nameof(UpdateManagedCertificateDetails))] public async Task UpdateManagedCertificateDetails(Models.ManagedCertificate managedCertificate) { @@ -218,7 +214,6 @@ public async Task UpdateManagedCertificateDetails(Models.ManagedC [Route("order")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Models.ManagedCertificate))] - [SwaggerOperation(OperationId = nameof(BeginOrder))] public async Task BeginOrder(string id) { @@ -242,7 +237,6 @@ public async Task BeginOrder(string id) [Route("renew")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Models.ManagedCertificate))] - [SwaggerOperation(OperationId = nameof(PerformRenewal))] public async Task PerformRenewal(Models.RenewalSettings settings) { @@ -266,7 +260,6 @@ public async Task PerformRenewal(Models.RenewalSettings settings) [Route("test")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(List))] - [SwaggerOperation(OperationId = nameof(PerformConfigurationTest))] public async Task PerformConfigurationTest(Models.ManagedCertificate item) { diff --git a/src/Certify.Server/Certify.Server.Api.Public/Program.cs b/src/Certify.Server/Certify.Server.Api.Public/Program.cs index 32061a7fc..338e6a72a 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Program.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Program.cs @@ -1,34 +1,30 @@ -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Hosting; + +var builder = WebApplication.CreateBuilder(args); -namespace Certify.Server.API +builder.AddServiceDefaults(); + +// Add services to the container. + +builder.Services.AddControllers(); +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +var app = builder.Build(); + +app.MapDefaultEndpoints(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) { - /// - /// API Server hosting - /// - public class Program - { - /// - /// Entry point for API host - /// - /// - public static void Main(string[] args) - { - - CreateHostBuilder(args).Build().Run(); - } - - /// - /// Build hosting for API - /// - /// - /// - - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }); - } + app.UseSwagger(); + app.UseSwaggerUI(); } + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); diff --git a/src/Certify.Server/Certify.Server.Api.Public/Properties/launchSettings.json b/src/Certify.Server/Certify.Server.Api.Public/Properties/launchSettings.json index 4529b8901..b542b9c09 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Properties/launchSettings.json +++ b/src/Certify.Server/Certify.Server.Api.Public/Properties/launchSettings.json @@ -14,7 +14,7 @@ "CERTIFY_SERVER_HOST": "127.0.0.2", "CERTIFY_SERVER_PORT": "9695" }, - "applicationUrl": "https://localhost:44361;http://localhost:44360" + "applicationUrl": "https://localhost:54361;http://localhost:54360" }, "WSL": { "commandName": "WSL2", diff --git a/src/Certify.Server/Certify.Server.Api.Public/Startup.cs b/src/Certify.Server/Certify.Server.Api.Public/Startup.cs index 60bc7053d..580f741e1 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Startup.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Startup.cs @@ -44,7 +44,7 @@ public Startup(IConfiguration configuration) /// public void ConfigureServices(IServiceCollection services) { - + services .AddTokenAuthentication(Configuration) .AddAuthorization() diff --git a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Certify.Service.Worker.csproj b/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Certify.Service.Worker.csproj index 98a7f08a9..0b879d060 100644 --- a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Certify.Service.Worker.csproj +++ b/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Certify.Service.Worker.csproj @@ -19,5 +19,6 @@ + \ No newline at end of file From 0034cb732655487d403b8eee3c738e609ca37815 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 16 Nov 2023 17:36:08 +0800 Subject: [PATCH 089/328] Fixes for build/untime errors --- .../Certify.Server.Api.Public.csproj | 1 - .../Controllers/v1/CertificateController.cs | 4 ++-- .../Certify.Server.Api.Public/Properties/launchSettings.json | 2 +- .../Certify.Service.Worker/Certify.Service.Worker.csproj | 1 - 4 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj index 104053b36..9f25dd140 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj +++ b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj @@ -19,7 +19,6 @@ - \ No newline at end of file diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs index c60ecc529..6254f9489 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs @@ -110,7 +110,7 @@ public async Task DownloadLog(string managedCertId, int maxLines [HttpGet] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(ManagedCertificateSummaryResult))] - public async Task GetManagedCertificates(string keyword, int? page = null, int? pageSize = null) + public async Task GetManagedCertificates(string? keyword, int? page = null, int? pageSize = null) { var managedCertResult = await _client.GetManagedCertificateSearchResult( new Models.ManagedCertificateFilter @@ -153,7 +153,7 @@ public async Task GetManagedCertificates(string keyword, int? pag [Route("summary")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Summary))] - public async Task GetManagedCertificateSummary(string keyword) + public async Task GetManagedCertificateSummary(string? keyword) { var summary = await _client.GetManagedCertificateSummary( diff --git a/src/Certify.Server/Certify.Server.Api.Public/Properties/launchSettings.json b/src/Certify.Server/Certify.Server.Api.Public/Properties/launchSettings.json index b542b9c09..4529b8901 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Properties/launchSettings.json +++ b/src/Certify.Server/Certify.Server.Api.Public/Properties/launchSettings.json @@ -14,7 +14,7 @@ "CERTIFY_SERVER_HOST": "127.0.0.2", "CERTIFY_SERVER_PORT": "9695" }, - "applicationUrl": "https://localhost:54361;http://localhost:54360" + "applicationUrl": "https://localhost:44361;http://localhost:44360" }, "WSL": { "commandName": "WSL2", diff --git a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Certify.Service.Worker.csproj b/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Certify.Service.Worker.csproj index 0b879d060..98a7f08a9 100644 --- a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Certify.Service.Worker.csproj +++ b/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Certify.Service.Worker.csproj @@ -19,6 +19,5 @@ - \ No newline at end of file From 1a74b0dcadaac8ded89d6ac7abe44f4d119dbb0b Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 16 Nov 2023 17:39:41 +0800 Subject: [PATCH 090/328] Cleanup app startup Cleanup with implicit usings Cleanup --- src/Certify.Models/Config/Migration.cs | 2 - .../Controllers/internal/AccessController.cs | 1 - .../CertificateAuthorityController.cs | 6 +- .../internal/ChallengeProviderController.cs | 6 +- .../internal/DeploymentTaskController.cs | 6 +- .../Controllers/internal/PreviewController.cs | 6 +- .../internal/StoredCredentialController.cs | 6 +- .../Controllers/internal/TargetController.cs | 7 +-- .../Controllers/v1/AuthController.cs | 3 - .../Controllers/v1/CertificateController.cs | 7 +-- .../Controllers/v1/SystemController.cs | 5 +- .../Controllers/v1/ValidationController.cs | 6 +- .../Middleware/AuthenticationExtension.cs | 5 +- .../Certify.Server.Api.Public/Program.cs | 30 ++++------ .../Services/JwtService.cs | 4 +- .../Certify.Server.Api.Public/Startup.cs | 60 +++++++++---------- .../Certify.Server.Api.Public/StatusHub.cs | 4 +- .../CertifyManagerServerTypeTests.cs | 16 ++--- .../CertifyManagerTests.cs | 6 +- .../MigrationManagerTests.cs | 2 +- 20 files changed, 65 insertions(+), 123 deletions(-) diff --git a/src/Certify.Models/Config/Migration.cs b/src/Certify.Models/Config/Migration.cs index 4f8243b19..4ac2d544f 100644 --- a/src/Certify.Models/Config/Migration.cs +++ b/src/Certify.Models/Config/Migration.cs @@ -1,7 +1,5 @@ using System; using System.Collections.Generic; -using Certify.Models; -using Certify.Models.Config; namespace Certify.Models.Config.Migration { diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/AccessController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/AccessController.cs index eb8738c17..fd92b18e4 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/AccessController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/AccessController.cs @@ -1,6 +1,5 @@ using Certify.Client; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; namespace Certify.Server.Api.Public.Controllers { diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/CertificateAuthorityController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/CertificateAuthorityController.cs index a9b3b675a..733fb2204 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/CertificateAuthorityController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/CertificateAuthorityController.cs @@ -1,11 +1,7 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using Certify.Client; +using Certify.Client; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; namespace Certify.Server.Api.Public.Controllers { diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/ChallengeProviderController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/ChallengeProviderController.cs index d1b7cf026..0b17a5579 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/ChallengeProviderController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/ChallengeProviderController.cs @@ -1,12 +1,8 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using Certify.Client; +using Certify.Client; using Certify.Models.Providers; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; namespace Certify.Server.Api.Public.Controllers { diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/DeploymentTaskController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/DeploymentTaskController.cs index a5f8278dd..c9798e017 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/DeploymentTaskController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/DeploymentTaskController.cs @@ -1,11 +1,7 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using Certify.Client; +using Certify.Client; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; namespace Certify.Server.Api.Public.Controllers { diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/PreviewController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/PreviewController.cs index 70ebed9fe..f035d576e 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/PreviewController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/PreviewController.cs @@ -1,12 +1,8 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using Certify.Client; +using Certify.Client; using Certify.Models; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; namespace Certify.Server.Api.Public.Controllers { diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/StoredCredentialController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/StoredCredentialController.cs index 023b3d581..c22fe5c57 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/StoredCredentialController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/StoredCredentialController.cs @@ -1,11 +1,7 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using Certify.Client; +using Certify.Client; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; namespace Certify.Server.Api.Public.Controllers { diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/TargetController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/TargetController.cs index f3c8bc83d..0d469e433 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/TargetController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/TargetController.cs @@ -1,13 +1,8 @@ -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Certify.Client; +using Certify.Client; using Certify.Models; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; namespace Certify.Server.Api.Public.Controllers { diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/AuthController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/AuthController.cs index 4e81051e7..c667da260 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/AuthController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/AuthController.cs @@ -1,12 +1,9 @@ using System.Net.Http.Headers; -using System.Threading.Tasks; using Certify.Client; using Certify.Models.API; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; namespace Certify.Server.Api.Public.Controllers { diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs index 6254f9489..182f407c0 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs @@ -1,14 +1,9 @@ -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Certify.Client; +using Certify.Client; using Certify.Models.API; using Certify.Models.Reporting; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; namespace Certify.Server.Api.Public.Controllers { diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/SystemController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/SystemController.cs index c3e2eb018..efdef9c32 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/SystemController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/SystemController.cs @@ -1,8 +1,5 @@ -using System; -using System.Threading.Tasks; -using Certify.Client; +using Certify.Client; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; namespace Certify.Server.Api.Public.Controllers { diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/ValidationController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/ValidationController.cs index 27df4e2b7..9f7212972 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/ValidationController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/ValidationController.cs @@ -1,11 +1,7 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using Certify.Client; +using Certify.Client; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; namespace Certify.Server.Api.Public.Controllers { diff --git a/src/Certify.Server/Certify.Server.Api.Public/Middleware/AuthenticationExtension.cs b/src/Certify.Server/Certify.Server.Api.Public/Middleware/AuthenticationExtension.cs index 240207755..990ee518f 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Middleware/AuthenticationExtension.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Middleware/AuthenticationExtension.cs @@ -1,8 +1,5 @@ -using System; -using System.Text; +using System.Text; using Microsoft.AspNetCore.Authentication.JwtBearer; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; using Microsoft.IdentityModel.Tokens; namespace Certify.Server.Api.Public.Middleware diff --git a/src/Certify.Server/Certify.Server.Api.Public/Program.cs b/src/Certify.Server/Certify.Server.Api.Public/Program.cs index 338e6a72a..7ff00a0c1 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Program.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Program.cs @@ -1,30 +1,22 @@  +using Certify.Server.API; + var builder = WebApplication.CreateBuilder(args); -builder.AddServiceDefaults(); +#if ASPIRE + builder.AddServiceDefaults(); +#endif -// Add services to the container. +var startup = new Startup(builder.Configuration); -builder.Services.AddControllers(); -// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle -builder.Services.AddEndpointsApiExplorer(); -builder.Services.AddSwaggerGen(); +startup.ConfigureServices(builder.Services); var app = builder.Build(); -app.MapDefaultEndpoints(); - -// Configure the HTTP request pipeline. -if (app.Environment.IsDevelopment()) -{ - app.UseSwagger(); - app.UseSwaggerUI(); -} - -app.UseHttpsRedirection(); - -app.UseAuthorization(); +#if ASPIRE + app.MapDefaultEndpoints(); +#endif -app.MapControllers(); +startup.Configure(app, builder.Environment); app.Run(); diff --git a/src/Certify.Server/Certify.Server.Api.Public/Services/JwtService.cs b/src/Certify.Server/Certify.Server.Api.Public/Services/JwtService.cs index f23c31550..529013fec 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Services/JwtService.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Services/JwtService.cs @@ -1,8 +1,6 @@ -using System; -using System.Security.Claims; +using System.Security.Claims; using System.Security.Cryptography; using System.Text; -using Microsoft.Extensions.Configuration; using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.Tokens; diff --git a/src/Certify.Server/Certify.Server.Api.Public/Startup.cs b/src/Certify.Server/Certify.Server.Api.Public/Startup.cs index 580f741e1..0ef5e6eba 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Startup.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Startup.cs @@ -1,18 +1,9 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; +using System.Reflection; using Certify.Server.Api.Public.Middleware; using Certify.Shared.Core.Management; using Certify.SharedUtils; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.ResponseCompression; using Microsoft.AspNetCore.SignalR; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; using Microsoft.OpenApi.Models; namespace Certify.Server.API @@ -44,7 +35,7 @@ public Startup(IConfiguration configuration) /// public void ConfigureServices(IServiceCollection services) { - + services .AddTokenAuthentication(Configuration) .AddAuthorization() @@ -67,6 +58,9 @@ public void ConfigureServices(IServiceCollection services) }); #if DEBUG + + services.AddEndpointsApiExplorer(); + // Register the Swagger generator, defining 1 or more Swagger documents // https://docs.microsoft.com/en-us/aspnet/core/tutorials/getting-started-with-swashbuckle?view=aspnetcore-3.1&tabs=visual-studio services.AddSwaggerGen(c => @@ -156,32 +150,20 @@ public void ConfigureServices(IServiceCollection services) services.AddSingleton(typeof(Certify.Client.ICertifyInternalApiClient), internalServiceClient); } - private StatusHubReporting _statusReporting; - - private void InternalServiceClient_OnManagedCertificateUpdated(Models.ManagedCertificate obj) - { - System.Diagnostics.Debug.WriteLine("Public API: got ManagedCertUpdate msg to forward:" + obj.ToString()); - - _statusReporting.ReportManagedCertificateUpdated(obj); - } - private void InternalServiceClient_OnRequestProgressStateUpdated(Models.RequestProgressState obj) - { - System.Diagnostics.Debug.WriteLine("Public API: got Progress Message to forward:" + obj.ToString()); - _statusReporting.ReportRequestProgress(obj); - } - private void InternalServiceClient_OnMessageFromService(string arg1, string arg2) - { - System.Diagnostics.Debug.WriteLine($"Public API: got message to forward: {arg1} {arg2}"); ; - } - /// /// Configure the http request pipeline /// /// /// - public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IHubContext statusHubContext) + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { + var statusHubContext = app.ApplicationServices.GetService(typeof(IHubContext)) as IHubContext; + if (statusHubContext == null) + { + throw new Exception("Status Hub not registered"); + } + // setup signalr message forwarding, message received from internal service will be resent to our connected clients via our own SignalR hub _statusReporting = new StatusHubReporting(statusHubContext); @@ -225,5 +207,23 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IHubCont }); #endif } + + private StatusHubReporting _statusReporting; + + private void InternalServiceClient_OnManagedCertificateUpdated(Models.ManagedCertificate obj) + { + System.Diagnostics.Debug.WriteLine("Public API: got ManagedCertUpdate msg to forward:" + obj.ToString()); + + _statusReporting.ReportManagedCertificateUpdated(obj); + } + private void InternalServiceClient_OnRequestProgressStateUpdated(Models.RequestProgressState obj) + { + System.Diagnostics.Debug.WriteLine("Public API: got Progress Message to forward:" + obj.ToString()); + _statusReporting.ReportRequestProgress(obj); + } + private void InternalServiceClient_OnMessageFromService(string arg1, string arg2) + { + System.Diagnostics.Debug.WriteLine($"Public API: got message to forward: {arg1} {arg2}"); ; + } } } diff --git a/src/Certify.Server/Certify.Server.Api.Public/StatusHub.cs b/src/Certify.Server/Certify.Server.Api.Public/StatusHub.cs index 9d8010613..2c2ad987a 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/StatusHub.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/StatusHub.cs @@ -1,6 +1,4 @@ -using System; -using System.Diagnostics; -using System.Threading.Tasks; +using System.Diagnostics; using Certify.Models; using Certify.Providers; using Microsoft.AspNetCore.SignalR; diff --git a/src/Certify.Tests/Certify.Core.Tests.Integration/CertifyManagerServerTypeTests.cs b/src/Certify.Tests/Certify.Core.Tests.Integration/CertifyManagerServerTypeTests.cs index 74b62fe62..cfa981f6b 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Integration/CertifyManagerServerTypeTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Integration/CertifyManagerServerTypeTests.cs @@ -8,7 +8,7 @@ namespace Certify.Core.Tests { [TestClass] - public class CertifyManagerServerTypeTests : IntegrationTestBase, IDisposable + public class CertifyManagerServerTypeTests : IntegrationTestBase, IDisposable { private readonly CertifyManager _certifyManager; private readonly ServerProviderIIS _iisManager; @@ -160,7 +160,7 @@ public async Task TestCertifyManagerGetDomainOptionsFromSiteNoDomain() var noDomainSite = await _iisManager.CreateSite(noDomainSiteName, "", _primaryWebRoot, "DefaultAppPool", port: 81); Assert.IsTrue(await _iisManager.SiteExists(_testSiteName), "Expected no domain site to be created"); var noDomainSiteId = noDomainSite.Id.ToString(); - + // Request website Domain Options using Item ID var siteDomainOptions = await _certifyManager.GetDomainOptionsFromSite(StandardServerTypes.IIS, noDomainSiteId); @@ -206,13 +206,13 @@ public async Task TestCertifyManagerIsServerTypeAvailable() // Evaluate returns from CertifyManager.IsServerTypeAvailable() Assert.IsTrue(isIisAvailable, "Expected return from CertifyManager.IsServerTypeAvailable() to be true when at least one IIS site is active"); - + Assert.IsTrue(isNginxAvailable, "Expected return from CertifyManager.IsServerTypeAvailable() to be true when at least one Nginx site is active"); - + Assert.IsFalse(isApacheAvailable, "Expected return from CertifyManager.IsServerTypeAvailable() to be false when Apache plugin does not exist"); // TODO: Support for Apache via plugin must be added to enable the next assert //Assert.IsTrue(isApacheAvailable, "Expected return from CertifyManager.IsServerTypeAvailable() to be true when at least one Apache site is active"); - + Assert.IsFalse(isOtherAvailable, "Expected return from CertifyManager.IsServerTypeAvailable() to be false for StandardServerTypes.Other"); } @@ -230,14 +230,14 @@ public async Task TestCertifyManagerGetServerTypeVersion() // Evaluate returns from CertifyManager.GetServerTypeVersion() Assert.AreNotEqual(unknownVersion, iisServerVersion, "Expected return from CertifyManager.GetServerTypeVersion() to be known when at least one IIS site is active"); Assert.IsTrue(iisServerVersion.Major > 0); - + Assert.AreNotEqual(unknownVersion, nginxServerVersion, "Expected return from CertifyManager.GetServerTypeVersion() to be known when at least one Nginx site is active"); Assert.IsTrue(nginxServerVersion.Major > 0); - + Assert.AreEqual(unknownVersion, apacheServerVersion, "Expected return from CertifyManager.GetServerTypeVersion() to be unknown when Apache plugin does not exist"); // TODO: Support for Apache via plugin must be added to enable the next assert //Assert.AreNotEqual(unknownVersion, isApacheAvailable, "Expected return from CertifyManager.GetServerTypeVersion() to be known when at least one Apache site is active"); - + Assert.AreEqual(unknownVersion, otherServerVersion, "Expected return from CertifyManager.GetServerTypeVersion() to be unknown for StandardServerTypes.Other"); } diff --git a/src/Certify.Tests/Certify.Core.Tests.Integration/CertifyManagerTests.cs b/src/Certify.Tests/Certify.Core.Tests.Integration/CertifyManagerTests.cs index 405da925e..9f3f4c649 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Integration/CertifyManagerTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Integration/CertifyManagerTests.cs @@ -1,8 +1,8 @@ using System; using System.Threading.Tasks; -using Certify.Models.Config.Migration; using Certify.Management; using Certify.Models; +using Certify.Models.Config.Migration; using Certify.Service; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -128,7 +128,7 @@ public async Task TestCertifyManagerReportProgress() // Execute CertifyManager.ReportProgress() with new Warning state _certifyManager.ReportProgress(progressIndicator, new RequestProgressState(RequestState.Warning, "Warning message", dummyManagedCert), logThisEvent: true); await Task.Delay(100); - + // Validate events from CertifyManager.ReportProgress() Assert.IsTrue(progressChanged, "Expected progressChanged to be true after CertifyManager.ReportProgress() completed"); Assert.AreEqual(RequestState.Warning, progressNewState, "Expected progressNewState to be changed to RequestState.Warning"); @@ -159,7 +159,7 @@ public async Task TestCertifyManagerPerformExportAndImport() // Setup export test data var exportReq = new ExportRequest { - Filter = new ManagedCertificateFilter { }, + Filter = new ManagedCertificateFilter { }, Settings = new ExportSettings { ExportAllStoredCredentials = true, EncryptionSecret = "secret" }, IsPreviewMode = false, }; diff --git a/src/Certify.Tests/Certify.Core.Tests.Integration/MigrationManagerTests.cs b/src/Certify.Tests/Certify.Core.Tests.Integration/MigrationManagerTests.cs index a7d1daed5..f31e480c2 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Integration/MigrationManagerTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Integration/MigrationManagerTests.cs @@ -1,10 +1,10 @@ using System.Collections.Generic; using System.Threading.Tasks; -using Certify.Models.Config.Migration; using Certify.Core.Management; using Certify.Datastore.SQLite; using Certify.Management; using Certify.Models; +using Certify.Models.Config.Migration; using Certify.Providers; using Microsoft.VisualStudio.TestTools.UnitTesting; From 08d1b67ac20c0498d051bc66a5ad5f0ed0c2d9b8 Mon Sep 17 00:00:00 2001 From: Justin Nelson Date: Thu, 30 Nov 2023 14:56:23 -0800 Subject: [PATCH 091/328] Fixing file paths for tests running in Linux containers/envs Fix loading Credential Manager in Linux containers/envs Fix loading Env strings on Linux containers/envs Fix when there is no existing ACME accounts in a container/env Initial implementation for managing certs on different OSs Changes for testing error message for bad Cert Store name on various OSs Improve error logging for Binding Match tests Changes for getting debug info on non-Windows systems Changes for making Linux and Windows Docker Images of Core Unit Tests Only the .NET 8.0 images are currently working as they should, there are some issues with the .NET 4.6.2 images for Windows and Linux Fix to generating debug info for non-Windows OSs Create common utility method to get Environment values in Account Tests Remove unneeded changes to Certify.Core.Tests.Unit.csproj Improve path strings for multiple OSs using Path.Combine() Changes to Linux Dockerfiles for Certify.Core.Tests.Unit Changes to enable using Step CA for CertifyManagerAccount unit tests Fix to BindingMatchTests for bad file path Windows docker changes More Windows Docker changes Fixes to compose files Documents for running the Certify Core Unit Tests in Docker Fix to formatting of Certify Core Unit Test Docker.md Cleanup linux_compose.yaml --- Directory.Build.props | 4 + ...ntainer_Debug_Attach_To_Process_Window.png | Bin 0 -> 32123 bytes ...ontainer_Debug_Select_Code_Type_Window.png | Bin 0 -> 9962 bytes .../AcmeDns/AcmeDns/Plugin.DNS.AcmeDns.csproj | 1 - .../DNS/Aliyun/Plugin.DNS.Aliyun.csproj | 1 - .../Cloudflare/Plugin.DNS.Cloudflare.csproj | 2 +- .../DNS/MSDNS/Plugin.DNS.MSDNS.csproj | 2 +- .../Plugin.DNS.SimpleDNSPlus.csproj | 2 +- src/Certify.Shared/Certify.Shared.Core.csproj | 1 - .../Management/CertificateManager.cs | 107 ++++- .../Certify.Core.Tests.Unit/.dockerignore | 32 ++ .../BindingMatchTests.cs | 301 +++++++------- .../Certify.Core.Tests.Unit.csproj | 6 +- .../CertifyManagerAccountTests.cs | 366 ++++++++++++++++-- .../Certify.Core.Tests.Unit/Docker.md | 169 ++++++++ .../GetDnsProviderTests.cs | 12 +- .../Certify.Core.Tests.Unit/LoggyTests.cs | 22 +- ...rtify-core-tests-unit-4_6_2-win.dockerfile | 29 ++ ...rtify-core-tests-unit-8_0-linux.dockerfile | 28 ++ ...certify-core-tests-unit-8_0-win.dockerfile | 31 ++ .../linux_compose.yaml | 37 ++ .../step-ca-win-init.bat | 23 ++ .../step-ca-win.dockerfile | 21 + .../windows_compose.yaml | 57 +++ 24 files changed, 1029 insertions(+), 225 deletions(-) create mode 100644 docs/images/VS_Container_Debug_Attach_To_Process_Window.png create mode 100644 docs/images/VS_Container_Debug_Select_Code_Type_Window.png create mode 100644 src/Certify.Tests/Certify.Core.Tests.Unit/.dockerignore create mode 100644 src/Certify.Tests/Certify.Core.Tests.Unit/Docker.md create mode 100644 src/Certify.Tests/Certify.Core.Tests.Unit/certify-core-tests-unit-4_6_2-win.dockerfile create mode 100644 src/Certify.Tests/Certify.Core.Tests.Unit/certify-core-tests-unit-8_0-linux.dockerfile create mode 100644 src/Certify.Tests/Certify.Core.Tests.Unit/certify-core-tests-unit-8_0-win.dockerfile create mode 100644 src/Certify.Tests/Certify.Core.Tests.Unit/linux_compose.yaml create mode 100644 src/Certify.Tests/Certify.Core.Tests.Unit/step-ca-win-init.bat create mode 100644 src/Certify.Tests/Certify.Core.Tests.Unit/step-ca-win.dockerfile create mode 100644 src/Certify.Tests/Certify.Core.Tests.Unit/windows_compose.yaml diff --git a/Directory.Build.props b/Directory.Build.props index ea837cc5d..8c52450e0 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -11,4 +11,8 @@ true full + + + portable + diff --git a/docs/images/VS_Container_Debug_Attach_To_Process_Window.png b/docs/images/VS_Container_Debug_Attach_To_Process_Window.png new file mode 100644 index 0000000000000000000000000000000000000000..c2ff170017401d9693cdbc7408ecf76451f09ad6 GIT binary patch literal 32123 zcmdqIcUV*3_bv!Z5fM=Vk*=sgdhbnCIsv42r1##WBZ#Q<4xvg3y>|jZMS3T62%vNV zp|^xG2fv?Row;}Joq6V;;dyvY_DRk;d+)RNTJL(-yS}I@%aYutzKw%}Ln1HtMhyq& zCKw0j#@ky2z?sx{2Je9buA7?dE1dE{+6|zAXC(!e!ojJECO$X82iio=a=LCfI1k*f z|8eIn8GtMN@5#TB(s*yQeL?C@JL$i8cKL!ij;)K4{cSZH_PM~V7;e02?uE}}AE>Rw z$YSuS=I;qV5sP=BkNtH+zD@Um8bJWGyw#Ey-3Rj*$mVfFq@l+Ma-E==W?@KJ+7pQ}Zcz0I z^l;KF!;G+23=#u<#(6y9feyU>@bXSD==!+(*!l)=xQEKR+|aeL#RSb^qV(-2>;2OV zV4edcWo!)R!>#+{ZP;Q|Uw*s9VK{@kf2b~(HD;JFEw5td?W8un$dxPS>xQTO_S>00 zw*4fy*SA}nw#rHjJfX>oxx!+H`m!!}0yzS$ZbVC-jWWQ#C9X(b6kLG=)*3|3ZZd%p zCReB{OqPLyNsAQX1bz}RR#p2O}CcbSRL0(PFW?Gh!r zLTvp0C&~TUU+m{sBV8CbO#6b#Nn(Xx2f-baMKXbSgz{R&W!Ke?)LHn|0jC3mL;Gg( z3p$ko-zzL?O9+W1on2g};vI4}x;hJI+P8%l9AvhNrRg1OUv5?`BeE_d{7|!Gp0Ge{ zgJ=F~QQsKUuNf_(6d2ZCt4`f$^NtF7vAu9fDGG<8L_#;3#SGULFxv~t`8H*g*Td+M zIJi2i^}9TXp5ob2NO;6&Goa>P;I|)7x{AjmK~4Lyj#*DM@A7izYJQOEj#kcP!PS{6 z%SCeFnQ`w)+g0G~f&_KCbtT#8qH-v|$TZ(O#L-B$+|9fgfpOxH*!hvx@?yks#+^=R9iG} z`iuaqS3^#MS8H6dvi}ViAt{cvFBXut3rkWnP=)638-)4{`OYr|bc7G;i;^tXA{L^> z_Pt@G{3(bN zUBLgsgguJj)d+RQ)@7EPsAfoTB~(5udUTPs2xR?)`cCM9n%>iVTzR=k{j_6t867h6 zQIBztiFZuCI_t()s6cjE4A#K7{P*uGUQK<9QOgKDyZkzBU{Eig`|j^t4#ERk;-0r2 zek1>&ZaJ z@d;lFVxv|-G0V=UE!9e>;_56A<4=>#_K83iL#rk)%e$kXTWIR$1b zaY9TwjeRp*z?}x@LXd*z=`KTe)@4stBfOVaQj+tk{c3l|?FLl!k^Rn)R_V>J75a1)u_mFI&>!aBJlcw4D+}vCX zmgM>d-LJr8*T?tw{yrm!Q0o7yW>;}jQZ3XDgJMsi=Du>IlqGej%#<7%mcAanOmROD{Q$wS6V_No$=p>$DfZo=LUh8bZqQI+$6yQIoA@K9otwX!EV~vD_s1)3ZXI+n@OB%aWlQF@o&zD*Qb@ zqCU0t9TyVSOm*c{1Ko_MqwlfI$?MPT11;5mltSn-I`101yE34U{Z>GqKnKTRcDZ75 zh=(@x0PjSsc0LbAk36f@J4tDYg7H~xQU=|0W;mO@ zGs!pVc*lZla_=-4-x&o@PL4AATYglYeir6&9&eXzjl>nmd6{7igey*u0e zcq=iRKRea9qMN&cO}Mg)oz*7o9dQRL1afcI-hPui)tZ=in)+U8^0%lWjhgC+nFpdR z;5P+zH{Fe1kKPx+r@b$nz>*;OoGg+_3B%nJ!H^>rqsN3w%XFnsv`iuTHZ$Q6Pe-cU zoiMn$#q9m(2c7Q0t<=Y+u3`GeA^T&mTQ`CO&1qCpvbTuR%HtX&bBnTd(Z4R@Y(ds` zUA12pr;WS|ZJwn}xTj*fn%ds>rx5vJjXN9=0>YM_=n#_{#fD*rt~oLHDY7uegU|ch zxnc*78APU85Wc3HI5@@Qk%IFt!tT<=Nn&y(dvB#5Hb6Hj(;S<7@{^R$f}wkgJznls zR!ZM^)W|d&w0%UnR@7{7klAm!-PE7yPl@M=|4Q-T)3kRpgsv=cTG(3aNB{7+&k^~4 z3&7kd~EM`@YIl;4gtm?tnas-bvrggB~42rCyuetIW%^ z`f3sUnZAK%)-$Ed^+QtmVwtiudnp&$JFQMO>Qeby`{$voZI^pG)66-i z>@NNz_=-N!EV-<4jEUWs+J`J(tQb}@yhujhw^@k;K0@<7c6K52zL4}y5S`XtV9RaR zD`25gUo=YDVwHu9_<2l{)kA92^~@K3%j>K2`POQKW*b0w;x#+Nw48vA9wp+3jkx*RjLgIbu4fULMKwATiHkKj(V9U(5@&%P2U zuiQ=1NVSD*2CrI+)?_ZFinGYpwUrxDKYyT+dN}_BKl6j$=QJzj1AfZyD|y%ivx|u4 zvTeq8+2!s^IqZaWHy=m)+8jnYi%@X}N^z#7K1ItmI;OyrO z^edi+F;?%PV8!WP{I4;+sdp<+Y<5K2rxPeJ~UU!gBIJi}ZJ@fPWvGO!*l%E^E zo$QfQH8WiPn#aUg8x!1g4ql8!R>HPiJ-v z18|{CD@B!5*(6^bY%g#3i0Zqo*}2{VR_hl6`7lz3dPCuxAWitH_sqwB3F2i)l1+oG zc?pS_ULL1NzeEGVN{#YqN&)Ruicx0dloF2DWgef*XvoWQ=@}72L_1qXd zhnmo$vWf{czM7V|H%i~Y=AXo3pfxH`MjmUaJ;@N)S+rgw_+$1Er1Wk6Zo^RB$054Z z8&~mYiWSe9IY`1H#h7K|1zMGhv!-(Fl(=nX4j&Y&{gu$>B(1T3d{qZy5P!zG=M7@0 zF?uSsn1ibA`)FRY>g|}Z)UZ(cr97SJ<+WVrMQ?w9M9U=x^$%vDacX(H zi9)R;={veGcRcwUOX841uJ4NTzX-Q>dtEd>PEYGn(a^+C8u(*H5A?>QYn*hu z?Px-ugJN7+($;s@p`nLFaBflU>+9itR)4@YEIyaLv|42(HQ zQZjwf!c9w2qFW?$sPZMEB)#k(J3v6W(m`pQ0Na84zWqQZdkw#|_}Fz808Hn)mf`h) zYy94TB1v!^Lho0{Vj=MD_pis-(*)tSGhncdBmmEBxpMc?@(uKku-k1(S;N?6!3v=e zaTfgv``BCxSTJzOi)2(q+x*3=vXK_UEjQa0Vu<~{>bD$6{XKK(sHiXLG#>5A$U=A7 zki6zr*8p_O7W;I#X7#GEmSR&Wiy(qCP1_;ql4>&0vY}M3PC?57oyzfgzONH)zczC|wf9I$G-w4>^) zV50J<9p?)i)rgwvH4MrDQB&!=*liKpC%gOs#c=OJ_fdv1H=^v$U#JsO>L%%y8hqKA z4eAw`g}v7SdCAn(ty*&qBFfYB7p5G#lbXtZcs{B^SXQXd)e@mHp3Z5P3+66dprh@g$fGD5 zwjLchj6Qk&Ac1qJyXXVQ^!)@ew|*tCoZGXwLgR&JW<>c@yfC*De_INVh3OAxj_MDh zz)=G)pLBtOI?gs!0$Bi(;sn@RbkL>JJ12c+>Q23BP6Wt!tkQ-rQo?(GfJr(q$Gw-5 zFVZ;f*Gmsi84#FTQu99}8l3+?G`G3_BAS_pWugBa(G0z|3$iDPowy_I5O-YWc;P;s zc2`jd+uSN{d8s@qU}?7z-*n)cF97!~3oRs|+Du9Ep53xAikf{D&V0ysM?TC!z^oa; z7^a4w3*bhv-<^`|hNHB66+|F+y_NUoQ=u1S)mQJ5@1!eGW)7xP$CQW2gLC~* zQUyc-*4)&$)|s+=)}@93?(n(SVDg>l1E@HH6$vf~;e#o2BUU5G*z%pB--Vmn+1nI5 zn|%lLq&v#I(tV}_f0Bdic5QE0yXu_`{Ha5KUS3O8=NU+kRHy7xyqISy(g5)qBa8S5qE8+S-N28Zy(>0VUhiLB(6^|%;5A8)piRun}OjbWSdT}!@=FQxjU0?Te;k~TA+WNhfC&=pU<&@ff#h1x^E z+jzx%!w!~?Bv@5xZNd3$U~iNggZHD<;nbh-Tn@&!(^Gh!eC!$c7v#$YAtawt^L8*i z1&|(Wo;Iy&`d3wIQQ;BcqT5oWU@yb@4~A}rixCD3JY|eN!hH&G1KB#a{_n*Ml#6!wy6j)i_bFx`f844Lt)04IG$&+%?EIXvGNqU0 zY)nJN|3auAtds6SKx_#RPO zln`Ke3c$a%YF?Hzr+WInsh|0tD~-u%-*OYdhz*(I9VFbOLG;z!PXkEgtb|TEpL^YP za{2{!n@RVli_}VuC(iYcdrX8M(|82$#Rg5!0Mdmb;2S?t6fyB~-(7&XnV&nB@H z;4iFxn1f0kq4MQTM=6WoV7sS2`GQrTpStf_)mDmA1B4R}X@8m4n|!cAofyY#z%5gE z=ZuWsrxV!NM~%H%4?&}?n5Tb2%W!X7BWl)bo>19?-!ymTH_Rrh9GTX*X+JWzzWSKH znbH5^7G^4+zRDoNde|%QP7(e>r==bhNhkhL`us9;mDRp%6L|{sE zXhU?Y%+;@1 z+LlWZ*w0A$ga%E%VYQo?a`}_uFlJ@*(}8*OsM~w%YGR28$^DGPF{8fErYWXY*=O>y zw%b#*T*A!tN1BSj@FPV^qq;!TcR6oDb)yDwx}chiz_jr3$zjgdB%NT1lw2mvWjy%X z0Odp>XRHo)+pjbzO?~`gp5gL0LvK5&}C377^1h0MblqVcG5G%BKC9QJ_fiCh7Ak8qhO(EmianANPzMGC)g2`@6fyq=K zU}!(XkzZdivycFt#_5avKXHK+Qf+hoE4>tx0Pm_wtp^`={WJu?F{?JWk24xyTUMm+ z72c|;so@b209CXeLlgllsv|~{0!d5DFXuQj{KeU_8$9QOD%&sjYVFI;pK}|U&fWmVUf_=}Se!M%W%E4`iS$}v7o2krp-~h1n?Wgk zPc0;l-Kudt+SE+VDtK=|%y(+o>Qm=GerkFHB>fLV*K+Hc3jE(>G?TcjA&Fi36WtL0 zay#$2JdMKXu6zq{xR_BDYf5&^5&d>~x?kbM1;BL%QJC0BIF^L|iOfp$RR+i}MUm~j zzN|Qv(2G7>jpj))MiggMZ^5R4ntY<~W$5V9H1mUj=;xX?Sb%N7k*mk!2iNSMqDzD1 z$2Aqc*Qz0zP?Jqr*Ij;)C}|B5XKgp$Nf0m&GEcM0PeXe|H@oXU9bt@?L_^|I%b$;b z>J#kmPO)mB5>1#^s`TY6^q@+p=(7@p@WGq~cbq+?%Zy+6UPjNZ6s(z8s|(L64GuW* zwYHqsGm{(c;kejN zZz3yNyS&bBl0dQ+r$vAHbiLpi{JECuXJvj0#Gc??MVLeL%I&EPCJSXkTW1s5?f9h1 z-3%eN(eu7LcB%7BmJ~|gtKWW_<%BGZ)b&tH=1S@)|3*O%ODA|O?H*boX z08b4j$CmpxH3xic`cR835*D`t6?Qwrp8`$aS?^^?IHnS{e;o`F!x*YkG^$8gA;b|0 z=7HC5?L&)c5`IFPw-Hjnrp-xJMh#q=q)?Eao<>GSPGBhGTr~#}A#0gQvh-Q}d%aKh z-Ldr_^%`|k173TLP0@i+W5)O-{! zL7G$x&R2yxB=o88A7LlL{TDtWAEs?;mwQ|XhF@#>7Hn(*OctIMUjSYtm-yah+0ZSZ zf6p-%zRra`SMWS&dWCF94mtM7045?JhJ zs#x)}!&K0zGuuh|PIPyY6R#g0CB~(9+_Sb^Q~h?W$yWg_KhBrv`*+^*)i13eF)uDk zqZAjZ=z#uY=M^udf_@<^tyBV)kKcw~Z&eMxVCeHTI9dL{U=&m9r|X}+%+L0w2aZ7y z=>sv~i1UK?zvNJHNdIZ-BxTFDsU%=t+U3mxF^PM{G^4B)@-sfbjkC)>PcdloP7uG! zvD#V}qpS&NvW#}DVt7C9A9t2934|`5gbX1WKzR5M{cgFko+Kxcw_nizFY=uyI%~a3 zD_@}{I!-zomM==+cF?bLxDXR-(1>xlO}{rEuV|2Q@nlK zwW>l|Z|0hFt#+*8MKN?uCK zb2-(Ihr(ZdIeDwNAZ^1iuf9T=Tk5aP-t{*2(8>0E)MAqI5gk>+V0}_P((^)eirJOt zoTjl+%5rPr<(Jz+(hiF356k^#HPG8-yLMptl_t)Ti*vP2J2`fvr4JOA`20h5vGDLP z3&i4Q;T7#q({AmI+xj6+{AII`(?C{g{FEZ4d%GsF8sD6>f=5bL*hKy<#5Fbj$>+vB zrJu-fSQg(Z9_hL$SN8CuZOkRu;=DDwxBFdzb$@LhhjH8e7d>sKr z_GDTGDYx6d}K@9a2!g6_G14f;Da&*zs(J172v@VMsbc<#2X;)8xmizY#m zBgZtVhj%|4HY`RNc%3B4>AgVV6`?@w59Zu&e{z`88Kr7*(F5Y(~gY8F-ytIK6>C_Uj_677T%_6IdABwddP#ykWTD z+;WIS{yB~n+~MKyft#_LEReUf5|i+nP8wQNgbXtIE;M$4c)>^0os19z9Cjv^m~Kj5>M0b6!{&u&^pyayrRuKxXT>Uou|Ej!^6%n zyw*dxH)4<__j~fdR*j;A8^S@Y37foc5jBMG%qZr_3Q_nfA{LMpthJ2F%au*SY#iaUN=wEf;jPbAZ} z&K{>2=YjGZD-PY8$si@qH{`8V#afseaq^WJ=RIZs>zq7An=6@*q_3pgdlUkHQ%B9?PiUX#W_aI6efB?Vs;fa-C7d6sp z6y5N%O#cM0`LcMDG%?h-xTp+i2d57x`1d`U5{O?yp2_m80^5V7K9_r{f z0=CMTzv*1`*J17){*3y!i!AYSd3dHd^z~~O@Lo1wZ6UeoWEnKZMfH8RLOBZVr69%f z4QiRCHlJX_UEjwma>~KIlMO|6h!mXX5*6h4e=>J)7ydkpkb(9k4f*9ftjVy{?dr!X zg0<*;=V4grSoO#^wo|Z?ANrwLo&*?++Zy6h@dA-?KjiBCzx^!Ulv8KrZPS?UNI&&$ z5Bp{KK{7x$E$?E=TbMakluNoyU#ToqJeDpdgD`kz?=g5*j3MS`hd~g)+TVNthrK(t zcEWIEJ7J~M_%{p(jZ$kli-#}esf1nShc=JVI)#oq-c8o!aVBJieEu%43hr1gXbScT z7hlY;P(H@Qw6YgYjh$1(c^#K$R`h&}b>B*`_05f&K>D78UUXn6Ae8eKNq-(`{P4TG z&Y(HrKFzb18<-UfL;F}wU9i0ul^x*D(&KN97tM5*ovu+rYRd=qz3o^vgCjIidl~Et zN9>#DY9^Bq*R^T2%n&l~OV3{Lq-0hbIN$s2_~d&x`!4Yc{L*`x8i+B|1-WM!7VWa1 zla4?18Dd$k_*<)go7NgSa!ER{n1#&>jGN3_J18s}(V&tNTMPfi%9f8p4WREbE7`*dH<+x-5{ zUnG|V>~>hIggh#<~9E%(Hqv7XcqM?zfq|+I)R*%pBb+j zYVwUZ@&{kjY`fi9+vcf0XuqDTFTh-#t(qNIA7qsKMDOI(3+}Xd9@L}My(b}~tGW|# zgI+gh#)SptrqrV$l#}}S)b;Y#0=Vx1*aAMxb>(ZRA#CF4+q)WBi*O>wsI&Rgu-o5A z)o1(v%u?Y5z4tL(crWj>?<0jDl&|(RCBHY*rkES!_ZnMi&vsf)oitEGD7})rMR*6xj% zrtjF)k^c)(-3<)DJ|3F)qUgrg6Xk|>c2Ly|zSCBpqvDp+KjE$?NH0zn=RcpXNB$LxkAk6_ z711pR$~U0YIo?H5m7aRxjgM$4fUk8%YV-U4OHSlpf%cz01CQ;|ziAH(Qiu#}vKxX1 zG=&_wqpg6_)h4PASY>C5LZ>QA`U5dKg_m2nZk&JdM$0;XQ;xisY)8%>Fk%N7a zWE<}8z6R#`b`8%x@vlL1_j~;)NJ75G<@TMZNayzrsk1-b=l8HB8acCsI4_TworcYC zN4Caij0#Vuc~teogE+?V6N$aX6~vPKl0@2SlC=L_#M1UI&0Kx!II1`H<`UW~k^8LC zs?m_+xUTWz3*>x;z)QJ$07a+AH#KD=rfUyFFPQRqdCvWgqZ*{PWSYj}GXu~m-A#Tu zU*lMIJdNOd92rdQF4$*|DpqzIuov6yzCwaGaALAcr4U<9Cb7r<)fI;3r?lE;g5(JSf|1$*VUzqVGebojKUcODoWMWYGF+oQtABjW@V6yU&M7SZ>I{Hm=n` z@LMaEmbYyLZDHLU@?O3xuD2X~8%x~3%Q=uG#}VUt`O>KZ-NxMESE-(Lm$?ow}J z9L+~BWV%YubFiL~(2kuk2z-3G)4_gKsQZa#b%B7XI=Y@#9MVKWh2m}*Rj8dyd>;Ya0{IHN|vUS}YW!{#$NMNaM` z9c6>*QKlMtVO_knviT9pRm5&|H>^{VUvWZdy< zlir#PkLr^aea;%wo`dMO+MJO~$x0j+iXxu=*nTgi1j{Sj?_YVvnN^SrFG$L@C${7L zXXl&}45x#k{gy%=ITU#K{1?NbyY=6*y>{rl8XB37pWIj15w922F;YxpZc*;-e>M4g znu=Fz8SREN+Ss5?R@Ae#zRtEd2Iy#oOC-I5pVwDMe*Uv%$BM8#LCE;0xi`5o_fl&bt$ND&wjd2F)MwKXz-c zW$m>;;C4VKT7l<--5ChL95q=m0EULr(#T_1(G_Iw~t zP_0Mxjyzenk~oOWfy22O&;pv2#Y{Y`6Mv!tRwm31i|cF>V|=Y5{(|si#kWeQsrLb2 zWYnErX!_3#fs&bLA;-6A_-LvIcwEj%2B#*Ca# zI!1SE`Qnhf+CSS0Gj*y>a2Mr&>4uoL`dV%5UD0Lo)6?cLQpc&Y8$g+uRz8kggh%*1^$pu@1|P-V%imO)V+XX-&9k*f35J0 zOwKDj#kL!^sS_-`MmQL_oc-sBoT1x(vD3l50m3i zfXFm+&UdG^yy8`cIHN4}vAimCf}_^SGTa7xXY`jDMpVx%ZwN#O$ZePg_-VdGhH$XUr=Cc!h=2Ip52 zrM?5G_T_(-#Qw#mK@{77rlqFhw~Pae^!z^KEe$;X`@aBb2jx)^KTnl3>1pP#f{ z(p0Y|0dR_=)f@YtQU^=)|21_G<9CPc<6qdwnn|EMB!Mk;IOCC2*=u}gy;~wxQ*W-P z^dCZKN~vpD`M;>7`QK7Gv)K&u&9ur)xJNg`83!ad*zNR|_jI?Z647Hj&)_cmsn=BJ zcZGU)sdyq!dhcd+k*ol77byi~fw{Y*hJ@xC=9{O1@6 zn`pN^Q-@CehJS`+?2?sy?op59-?V>Y72}#ziNZRn{fp9N{uGXn%Ud~Aif`>VokoD#fCD?a> zKs9XX(}Rg`zvVh5s;iInvx7G1jOni#v)i}xIt`OW`>PSZO55Q!X^sLf=**j3mPYp>*VCbcyC13R zmz3>2Ur2klHMxSyLZ;Z#*N+_bEq#<7Fc_dML7vcgmVw1)3DVYj+_ z_UQw)pjn=hZtl$+hLq~O@jZeSrX8gr{%0NOM50~=X_FoKy57pyny|;5D*#rP4PLb$ z32q^=C9wEKF(0y)9@QQD+jkjZ0`6X6KEwE-yU3Wotp*3gdUs>va5tis5qiC( z7PXJFZQRXO(ZqJqYMbWzSbv_BC`cZX{FrdZMp01PZkj18eyCTbEMrFe`3ai_Vwvtr zqREP-&_cInuf}z#r_|C#EBw&#JY6nNhV3#S+8Fv95VN*|t~1Q5m7gYVHF(9miWY&I)2Hi{|~Syl2r)6uXY0ad5`N|H)7OEd#;H zw){_0y6f-%y@tW+y0oFVzkf>+O{wa;i9$QKDdduK6JM7kyi6R)-ncsZ?K4R@78Q8f zsU%GR<{P@s@%(#E;a_E1s+D_tE@wX};g7Gi!Dd}iJ@G0M%ucOGNi=%;|EiWC2kM5F zRhEY?w}#AJUDe8sT3OW9)%RC81E&d8>K+N*zb=Is`{>leAbDOXM!VrNB+0pd?!DpP zsC{U~rF=^BB5H!`qFwSlq-EQ|DSqL6U?CxdkhGUc@=V^lOLYDTFE8)@b$wB!uIS$< z+IGS$^oMfy*K?&NYgUjToA5)inWq?DGF`5+=lEQWhQ2KV@;-y)}4C8<_@9S@V!WwfnN83 zQ!*9m4HpvDaD>Ni;DWH|H>SiEuAAN*>N}e^5=Id~0mGtxZCE5uBST5<-0Aje!y0aE zZpIm(h8jBqf$W&waY-j(*2R{Up)J&HZE%{y7Bx{;ukKes1iT}QxmIIkd`DW;4C?C`sm9A9f49Jpz8!|wWYZP zLPM2n_dI<672mqZ9!dP+O~GB{{x@GY`me72Jd#x%=OvO2-ASa9oJBo3i0QLH9f?Qf z>h5!EZBG|`F59c0V}F$7c{Cpw?xUU3ZyCAY|9ZVYmRFpvj-6KQjIodlaX~?*i;D}- z1zQpXX~6Ey>$Eyp!m+A7{(Q6IvX~?ZD4`k;`nzYM1dJ644W&plo+#Zr494p6aHGtP z9rp_TUcnQ}--avJ?5EQcj|OdUUjE@U$eMzB?|2XSZaFzz9*;x^XwdttKWW;4rrATH z2^7a4a54You2Nq9F?3D#dpi1z(|D+_0loxv;g>0z$$KGqud>xgA6zubsrgwqE8@&f{pY%v z$N6C&^B?2ock3elyR-F08H%@z(7R=lgJ49~neq-Mh84R)GD0bspqCH775w*2_5LCR z_dNk|y&4#y201_qdM{`nIQ=yUsvgCA9VGcOe>ctdk{P2pRolNT-HW(@oi9-DLjQj_ zyC30?fu&g-0l#&lP_sq++HT_{-TT)~f_R?(qtyK;WBD)Z^52Zje{V)VziQl$%?&|c zSN7p7&one3F7#T|{N_A$gQ05Zzvi$mcWm#KCLy>)yTX&1b&|EBh4E@n@>2E;xo}~4 zZ9aahX=kow`YNO0SjWi**V3_Vf}{t5R*KR7QH2T;^g>#!2q3x7!ejj>6>5=#i$Eh` z+YBXujP`l}+57kS*h9G9VH&QE7ZkL$lh4Dhb{TG^178K*mL9JC1zgZ2am<}oA8_Gw zgX#K;SNHkYZn-iLTk;MX`T|5hQU z@ov~%#ZP)F!>^f&A~xIei7`+YL3o@UUWf26>IeX zWfVg)@d2fIU~5!I2ZK(;ueO>+(mR?Jx)7#%MG_-M+pve-4dLnafnm@z$Altt!pM_%w5>!RqCC{Mw=SYNCSuC0In4|Hk#U5C z2^N$%ZyN@=*V(Mn<@d9Uk5xw4{48ECtX@pd+FQ#5huOQ)o-}xYF1!*)BBI+G9xyLA)2V~HCn}Ba3V8IDe`Gm} z06~gES61upLN+E>@Gh49zto~*%Bwz}_rOZEK>w0Diu&QIb&AML7Q~npRxS-&nvA)X?L!Nt0#BQr&LGE;OPM-?7Hzc09zPf@2*Dq}Z;{`h9-}glxZ7*1-J~l$R zIP}u3YC?gb-3((~db5wI6$aH;#2M!F5n;NnC8Nyy0WODn0_=3JN$RsR?z=DRYq1ts zv+LY7?8@UQ>^*s;b{35iw`B7Ar4-+k$z(1^VDD4q*H^UN7J8cmqV#DU>2^RkHjI2$ z!1^+rI}b2PTV#(_T_W>_5aD8h7hD}207!$sNNmVc^h5G@KEmiN44h7VSyLNP_O?^zG{YdotsO8Lyub*$2zP|oOWeB)Yg&1^oK-HxvunlRpL zW3TqW4N-3PifXL);8FGMN0VOtpO{Lv%xZ=G>1(1}@g&2Sb(JHp*fq|>ANzSlf1OMW zSXUKo)ik+#OuTwRL^f+@IV(BS$fxm;hE}l0$IDSlqu=H3JKVBSgrLbzjIClApy6ye$(hw)sj5TSz35e1&>XH;^~Z9?$BIvCncr?c$3u)Jw;Fjr-BJ9x zd_fdXv;mnH>$6=^`!aB)=-Hp7=m7KtSpB-2lW7S1TS)hF3}}d|vKV${UQHQ4qD3WD=8Q`mKR9PA zqu6awMM}kMHNiFw^eL=o)jr`{m!Ul>@+M4*L?8U%dm!IHIw}n(%Ld5#k;C@YG*4oGN$Kxd=pGqV^lNC29Ezhht{MLvA)@!>5D{W)Q8DC`Q6XI{g{C*`6 z*P_nVRy@;IAVn|QkkR4TnPh?+en$@^O;=+nj&xIN&V=Q?j!RpU?$gob^wv?`zG5#0 zRDNk*ZN#2{K2R6tS%&Hx0z6i=lT$&dQR__pyL?}C)7ml`w&Ur~+NFSWe8)_I^@2JT ziqt*zQJ2}#Lq$@;x z<9$i0)}R9yKdGRqE|bfy1_d6OpdHWI66vVL8MQAKHw~S73+7@d0%w;UaLeRb3w9?c`oru)0*Q zH`f#8i%Vi2Ny)5DC>E06R3WR!&sh(+q7A@_`6l-z=egr_XFXKC%dZS`8^WJ9-XYMj z%4_iLzmQqsnGFB(ntL)xVX#eWdW8RR!5@XB`p0vR;|URyzs>-^70wdM;nmcMf3M(8 zvw=eX=YcR@^-&?ZEiA(F^a;{M*oGv$zx~uK+e0?_&Koi-sQIf-bL(dr-n@YBd~PZ` zl&LAkZC=kY(+s9?kMG;p_0V$pyPLX;{mfMBGbWdKnm{Fpyz?J91;U_~j97Ite^PeJr)_!bO}9FEopKFFDI~povZ%wgNrvPxk8sNMlOrhIkB86 z-+!Opoir9UJ@_`h<>aMjZS8IqlyW_bTZbf(qhj2N#fcCqsyL7veO%85m$Xuxd#$EM zUc!@rHjQY&R`Zl_sE@}Ayo#15-AOp~g;;-{p_Gy5M!D#iBpov{-e>w3EROkqmX5ODJ6gP_dSpUzNF7Y@xev~FN zt7dQ(1|sd<6PS#C(6@$RFyaicyFw#p#yBpiXQtd#A(g_EsRjw)JfO>_0Id zq$GJwmnx{0W7Dy0a!5-q*b425!8^eT#wn_DXyqeGckLRfRf+AzG&aA&cVL9}@wV4Z1C<^R&cAd-8aEO4?h|vkC;AcMzx0rokfgauwgD0Z zBdhx|vj)7alZBm5P8G^^64LD|z=sB$UFJD^ALS*{dXLQeY#-#|YpWmj!xL)hy$&W& z=PzqjlMU)WXUR1BniCh(mHdLZQYWbO3;5jG52CaKX{raIn9`g8Ez2Y!l?!M z=r^3bsG6%JJ47jbJ$D*&hey?Ln|@8URGc3AE+0R@bw(wPjDqw>U0R0sr~{QO_$bC` zW(GE>4a;!JTD|G84YW0e!b_Tt!WH|$Aj&VZAf31M+m_E>RV;84!hVLxrb=RVUrjW( zM{L|>?7JiS`|k2O*Me9bwL9SG`Or%6f&lhOC*0xP}VYKxSYuE+G%j+{~IN|r-L!z6V6gKb}iAjU4 zn+xoQt)R@6@d>tH54R7fpy1?ZRV`cvVB6MWx31AFb`G}EEuN~dI2ISv!ly1oJ^+7$ zRsC05HL3})wQV49-?~Agb zJ5w7MhK$ojWh@&NAG(ZD`7R(haub;!y4S(l%1!a#s)7B6IfxClYedXMltcaL%-|BLFWz+VJ?QTN~9oQ}9 zv6O2~RbBsR`xN9NW^?(<{jXjkuYi?7zL}`M(V7iq0T0fc)qE<()7}zX>^bN^Ffoxh zCQIRc5&DM`KCQP;BG7vl`6fa-cg8&wSXZax0PL7I z)tQX^R_*HE>q&Qv_q59W4rO(FIp&F7cQWBvc8H&=l7ttGRILTaj`J;|1pBnM2^`>y z)1%fOR+n70SHv=2^YIGu2~XxvVyVoxJa_Tu{9`fi9Mq0vTEo*M)h>Lc5*7EcX%;Ey z_s@Yd?b`K_Lf5Rcv{y%zquj6=1~Yg)-Q6HkO*xHyPq^BCdgl_g-v^1uzfPZyeI!A+ zUFb+xoXaT9ICdaTZB1!b$l6$qg3Q9W&p7)NEI`>b2$MlJzw~^|0Ct1BeAMbo#h5W| zcinFR4V{|oB!BglvaO8kz(Wa3wz7ZyN&r3vNSC1>vB=g%h~I4~lh_D$!xpRH<=<@; zJ8D;DQzavh3_ZVM7VeZZ|2G&2*yax1Eef(tiVF$l&DVR&{?aLRIr_@Pk)g?#&W}Kd zDq=dHx>gH_Y&lFG#lL!$;Bd+)Sf!U(R|$iK<}ToM@?@xaPlc2B|2?!3>&b2>Y*kDe44!x!9YCSsHz}j_oszcaztY#;4%* zy=~kD3@Eknk*5pTqGGR+N}lIfuXJDoUNTcMPIGsi9=WPwGI~^S@WglWcGt%&KlgU4 z{DM}C*Z0OSGhJqaM`dD_lH#fvn1qTx4tdI9ppkz-V62+n@u@KSX2i-iT( zMBkgmoAF#Fe!u54#dr>~LvVli0*e{g6!{vDZBTvy(nwEhtP9b{alU8k<*$j4=OO_y zYY-&*-VNHTIA()LvCXUDmT8`Z=Fx`~Yo@>H#Dx3V*gnObu>{+F77I)kV>xeZJggb@ zrF5}9qN-I*){|j1HuSl$#`AA)?Yc2KZZfbAb}{J8D_w*Tpb8Gt8>P|O)!jzB&6RA% zbDsEp>T6d4R&^!xXw*E7sZOrn&X{X{cY3GXrg{8yoU;4uISH>*1&Z ziN0%%pSWu`%1Z&OwL;^ydEZn+DG(`n#<%O#g8h-c2$9M!$Tl7MDQic0f0(-^I;tnE zTfayx~+)Sy4JOygb7!*SbvL@;1yfQs9DuhZ3z&=a7=>1_V;WsD?Le1;N7VfsHv5<> z_TW_kVdU{rvE`#tn`_yUicMe z;FZMAW~oF)Bax=?!I?Sg+dSmL%^ZF`v5tr*3`PSYT{u zmHe@fxr2DpSXCA0=D* zRP&k<@lA0ctfpvRc(q>`!+FX{P&90f!RGn!;7eWLA)^ATC} z12^+OJqZ_Er#=}$HOhb1@s`m%@&VH;Gn-W{3+&Nd74*m1dAglr&`S?@W)Mw#4@0FW zBKlO4+rliUIctVTA)+I+gHBgksD2>~F zHH6yF{uUM`0t3Wvgv*nHtTOJu>xa*dYWX>?ME&c}-Tm8MNb1?Y{A#7ypyNxCkIf2rNx=eDr zJ*MJn+=tZM$$;T>;Y=TiVh|E5|MBYB@-K2sM8aq3GjtVLcnzw-MexftfI9i<)?2LD z>2!mE(}mtm%3wyE*2`Zhl{MdFTzqzl6`CG^OxBw<>?>-|89eym?lZ`_?L*KK{F;u_ zh@xewPStUrVtq~ZBc}^GPC%)oahxCf8*fN7`pw5`ud`d5779)IeTq(r^xj&?+jxvB zTlI#%o@hTV(yh{Vb938T_i8FOs<}@pd-W%Zxw^mHSzC((N+{}R07HyYNWj zfGTf;QNL`Oik(~w1^UZH;WC}v;?9PV)O^Fi0*e-%efK_*WR%}yx(=l}cY=xwS=+wY zn8rQ7GjH|sxSs5BZ^9{SubgkPWBRFHF<%Zk^fVfwcbUNk74&y<_D^c`zp2j6yd0b@ zSmrxS;`VUxX-0 z>h6_V&lhwLXVc|G+0p*xV0( zb}*w2EZRf%>%qzy+3p&P*_YkkCt(E7$eL;GHX032N{3`se zn3A#Kf{BIxAKigLU}e`ZEAC7HIw-`~beFE3AMhCOMqHL|Q{H-_^?3IzVot&+kfGs> zNTS|wa9#=9kfrFN!cPV;+r+t;k9&uiWNCXWWd;}U_)eAtwxqjK&5WR2_jdCv3lB; zRo9e-k)_4$)Y(0iDCr0lw2(4jVGxbv5*xhWrJ%7j%+YS^VbQzy=!)CfjaW{M#4yt74BplMT^s6=DcxXjEfrcd${dcL`1deetA{HGuxXoH#no+eQR8 z9?cILspPkk3r^~E3&}M{GB_+J828)1`te$r%1J`Aj>|Lc!a=~UlcODNV0ckPeb0Er zz6h?P_#TWdI^!G9*sr>uR^1Y18#fWp2i;JTp_s~>#AEEMU9SnLZD#qJc3Hp13pYRf zVA356RBj~FlQCPkw3lSFIh?3q@uItF-&PW9yk*2{|KJ3>=hty|l`{ZNka!#?<`7V; z##&`BSJAZz-~_9$=tgjZ#SLtAXHq9)Q*} zR<(}0jIPmm-aWsT-|29P?mGO2(U}IacR#YMe#w zlSHGETj%?qISy2^f}!2^`s*G%>p=WtwKHIChgu$;%V`~%^I02@*XJv_VB-U?!&P8x zela>vZ@a6o(yJvA<}Qi$=HGMHc9&STI%&$DpfAJ9DK^Ta)l9e(J18S2BlgNP@r`=m z+g+&YBja_v?ZhE^s${_bXtq0%HzxtM(eIikb%R>Eq;np zxhDEs&Y~{Pc$IUZgGnLZ86IOm1g@M1Y-_I)f|_w;r8u*IJ~j_{yZxlOs=++PnRby- zSI|f=0ooi@^LjB^rNQFmpFC`g?!KKyX90e4RMg{^HIHfT-X zYt|n-UDc#BeuU-x zQ>F^VxT~EV0RKSa^=rhM-N(5*wJ!%=G6p}O5e%=CMMsP<;SBMK&wwPyPC)5mTCIOVUAPi2WkF0oNjUX1sy#`9?zIXQwrS>m$Bt zq`?f}InK=^dOd(A#-+aUr1*4vaQ6Q8fKY;z`jV>3B<`nqHm`Aylj_^^S1OQUbJ*Bo z9TnHM$`&DM>Ky*LQR*-q(f4Z-;q{3@vt*9DTR!POr+O~J=xk#f&*JWno{PFE6uLY$ z#dvb_Jx-DC3aogv>O^Fi7Yuz;>?Vf3HZ4Ra^eA-XzHG6D7;yDJtzY!hzhBx@MABxO zjEIIS?iLf=-~dN4Qu$y~qvfY)*0P~GoBQ+I{*pz%>p@Y(XBQ_w@X(E>YVQdzqz9~d zZJD?eAGw&u9aQH&^`2iCh?keVoh08j9ito2KKgY;SkB+@W5>%90uO=9nvBFQI0{_1 zgvzzmWwo?c-V^Muy5pf8Qsrnh`cM^!_;xL1EjTkN>v=P&@iDO9)+bmSg8~dumw5d` zKYnNE3VBRI&_HL&TENq-%;z5*DqTYy7weYpJUnCfWu#unt47qty5D?&o6?78T9_R~ zoCs1&ZcC25T@4`ew3wXGd|E^D&HyG>#iIbRZ>__Z{tomQw*7$;c{O25Ll(+B#Mguw zAA^1X*&MvT4ou#1K_`PchiH+ym0<7o#7MugF3BIOU!aw*O?Y&@h1~R-pWHl7s9#BF z=TE3B9=kji@Ej&0LfP@OG17o5^M5_)Q=3}9yql87zLcEXypcYX`~zFWzIRF~#G61b z+?JUhePbuTuf2#aHdg*|1+Jme9pgwQ4OCBk$CvjEHnXb3(<|i}pASU!8Jolnf8DCu zeteeN4kqGQ_?!0WJ5uD<(|}B?jY<8yMKpQ=UUIwoB~ancd(ObAbCpd;MYe-&)We&i zq$;BCG5Lu{wD+CUIkd~4Xq~~|?oiu9xMs|zSzSJ@dOGt~{zv`ASHnRYwWg$Ozs}d1 zzIxc763eHR|6yMCuW6S^dMc<>{NTrFUVpC|6D@eL+F@`v^R-$Ww`h&l9PKo9+PaXl zH?mvW@1s!N@kQTtCP`_^blo4S}PFZPmn7ZgW3KDJ8u zWsP^i$`KRJ)xX9mrtxFQO6r>3f`N6(V&j_0HCEo$<6-~Q2B#|vEzgL0mz68Slny2I znQr|h#LXJg1*u_pjG8T$$a0yj+gbZuaP0}(Aaes|A*eGl^2=(kBGn`Zg*5bz0!z+$ zJ%~vxM8Y{z457PO)0`}-CUhh^b_i##A!%3Y76`nxQ&637?}G;T6Kc?I&V9x&U8n^fO!Z8r;(VBL-W%_Q5H@CIRxn*$Ch9%bJJ*4?{^8`bJF;yC3cr+7yTH`?%-FRRq@~AK>7z75CqB0t-_t93LU0iggg`{(6-3Ixnns z!c&i5gqvApYe(Y*p)OY4x{JRCu0XsV*)WE9XC>rl_w_GhMjeBvL1o7gXe&IK=A-H@ zIS8@Ip2yg7m7+xXD8J;kC!yp}gqlR3?x#q#vHHU%idlt~br*{`vakc_M{fNQEc82V?=z|c< zg6!;*1kIgoG)wz>tl4zh@jDU^5pl{xv|o{qiC(2It8`% zPGhVtbvnt+ojdZTG5mK%kI*x$YtM++xmSKHWd4#I3E*FMo)F}+q1*JP51hF5a-`>R z_JhUnR*T}d-hk_9zSb>TAxG)o$;tc_wt>hg@1NOZ*76%C`gHvPMdyy(qM3S1O^rAY z0)HFj+qoZz_B5pk775T1N@P99z^2jrL!Fw7cP6+?x0Xcv_uD5!xi`E&&W@;eGebn~ z3@$)^jmLE#Z?c3li(R}?utM#n+%j$CQmL%i&AjZKdFEYkLusWY-xLY$$wgdUc(Al@Is_Qhw! z(54`Qv7mDdxkMx95H|Niy>|Z^-+KZwKoap`6^^!QKI)FInFtdIzlC7jw1Un1pGCo_ z)HjJsk|WLd{7&i4y#p2cgod!{C!ojc9MufQc^pXZDPyecSGw%zJh^n{K>3%MznfPC z-1;UVH=Ma*WdfIj>FgHo)EK6FO{I#^-eFAGtxw7F7z#l%B)5ayxdWNR zGT0&_-!w6PNo9WWfO1jNOD!DB?WK%qbsI?WkWo>hIAaB?sb;k|M!vdyE|76*{G0PY(s+3T5XU_^3=q2kYZMqKobLz`af?oi zk1(x&?z65!>!VqLQ&?UQ>{AHb^MkAL59%qq`|n|I;YI-A6PP&g~j>(q$j@O2YtU@K3_51c}fJE}O!rc^{&GaR&U zCU>+ySSz9(WtAP}m`ZfB|Fmix=9QKIi7G$~^BegPGs&F1wr`pMNi<35iIlQ9dE+){ zu`Y5>FC*BR=Ri8Z7vLw>Yt@%qti^U}$WK!tCg;}`Xk7_p!g&(CifD>|z!z-*zMSNV zxnyY+NbBEIcnVyifLrGr*xxKXm&;y9 z03%_D5|!A*R)6*NxvvIXQB($B;yj*{7`PezQX3LOhS&;KbJ69U>Go>;6n)v}H$JhimFb#aF%X zI+YA-c1k}Sgb=_#xhY|)ie!@fwO_YnZ~bD?F?jXkVI?jRBBky04Rsr+(it+F`esF6 zQdkD!KX?V-#6AzRmGapwxx}#UzQA};_>y${VPO{VeN=zE-5)o2<5EuD;tqg%Fg6W{ zU|@{%_m!~PULNLQdD%Jaa^_52kFi$=;rI?*bkmBQY=8)PHo;>zm^l4vYlKEBXQRGs zW~)B;^%ZR9NbLlV=8=o&TmP2(c1wDYgYG)b&1ZQu)a8~1Kz%qM3vm3JKrY62u(2yWPh*)OdydLD^uA)| z{qxj|_34X0W;18BGCt=4Wha2$X&F$95)Q~+fWKUkzpFg|kJn!RzZ)ct_y3RK|NrF= z=bv@?mVo@Hm{(dp;#yZ@L&~Xj@tFZ7Mqbj?%*-ZL>HkBD@joR6GxJTwj%@;WZ18v# z@2O2NzWQhfvoL)K3!K|O6GvUbK$4@pwgm#;^!87Q({X3whQoc%qOGpc^?dBiWSi1 zr>%~DYav+xoOx=VkCyW z8oYmiJn9& z$5EtSB)Lq=OhVw;CzPM?89~~r)Qw}AWmY*Ioi$>R!Sz{k$fd8WT%RvldR|jnR4iBP z+lUuWBsE|vbRXWm{|S|7={HEOzvu&ZDp_@OL4H$!Sw6RGC#`)ihiQc>|F$L?s#?LT zkIh2Y9YZ5Bg|+fn{Q;Y;VbyTYoWMn)JA>eX^NgaswiB)-?UBM)SE5uDf zE(>TW(fm~Bk+stB$3ZE~L2b6S-NEMF`rvD*FyxBC(~;x8C0j979`5i#c4TMUEXO3G3QoFaTxA`OEw6MH4YQNCs|<(HCe+d zGL9M%n_AS3rEQP2A5bqC7L(h<$RG4{GKhjyYc&zO^+rO`9a%-su2^Q>;`is;klOmx znA@^`2L{dYC^Og060vu0EtJWARwZmWMY>3ialpO&p6Ik+`I=6V!GebRnh>wB?SS8K zdUXt!_fMJw`ppabL73zXvC1J7yi{xi8or|+KSRfmb{jDHC$75hh$RuqAA0~J*X%!< zd}@Ca3rp){1=rII85Dl&JDQh5Q~&1Hh5eNp*LJ?CTz{NQ`0i$*B z_2J9n`!Z2LWI8ZtV@j}oWS1!Anc?!3owQ^d;?osV#66VS+S?ZZwJNRv4`E+9D$pTr;&YY-`Qt_~JolL-7+Vs7Tn7}Fc z-VLE)sZ(yJ@M0PfzdJuV-8i#)h)4$;9OI+SiSpU7Pyl5kZ7LpehHrzCm}^N#4G>kvmDbh_^CB{8Wr@u+j=#B3Fe3o zXtV$kLhvj!jSOIv`ayWtEEolQkKPPqM$QH-EG`9lPaWmo^6HS6B7%v891!~WEV2C; zQYa%V@{&6s|7{x1p}obIF+{qtr)vXh)+(M#i(#-Cs7uTJiv$_dt$R}M+y+mx z9S~q)zeI2AR)ETWE^;T17gGRTu7;~|8(q>mkDUZm?QRbDP37eiQLamDU%eC#>8DL$5J-TefVM+CeDBNZy22#?b zHf4WYRJgGjr*r+xm>ISXc^&L(6P&?&nCW+S*j;VDvt*KoSx{PP21+>6S(eF4obO^p z+O-`d3AL))oXfyn!4Fri$v|-feIUq!G&{ba*n39Vv|%Qgi{>*a{0?Q0xe0Ksy+Uf6 zB8Cs?6W(*e46>ZpPgi9>7gg*&p?saSx|F$G^vEr{oH;O(T$uGbP&Ap{qDsyTo_^3C z>;gIg%;0(U_}n(!wtiY&wTo@4UU&ii<40PH!#=d3u{Q$jI|R0$6KWgx)qN0!fLXje z+KOw@FMn%`$Etjuf07htUfZaEGSod!4KYzE&4hZXk|g4wvPNFH}BbXO8mJ6|}4)GcBO~4C(DYL3D4J#v?l% zQ$NO>O4WpIwIUe)O58ku$2h7?P6Qf2$FMo_Aue9TAH(iCFfwWs*8(&0(RZ(1sOD+( z;gIWg-}yn~=BlzYdX$8D!Y3V~cC1UpY#hH#C7J>*)J^hUv9{$)F%M3gYEtOYZ2aRg*&Rb^7-Fe; zRH2`tZ-Y_cF?I640+r+DGoGBB>>a89r&xjej&*2kWcfyAwCjwjfx5dw3gsmTU*0dAsTw% z*jm84DOt=-Zi?9J)iqLNXv_huzs-dAR~qi{$fZtRTDn3nVzPY6?qx;ZgeonE2e9nS zWI0s-RljVQg9s5nh;PXtiDdnd5^U^JhVA(Oh-qQ1YoxDtBV_M z{+;=Ab5(j*8~W81nGwc71h9{)I9hN-a5J1e*=v_hm_9k0PL$pYk$%mtN_d#K*ABF_ ze7is(E&F|G#KslF(J9kLcmYSwaWR%9WVGF~~dk@y00 zJ!kq{thQk0;C%i4ws30VCu1M6rqcPyRx6|n>n{rzy1_juu0`T#vT@>G#|=vS1wnBx)pm&P#p#V~Fl2Yx2vA%=&^&&zj|NB$HM?KgQmr zPby9WR39j0qK+C{K4_)r$Xbe7o4A^(wNb1KNZ(8(@YiQW4&#Kp1N3+{E!;S87T(@| ztvvjslM%r`v-*THL~zP;LXn(`x5z>HsynHo@4fH5lRcmFF0zm)rIf?$088e-7uKc{ zJ1{j^tR9O?i=TnLadD3S#Knd^?*(BFRn~JFXkWfeEH#>K$NJ&-J{bctlrvAsssXDp zpfMq%uxjP=Ej%fvuLWRj0@~m@A2=wX4Ak!!BXD|nF=69Ws(Geu8R1|B| z1no7`XO2=$5EEd8Z?*TOxO55a*cg%{0Wj`d2)@??XpO;PIWM41!Fch&Io8T~8O>Gu zR_Y}RCpW3L7!BUUW@`(3k#QTJGHIj4f*1Qnno<%e{wDHixJyIeA)!>K&%hZ&VlRQV zhE8$WI)KZ#;EVB{2qVW=Z#3wsF7#Q0X@_XY1{EGoHnz$jK@0n$Otx2{+dtq|w$W9p z=x&}WdJc2_P#kbY-@D4^nGup+>&b_|KT5}qjOc9aLWN5A@rg)>%-$VO>LIN#^w50( zziB3Qz*w->R1AQrfZF|!_W1w9Nqh;oaVi1UDzozUC0SCO84~}u8Ug)V`{)0iga0m& z26pc{$$_;y*?xBO`F@KO(((4v-YEFj@k-%((6y5;dScBmx-RYw$yLv&#`T3s;E{*- z3O639Lu%yc`k$SJZhs`fgF-!ezN0U^jgKZ_&WZj#h)yUB{7&-}>Mkv1!A@ zz+KFS($!=kGar%&Eka^F3XnQ?d7z_DDxZc-1ACG#()Ycl{k;^=v5}x^>8qnUUL+TC zpmh9#%7#08E(`J`K5@Rp#w7Y=+IixahQQGbg>;m#{Yb|eVtQTUrLvIiKJfBC1bhJ# zmD!no2?5E|Z*tiITqh;r1;)&FJ;%U>d6^vMt)RAnE=A+%mns$QsVc{;*h$ax?X7TD z?@Ifg9yF>Feb~ot9N2IIVbkL6!DvY4wCR&J;{VD>q<1Axx-}Rf7YkQ76G@$NDCRq? z&|faQ=tn#;!=Lz|C11B@amB~`s+iEpC4r&qq>8j**`s(is3Y)|gJBG+rMM_k6`@4EXrGfcAlVD=}=~PHJux-*8 zZ*5h1^_6wYULm+E+tGEk=Z5#o^~Cn3X%ZX)A)-}Y-MPh3aj1CA_t#b#9KbBgB~Wm@?}Ip)8lI1BzsnB_moju zE@#X&>UW@yp)MW?-jW>Ipl5Plp>;ma)IR5%^4%#mcjD zYga~;7j5-Kp%Ree1CvNAq&#On^nf`w&sJvSc7!AuE*Gch&EYU*W&f9hT|Qsq0L)ao z5%Bk#3Bk)6jtJVY>WP0nQY5a##_g_*D9655kv&b%YXz0(q(&WRg0`NG%WedCH_L%A zmhB<^w9=N_&MVRF^Y`>iDz)lZ;q2Pf=9lNxA`~>U$T>4#Ms$5!OR5*!PEtww&fZm$T5zuZi!Qxsf!BuBZ=5M-?9GDa8BYwvRcr3Hu_w9_^#wWffkKEF{ zOIA>Xh7EtUb-+Ob{n3E%kER#1>VM2${wo^R?1zE>mWRM@ECT5F)ia?s<&K;+2Ik)m z_q$vWyxZ{4Jf8Gj>{B>-Ur O4-{Xh$`(H}3;rM6im(L$ literal 0 HcmV?d00001 diff --git a/docs/images/VS_Container_Debug_Select_Code_Type_Window.png b/docs/images/VS_Container_Debug_Select_Code_Type_Window.png new file mode 100644 index 0000000000000000000000000000000000000000..3aebaf79e72fb92702f17089a2e7889127994f6d GIT binary patch literal 9962 zcmeHtc{JPI*Ka(n4wUwxrKq9RQgdsbQ!Q0P(3%>ADnbk~Q$s>2T5ZvqXKHE`X$di> zsG7$hBnUMRG0#c3p5Obfb*FdTweJ1v{_*?c?C&~f?RC~ZXPy1o`}^7Zosoey3lldJ z003aod7}9Y0ARqK4yz02PFJ3{e78RRFnB-Heh4V*y|HvUIqRqa(f|N{#4^)t8BgaI zJ)VHQ0RT3ie+I)Ol>Zd~AQYmbsbTWUnlcd-XX;q9uo?BcG4!;Sb_WY~j%xF`6(EiSIErEr*h0eFaga-0&-uD1# z8DYK*=BvCBdTj#lk$A#p~9s_PubjNMS(B>6zpQPw`;F$W-%NF;U-ygl`yM(R<;7I_fXoY@6z_A`% zmpRna_efiHWV*q8Pr4)3r{A{DkMbEEJSSGwml$7V9kGZB%!w>6F1FuU9Hp`Qx{tG9i`vF_CQ}jIw z$$^N0Y4{uFO;@=r7^DVJDFF;A^5Qzy6-8@rV-c%%Tl4AeM0H-|MmYbdKi9xomj*$* zrMhmh+?Q7}f@4$)M=Pf+tFkn@2hZ?~nY*}r;~m`fLHIVrJ2~;H2P--vV))dYqpZ~f z2XnfLOG+*o^fx=O2R~TC6z-!85u#t0CSJ{aly)7xrxbEr&r7Uu`U3PK^_|FTmj8`Owcln`6AaDwBA5yJkw9A-x<`?_|}JC$w#)E`rR-LrkI9Vt?*bLvNcz{e-Qk>q9&uccoDFmHKvS z-`SqGwQkb~aN}$heqI|F5x5LwTt4X`VSzpSK-0Coy6fV8{2CBDCIhFN_yAqvqmBjN zV3Ky13`2;14)b|~%?h>a<4|k=l(KJ33v;aRB?$$fvgu8fFf3?qwL^*3CuDKqOXX8Y zBT1EqSE`l_AUoB3588*P-nVii*La=BNJArc zzvbwb?5y*j9JJ+3rQEZ9Xzj0PaB$SZe=I<-@&@gN6`$+mY}|-=F&cOC(d;h4=Ca>G z3AiHcm^)JFZTMWIk7{WWZg<3Ts}hZnP!t>p+wV@5*&0S8J?uYrPoSZ(}b9l`ti0`mzxm{b#R0AvU)y9kOF|~$1mKGKkVU)pUt7JOS1y z(yL8yX$oU(!nDQmC!Z=aa>n`|6W9H=Z$g+L{BF)eeOk%9Yhr3#*Ok2TyE9c3p2r|1 z&213A_wt7(uT1Vqrs238ms-mOstd!{*Cy>9@xtEfIp3i?QHDg?qGa zy^&|gwHfAHQ%i>EtR(Lp=Ic;Pd>Y{_y8GC&yl7VMK55^Sk*Q|iA-lUUpyrbN8;0xh z6H%CRTwa8N6#BV?2k31W*Eml#C=<@^gz{=OWY)&s>xUz*Jz{<-QG$6fk{tOf(d6rf zhH{|ey$XGP+j)Nk@4}RnKWi8U;0DA#~@R1=v{AH0q89;KS;!PGQ%kR@} z0iyhhUgvj>Ky{Pl1w|od&&5n@;cMW6I^JoyC)YB|Oe{#J&xJni6>fopvX$xGr?PCk zK0b*e@2;S}G;uDw0-)}%2Ez22V;57=975cDM3aK`N`Zk*%*d3Vi}$lv_%|FcEj*kl znvXP@;cmT5OW?<^TJ%QGa&QE~sI z=B0!>_36zoKAB$>?xQxm2^>*I#N$CkF}1n0A#&T#{o$am$#mfILW>j8->lIb(DwO^pFCnLB?Tu)#Y0e-Z}S_#(q?wbOuU{`HUn zQ2*%Q|6Lewpji2XH(qBbdfqw+nr>!XBA~M`amzl5yv)x>V+7=2by#qx&Z;*Va)RKJ zb55}G?vWeBg@hcE$j3tK4f|R3hx6&u_U%`=)B-WQJb=*2n?J^k!_kJ>>e2yQ_FPI{ z_E&y{M>|W(TB;&kTy4e9SeJJKZ|{Yqt)^b zWcKr(_DwZ=9x2l$`xM_+|G&*T1{ zbI!L?8E<6l7yD(s?E;4gR6K zM)&hR18i62u?>r_s+l^4Cp`Mqr&~GSF&!E7O=!rL%P;@Qr>RJ zJv$1SR$g&Rci7!?DNwdB_NelnEdfh=SsJ$m&aQ@|yh12fmEy2#1BikdiMV%u4f;i^ zWZV9VJz67paF9V`EEm&y{8tmsh;J z2sa(Gz?*G`G@5IaOv#3)krRJnGcFMLFwMVS zC|sSLk|kKN5R(+I5~Jg=+p_huhyongM@okG09PL|uP3O8Q9EKng3NkNmodr{?hfle zJh%ET0|mUbvlBiVx+VzD9E16gFUrPYwb_S&xTGD5;7MA4zkX4&z(n zzFk+XK#9Ythp8}OQb;c?AYz5xfTt1n2pS{3;+umWhY7>SYMvei?=f8DNKUNFJn`58 zAGUtGH4_%F^qt(P2x+T$P7iD!$WNn%E@0y z_Jusb$wTUxd(zf}G(w?uWGBlR;#Rc-xIfR3wdm@46`FM#0Q4 z-td~XzGj^@S<$5foiv+j^@1DcBlbLr<nLv2%U5onPA$}NOyJVpG!?fc~$wYhO(k&l&E)%nvq?$h0NxIyy#Ki zZgxnlUW&~!(l2R-?@_C`@O5Fzb^SOWT7HjV6lB?RO4M&crD6OHJx$0B{PP+c57NR| zuQrC$cfLC{Wu02t-s?6`cCdUjpWRv?Hji;_8e+ku&RzUOb#W=Es@m<^e9b3ve5Rcf>w_E~F;zmm^xxn8Et59(p{jV=rsJWS$tQc%_~m;?ew`s< z10Gws^h&fWU1C=y?W;JJw<_m4rW4Zc@HBA+^earf)p-UR0W;%oWTp@P+UnuwsX&!*H_hlPzBu8zC`n`do3%}+oQ_y{qE48pn%x+&NTrd387R@4@|9 znXPS`lw)D-bWh>V*(v2#T$haU+PZq&((vVW;5sCPF|v0H9y96 z>=Dx?7uu_rq>qQ~`~qdftFyLdy4CxWCloxXq9LmO=|12)t5}R%CzlkAcft)sA}G_I zEjUKDt~q9|-{(?2xHDG0$>n{iL3C#QvU}NGxRSDgXIqZg^a0l=d7w2 zTDiiCPW!y~ZEP>$WOLSmQS6MBA7jY07Ot(xCg|Xx4ahf6N97iQzF5c%y{KQSU0EHy ztGRwf?cSYf0~McC8hxCtX8Vd-wDn^rMIryz2?u5?p&vb2H(9E9;ME!iS*ju+5UAXp ztbT~kKE5qWG$i%uea)o48xk@?!Q(NEfCuZJxRIBE0h)XhyD^kbENyRKtDSbg>h!1lpHN<5 zw1KKrAt#v{@>W91IPF{ebESs@CMxz!Rm49U_{UzAb55=v?5ZTUI7YJo-IlvA zHZq5B!hphC`xU7;P9h~#AFzCISlA&bmBpHMggn}Trec;kxN83}s+80naPYmk1Nilh z1=#RAdDph?L&Lma`8KXE?)`~-BW*Ke@}^zXZWr@UAAddi``C62%f~{+bAX(nRsxN+ z!I8|xe+&*@h8FkrGH$If6uHX9^&Pn*rg!RWr;!MJzvDU z9&LKGeUF(RbdnGnq;#)mRl3+Yt12J=H6}o{AC`csVmY|$R0aKbZ4xW@-_Rp<{p4Y! zc++l-K}cc+q_nCiv9^10uZ$%rQ)Zphti3;IB(1WXy2a=Z&K$iwcF6FckAnvpXzn-? z#ToX~d!kI^)sGqVR9r^g3-YzDt5sOlJKHVLfu36~X$7)J)0bIWf+z$knl^)JHSt!1Z1=ADC-D?*)la z832J%qZ@d%jD2bvxi^{Us{K1Vrk+PODzdDxI2&Q!E?W~&f4J@ZO*Tr7tyD?;-fnz& zluVwJE&Z!RT$@C4;`@CaKO?qGGDbU}$nkR~2`w9e{E;y?q1L8P5HGb@6fT`PNOvGc zGJZ;XO5A&nR+loQSc;1!JAM%oOY!^BeX*rwV*b=jXzJ5i#8}}1Xa0{GyUBOdh6K8Y ze?!Kx!}UTyzjte7HZoS~+q(8F&xwCBZK*O|W1;tqdlzjz^-sk~XpZHzT?8&?Bl|Xc z`JI`UW3DbNO=4Bc8^suT5_aMiloxc5Hdr5GlEi4+Kk(ss_nuGXv*ZA*32^Yn(6}|E z?s3`zIsJ;s0NuJ12?))SlBex3uY%8<>vC~C>MAl*7emYgh5Tt<+g`IDP)obG&KQ9$ zoAv76Mm;up@^Jbe!gU1=p^nIuLXAqptiZ`VY-NFdsZ;&9Y7dIBv43>n?AL7)R+rnA zp@cCAS>gEZ8R2Qyu}$auSp9ftsSw9?g%{|6`Hyx*zUYXuu@_h+x4LLB^n>SqFK0lp z26q-)Y6zMKaq*u!`6&kkG-w9*^bO5=o$-ikpL%-BoP1aM*N(#L>lW(X3BmAsW8-eG zw9d$~Pw6&3Xl94CV_Ct`>G{M`W&L=a^D>q1a^Wx1lyyCNoL}?eo~{Vql)?4zwj^}~ zJEp9_Q!7Tx%$tgJo0Xjz-0R%Iavkz^g>HuJB_~o8bM;ll)Th#T`6$vgD;wtjw6c58 zIetE$rQCKqGHdw!5LGfYxSY$;c0TC|~z9IHtmeBk6Z z>u7{2yq~lTzu;!pQ!~H_pc!vOl?Fw16E2uhDl(f`xQC9sBB)PrM^{RxpKhO-N|wF| z1OTFwiK`a@50q}v0D#GwlAbIU7pX&%9{^xgzC2fB6I!=AjXjz^fz|;4)mzTE{gAEZ zSg7Iu#6s3e&>FWFQQ$-sq0~hYgVPZSeUsdeY(p;CHxpI3XbI+>q*O?>@NG2LCNQj#~by@ zIB@B%m*wkCA9CxuFrdK{`^F}h+h%BKFP7-vRituB(6Qpii_Q>J?nkY?OGly(-qc^X3%@4^CF?vd2N#$Tf1BYl7ED$ zS+r}Q{4;4cgvE#y_$AZPWYO*z=oa&WP-Ii{u6JSGAWNsO(y-i2K@N?L4?15i>@G~TaDnTl` zZYu5G5g4E>{&(e-;sZEZ?0cIM@$#K%S`&-d)c8tI#lEHv0LRyp^iG zb56y?ibGHV?9U8zRC)W|ZAz3;Vu^R!3z;7Dsof?W*`AT83CaMXnBEiE%`x^zzSW=_ zEV^HnVtD?6(&F9*kMf4#R*SpA#Yun31Ld}K=xz*5x%Ew3QhuT0ewxiY zjZbDj&y(hdE(xb_FMe+LlAe6OA@6q@hJP?Y`s8y#@NaOfp%yf~&!S3m-!ei!gHM4a zbn+{y#l2-s!GX}V-sy6(!ko$4`14IoVHs8@Yr;aR62yBmzMuzYQONk(R%=@_Og z1A6f;^LOJ*cdMnpOvMVyrh26A^dl4}P#QMnVi^9g7v=7cG~zhQG&s@K+)AO7x!P9S zcmQqaWE6im#XOqM1||F=i+g?5+KU7QHdU79xYy>x!i2taUlfCs?pE?vKIDbgdZizH zz`(pTU9MYFB;TJ*Y)O&{E~#UZ*Po{zRapo z<`Ynao1`3CKxeSf6BJj&Y`10&lD3Ud%`(5^i$1JT@V}btoY^okavzbcb;~>aYKN<( z-*XR;6bdW{1Lli39M|luTo~)?tcRu(Lulq&pBGuADhg2W0rVv12?J>Kph)U7&y)U#Zil;mq2<1tWX=&i2ks>`o|X zI^8?R|73Zefni5qZT{2alX4&A6(>)*2g$4_y^ei*t5Y&zLRDq1Ym^IF9RCF{issukjY_sbg4a$N^$7>Gb zeb53rAM`J}Y2vdYodJsw%Rk#EVdi^96|3@!=<#g9E`GjVvuwHNgNDfrfWIc#ki;QW zli4Mfc1+<@gh|hif!de=Tin0!6|%E!DzwPqW-+=+`rOo)t<+Z~gO*5K%L^aZzOp1U zCwlAk+NH}#s`PeV*HKUsv%(!U{AEB~zBv39xSDu243T7G*q;_~wT=$yHAC6tUv}zY z87cN%0$xx<(D52S=PqUiUs2y}Qt!C6mwKA^+@ABqI{qg#vK2ods;0QfUvf6FSi!de z#)|musW6aHNUiwR6MjUF+o*mjKA3AiVaKe)J{wd!_4G-rYEs*$J*$RN!>*${z3cjB z`m%E5a5bR=h8%^v>1bt>Qv7`LfzZ!lUtUnO-UuPhQ=g(sj_P@*6x;Zt599sWaRtBP zptE8Biaid#J;(7bz#7v1$S{+?fwheBTobr+S8M;>;!&KZBO-j^Y@sTG7Z6wE+AQTH zIsUo->{mIgfJb!jqRX%<`{z^mGz(A;mJZ(V@lbfHKk=jB?WkuntT|dO^-9%5$7z&V zwYxR$f@Cj{7go%>jAH~`efD_Tak0fQPXXs7sTxl$9*+=qLNdkaZIhM_aAvJU3HQrTXnaBZ1n^c*?m^T7L;x#)Uf0A?bDym6lMx&z|~2A$7vWZaC{n@mPJ7Ij3;J#C$bHUowF@c zUoC!_HM^TR-6W^I(EF5SzcW2EO$3k=2S1G9q{C!PO-&Pz5I~l8;q2gNy;*8va&mH2 z?ov6R()XHtBWk{zr@PW?JZ_{$jk|IS#!U%mDxl1OvE z-m!Zf(*h1pO9E~-3GZK@Ip6u+wk1F0g6savH<$R; zt&a8zHuEx^-exNM>Jo{>+1iydJ@ErY)=ta&Z4A@r(Kl55^dnh$MNbRyA1UEp`4N++ zT?+Q6T_aV7k}+q1`Ml>d)_#gP_GPg!sU;|s_Q1TMZ2z(ThQA(T(G}za@IAS(b# zUl_5)B-c18xU}u7itUE6_Q*Ss3OgfX<~PQZLdT&)tM(caGOIZ8^;y)7Q!{E()OAEx zNmfAxhj9p(Ny1PH_aE#xP{Wl5-9HQIp5#1RZ>K!fgHiuX7w>r)_pD^Dk%OGoo~pA> z8)Jp;qz`FI{L-%j@Iv70^4SW}!B{KF9i#wxPv&jCKGwFJKeR%z1I#pfYMt_ zJl*cb*`D;+4txOTJ|;B%mO{;(rB>xCJmV!3cFVO_R8J?{{YkpW!duyMa@FSf@LCEP zc9|G3X3i!5iT(7ZuS*S+kw$#!Gw0IeZHC_}QIhMwtTbxRCDxviUWlF)jqPOy+AP#& zNaqrACZf_D+(*T^Gd32}|8mWlmxIONbP7{y?UdwwYyUY|$^-n0jCZ+$=vmX(zf_dl z%-55Ci^zGgGmNS%E>NT8!BN@h0K`P_WFXX=tczmb2A`v+Ip{u>1G0au<8jdK0gW9x zgzc&a42f%J9M2s!v+Z24-)WiKmooG7GA#T4sdHSqe(!-nhN5kqQQ2v;282t|BZt!2<~bFGnF-wpH~**9 zdo}RrPdARX=tvwH;i!*f;ek@;XB6+cx867(Jj^f&b9Yb9(~j*tZFrjfqs4mS^@Xfc zceMO1+>%7yUw5beKGn53)%_Q1EgSULe_51P72G;u(1gyP>kW+1Iqgpa=x7;emOXqC G_TK>5_x*?f literal 0 HcmV?d00001 diff --git a/src/Certify.Providers/DNS/AcmeDns/AcmeDns/Plugin.DNS.AcmeDns.csproj b/src/Certify.Providers/DNS/AcmeDns/AcmeDns/Plugin.DNS.AcmeDns.csproj index 59701609a..c549f5180 100644 --- a/src/Certify.Providers/DNS/AcmeDns/AcmeDns/Plugin.DNS.AcmeDns.csproj +++ b/src/Certify.Providers/DNS/AcmeDns/AcmeDns/Plugin.DNS.AcmeDns.csproj @@ -5,7 +5,6 @@ AnyCPU Plugin.DNS.AcmeDns - diff --git a/src/Certify.Providers/DNS/Aliyun/Plugin.DNS.Aliyun.csproj b/src/Certify.Providers/DNS/Aliyun/Plugin.DNS.Aliyun.csproj index 7fcb8ae6a..f137b7df3 100644 --- a/src/Certify.Providers/DNS/Aliyun/Plugin.DNS.Aliyun.csproj +++ b/src/Certify.Providers/DNS/Aliyun/Plugin.DNS.Aliyun.csproj @@ -5,7 +5,6 @@ AnyCPU Plugin.DNS.Aliyun - diff --git a/src/Certify.Providers/DNS/Cloudflare/Plugin.DNS.Cloudflare.csproj b/src/Certify.Providers/DNS/Cloudflare/Plugin.DNS.Cloudflare.csproj index 72d47ff8f..25862bddb 100644 --- a/src/Certify.Providers/DNS/Cloudflare/Plugin.DNS.Cloudflare.csproj +++ b/src/Certify.Providers/DNS/Cloudflare/Plugin.DNS.Cloudflare.csproj @@ -6,7 +6,7 @@ Plugin.DNS.Cloudflare - + diff --git a/src/Certify.Providers/DNS/MSDNS/Plugin.DNS.MSDNS.csproj b/src/Certify.Providers/DNS/MSDNS/Plugin.DNS.MSDNS.csproj index 57f33b801..c33de58bb 100644 --- a/src/Certify.Providers/DNS/MSDNS/Plugin.DNS.MSDNS.csproj +++ b/src/Certify.Providers/DNS/MSDNS/Plugin.DNS.MSDNS.csproj @@ -6,7 +6,7 @@ Plugin.DNS.MicrosoftDns - + all diff --git a/src/Certify.Providers/DNS/SimpleDNSPlus/Plugin.DNS.SimpleDNSPlus.csproj b/src/Certify.Providers/DNS/SimpleDNSPlus/Plugin.DNS.SimpleDNSPlus.csproj index 51cd4c601..9b8ea49a7 100644 --- a/src/Certify.Providers/DNS/SimpleDNSPlus/Plugin.DNS.SimpleDNSPlus.csproj +++ b/src/Certify.Providers/DNS/SimpleDNSPlus/Plugin.DNS.SimpleDNSPlus.csproj @@ -6,7 +6,7 @@ Plugin.DNS.SimpleDNSPlus - + diff --git a/src/Certify.Shared/Certify.Shared.Core.csproj b/src/Certify.Shared/Certify.Shared.Core.csproj index bd8ee3afc..e28907b12 100644 --- a/src/Certify.Shared/Certify.Shared.Core.csproj +++ b/src/Certify.Shared/Certify.Shared.Core.csproj @@ -4,7 +4,6 @@ netstandard2.0;net8.0 AnyCPU;x64 - diff --git a/src/Certify.Shared/Management/CertificateManager.cs b/src/Certify.Shared/Management/CertificateManager.cs index a9e5d7435..5bb5e56fb 100644 --- a/src/Certify.Shared/Management/CertificateManager.cs +++ b/src/Certify.Shared/Management/CertificateManager.cs @@ -32,6 +32,9 @@ public static class CertificateManager public const string DEFAULT_STORE_NAME = "My"; public const string WEBHOSTING_STORE_NAME = "WebHosting"; public const string DISALLOWED_STORE_NAME = "Disallowed"; + private static readonly bool IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + private static readonly bool IsLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux); + private static readonly bool IsMac = RuntimeInformation.IsOSPlatform(OSPlatform.OSX); public static X509Certificate2 GenerateSelfSignedCertificate(string domain, DateTimeOffset? dateFrom = null, DateTimeOffset? dateTo = null, string suffix = "[Certify]", string subject = null, string keyType = StandardKeyTypes.RSA256) { @@ -269,6 +272,72 @@ public static Org.BouncyCastle.X509.X509Certificate ReadCertificateFromPem(strin return cert; } + private static X509Store GetStore(string storeName, bool useMachineStore = true) + { + if (IsWindows) + { + if (useMachineStore) + { + return GetMachineStore(storeName); + } + + return GetUserStore(storeName); + } + else if (IsLinux) + { + // See https://github.com/dotnet/runtime/blob/main/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/StorePal.OpenSsl.cs#L142 + return GetUserStore(storeName); + } + else if (IsMac) + { + // See https://github.com/dotnet/runtime/blob/main/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/StorePal.macOS.cs#L108 + if (!useMachineStore || (storeName == CA_STORE_NAME || storeName == WEBHOSTING_STORE_NAME)) + { + return GetUserStore(storeName); + } + else if (storeName == DEFAULT_STORE_NAME || storeName == ROOT_STORE_NAME || storeName == DISALLOWED_STORE_NAME) + { + return GetMachineStore(storeName); + } + + throw new CryptographicException($"Could not open X509Store {storeName} in LocalMachine on OSX"); + } + + throw new PlatformNotSupportedException($"Could not open X509Store for unsupported OS {RuntimeInformation.OSDescription}"); + } + + private static void OpenStoreForReadWrite(X509Store store, string storeName) + { + if (IsWindows) + { + store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadWrite); + } + else if (IsLinux) + { + // See https://github.com/dotnet/runtime/blob/main/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/StorePal.OpenSsl.cs#L142 + if (storeName == DEFAULT_STORE_NAME || storeName == WEBHOSTING_STORE_NAME) + { + store.Open(OpenFlags.ReadWrite); + } + else + { + store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadWrite); + } + } + else if (IsMac) + { + // See https://github.com/dotnet/runtime/blob/main/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/StorePal.macOS.cs#L108 + if (storeName == CA_STORE_NAME || storeName == WEBHOSTING_STORE_NAME) + { + store.Open(OpenFlags.ReadWrite); + } + else + { + store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadWrite); + } + } + } + public static bool StoreCertificateFromPem(string pem, string storeName, bool useMachineStore = true) { try @@ -277,9 +346,9 @@ public static bool StoreCertificateFromPem(string pem, string storeName, bool us var cert = x509CertificateParser.ReadCertificate(System.Text.UTF8Encoding.UTF8.GetBytes(pem)); var certToStore = new X509Certificate2(DotNetUtilities.ToX509Certificate(cert)); - using (var store = useMachineStore ? GetMachineStore(storeName) : GetUserStore(storeName)) + using (var store = GetStore(storeName, useMachineStore)) { - store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadWrite); + OpenStoreForReadWrite(store, storeName); store.Add(certToStore); store.Close(); return true; @@ -367,11 +436,11 @@ public static async Task StoreCertificate( } } - public static List GetCertificatesFromStore(string issuerName = null, string storeName = DEFAULT_STORE_NAME) + public static List GetCertificatesFromStore(string issuerName = null, string storeName = DEFAULT_STORE_NAME, bool useMachineStore = true) { var list = new List(); - using (var store = GetMachineStore(storeName)) + using (var store = GetStore(storeName, useMachineStore)) { store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly); @@ -394,7 +463,7 @@ public static X509Certificate2 GetCertificateFromStore(string subjectName, strin { X509Certificate2 cert = null; - using (var store = GetMachineStore(storeName)) + using (var store = GetStore(storeName)) { store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly); @@ -420,7 +489,7 @@ public static X509Certificate2 GetCertificateByThumbprint(string thumbprint, str X509Certificate2 cert = null; - using (var store = useMachineStore ? GetMachineStore(storeName) : GetUserStore(storeName)) + using (var store = GetStore(storeName, useMachineStore)) { store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly); @@ -439,9 +508,9 @@ public static X509Certificate2 GetCertificateByThumbprint(string thumbprint, str public static X509Certificate2 StoreCertificate(X509Certificate2 certificate, string storeName = DEFAULT_STORE_NAME) { - using (var store = GetMachineStore(storeName)) + using (var store = GetStore(storeName)) { - store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadWrite); + OpenStoreForReadWrite(store, storeName); store.Add(certificate); @@ -453,9 +522,9 @@ public static X509Certificate2 StoreCertificate(X509Certificate2 certificate, st public static void RemoveCertificate(X509Certificate2 certificate, string storeName = DEFAULT_STORE_NAME) { - using (var store = GetMachineStore(storeName)) + using (var store = GetStore(storeName)) { - store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadWrite); + OpenStoreForReadWrite(store, storeName); store.Remove(certificate); store.Close(); } @@ -727,7 +796,7 @@ public static bool IsCertificateInStore(X509Certificate2 cert, string storeName { var certExists = false; - using (var store = GetMachineStore(storeName)) + using (var store = GetStore(storeName)) { store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly); @@ -772,9 +841,9 @@ public static List PerformCertificateStoreCleanup( } // get all certificates - using (var store = GetMachineStore(storeName)) + using (var store = GetStore(storeName)) { - store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadWrite); + OpenStoreForReadWrite(store, storeName); var certsToRemove = new List(); foreach (var c in store.Certificates) @@ -866,9 +935,9 @@ public static bool DisableCertificateUsage(string thumbprint, string sourceStore { var disabled = false; - using (var store = useMachineStore ? GetMachineStore(sourceStore) : GetUserStore(sourceStore)) + using (var store = GetStore(sourceStore, useMachineStore)) { - store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadWrite); + OpenStoreForReadWrite(store, sourceStore); foreach (var c in store.Certificates) { @@ -887,9 +956,9 @@ public static bool DisableCertificateUsage(string thumbprint, string sourceStore public static bool MoveCertificate(string thumbprint, string sourceStore, string destStore, bool useMachineStore = true) { var certsToMove = new List(); - using (var store = useMachineStore ? GetMachineStore(sourceStore) : GetUserStore(sourceStore)) + using (var store = GetStore(sourceStore, useMachineStore)) { - store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadWrite); + OpenStoreForReadWrite(store, sourceStore); foreach (var c in store.Certificates) { if (c.Thumbprint == thumbprint) @@ -908,9 +977,9 @@ public static bool MoveCertificate(string thumbprint, string sourceStore, string if (certsToMove.Any()) { - using (var store = useMachineStore ? GetMachineStore(destStore) : GetUserStore(destStore)) + using (var store = GetStore(destStore, useMachineStore)) { - store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadWrite); + OpenStoreForReadWrite(store, destStore); foreach (var c in certsToMove) { var foundCerts = store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, false); diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/.dockerignore b/src/Certify.Tests/Certify.Core.Tests.Unit/.dockerignore new file mode 100644 index 000000000..3aae53927 --- /dev/null +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/.dockerignore @@ -0,0 +1,32 @@ +# Include any files or directories that you don't want to be copied to your +# container here (e.g., local build artifacts, temporary files, etc.). +# +# For more help, visit the .dockerignore file reference guide at +# https://docs.docker.com/engine/reference/builder/#dockerignore-file + +**/.DS_Store +**/.classpath +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/bin +**/charts +**/docker-compose* +**/compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +LICENSE +README.md diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/BindingMatchTests.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/BindingMatchTests.cs index b492f8874..5e829ce2f 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/BindingMatchTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/BindingMatchTests.cs @@ -360,19 +360,19 @@ public async Task MixedIPBindingChecks() Assert.IsTrue(results.Any()); Assert.AreEqual(3, results.Count()); Assert.IsFalse(results[0].HasError, "This call to StoreAndDeploy() should have no errors storing certificate"); - Assert.AreEqual(results[0].Category, "CertificateStorage"); - Assert.IsTrue(results[0].Description.Contains("Certificate will be stored in the computer certificate store")); - Assert.AreEqual(results[0].Title, "Certificate Storage"); + Assert.AreEqual("CertificateStorage", results[0].Category); + Assert.IsTrue(results[0].Description.Contains("Certificate will be stored in the computer certificate store"), $"Unexpected description: '{results[0].Description}'"); + Assert.AreEqual("Certificate Storage", results[0].Title); Assert.IsTrue(results[1].HasError, "This call to StoreAndDeploy() should have an error adding binding while deploying certificate in preview"); - Assert.AreEqual(results[1].Category, "Deployment.AddBinding"); - Assert.IsTrue(results[1].Description.Contains("Add https binding | | ***:443:test.com SNI** Failed to add/update binding. [IIS Site Id could not be determined]")); - Assert.AreEqual(results[1].Title, "Install Certificate For Binding"); + Assert.AreEqual("Deployment.AddBinding", results[1].Category); + Assert.IsTrue(results[1].Description.Contains("Add https binding | | ***:443:test.com SNI** Failed to add/update binding. [IIS Site Id could not be determined]"), $"Unexpected description: '{results[1].Description}'"); + Assert.AreEqual("Install Certificate For Binding", results[1].Title); Assert.IsTrue(results[2].HasError, "This call to StoreAndDeploy() should have an error adding binding while deploying certificate in preview"); - Assert.AreEqual(results[2].Category, "Deployment.AddBinding"); - Assert.IsTrue(results[2].Description.Contains("Add https binding | | ***:443:test.com SNI** Failed to add/update binding. [IIS Site Id could not be determined]")); - Assert.AreEqual(results[2].Title, "Install Certificate For Binding"); + Assert.AreEqual("Deployment.AddBinding", results[2].Category); + Assert.IsTrue(results[2].Description.Contains("Add https binding | | ***:443:test.com SNI** Failed to add/update binding. [IIS Site Id could not be determined]"), $"Unexpected description: '{results[2].Description}'"); + Assert.AreEqual("Install Certificate For Binding", results[2].Title); } [TestMethod, Description("Test if mixed ipv4+ipv6 bindings are handled when not in preview")] @@ -385,7 +385,6 @@ public async Task MixedIPBindingChecksNoPreview() new BindingInfo{ Host="www.test.com", IP="[fe80::3c4e:11b7:fe4f:c601%31]", Port=80, Protocol="http" } }; var deployment = new BindingDeploymentManager(); - var dummyCertPath = Environment.CurrentDirectory + "\\Assets\\dummycert.pfx"; var testManagedCert = new ManagedCertificate { Id = Guid.NewGuid().ToString(), @@ -406,30 +405,30 @@ public async Task MixedIPBindingChecksNoPreview() } }, ItemType = ManagedCertificateType.SSL_ACME, - CertificatePath = dummyCertPath + CertificatePath = _dummyCertPath }; var mockTarget = new MockBindingDeploymentTarget(); mockTarget.AllBindings = bindings; - var results = await deployment.StoreAndDeploy(mockTarget, testManagedCert, dummyCertPath, pfxPwd: "", false, Certify.Management.CertificateManager.DEFAULT_STORE_NAME); + var results = await deployment.StoreAndDeploy(mockTarget, testManagedCert, _dummyCertPath, pfxPwd: "", false, Certify.Management.CertificateManager.DEFAULT_STORE_NAME); Assert.IsTrue(results.Any()); Assert.AreEqual(3, results.Count()); Assert.IsFalse(results[0].HasError, "This call to StoreAndDeploy() should have no errors storing certificate"); - Assert.AreEqual(results[0].Category, "CertificateStorage"); - Assert.IsTrue(results[0].Description.Contains("Certificate stored OK")); - Assert.AreEqual(results[0].Title, "Certificate Stored"); + Assert.AreEqual("CertificateStorage", results[0].Category); + Assert.IsTrue(results[0].Description.Contains("Certificate stored OK"), $"Unexpected description: '{results[0].Description}'"); + Assert.AreEqual("Certificate Stored", results[0].Title); Assert.IsFalse(results[1].HasError, "This call to StoreAndDeploy() should not have an error adding binding while deploying certificate"); - Assert.AreEqual(results[1].Category, "Deployment.AddBinding"); - Assert.IsTrue(results[1].Description.Contains("Add https binding | | ***:443:test.com SNI**")); - Assert.AreEqual(results[1].Title, "Install Certificate For Binding"); + Assert.AreEqual("Deployment.AddBinding", results[1].Category); + Assert.IsTrue(results[1].Description.Contains("Add https binding | | ***:443:test.com SNI**"), $"Unexpected description: '{results[1].Description}'"); + Assert.AreEqual("Install Certificate For Binding", results[1].Title); Assert.IsFalse(results[2].HasError, "This call to StoreAndDeploy() should not have an error adding binding while deploying certificate"); - Assert.AreEqual(results[2].Category, "Deployment.UpdateBinding"); - Assert.IsTrue(results[2].Description.Contains("Update https binding | | **\\*:443:test.com SNI**")); - Assert.AreEqual(results[2].Title, "Install Certificate For Binding"); + Assert.AreEqual("Deployment.UpdateBinding", results[2].Category); + Assert.IsTrue(results[2].Description.Contains("Update https binding | | **\\*:443:test.com SNI**"), $"Unexpected description: '{results[2].Description}'"); + Assert.AreEqual("Install Certificate For Binding", results[2].Title); } [TestMethod, Description("Test if mixed ipv4+ipv6 bindings are handled with blank certStoreName")] @@ -442,7 +441,6 @@ public async Task MixedIPBindingChecksBlankCertStoreName() new BindingInfo{ Host="www.test.com", IP="[fe80::3c4e:11b7:fe4f:c601%31]", Port=80, Protocol="http" } }; var deployment = new BindingDeploymentManager(); - var dummyCertPath = Environment.CurrentDirectory + "\\Assets\\dummycert.pfx"; var testManagedCert = new ManagedCertificate { Id = Guid.NewGuid().ToString(), @@ -463,30 +461,30 @@ public async Task MixedIPBindingChecksBlankCertStoreName() } }, ItemType = ManagedCertificateType.SSL_ACME, - CertificatePath = dummyCertPath + CertificatePath = _dummyCertPath }; var mockTarget = new MockBindingDeploymentTarget(); mockTarget.AllBindings = bindings; - var results = await deployment.StoreAndDeploy(mockTarget, testManagedCert, dummyCertPath, pfxPwd: "", false, ""); + var results = await deployment.StoreAndDeploy(mockTarget, testManagedCert, _dummyCertPath, pfxPwd: "", false, ""); Assert.IsTrue(results.Any()); Assert.AreEqual(3, results.Count()); Assert.IsFalse(results[0].HasError, "This call to StoreAndDeploy() should have no errors storing certificate"); - Assert.AreEqual(results[0].Category, "CertificateStorage"); - Assert.IsTrue(results[0].Description.Contains("Certificate stored OK")); - Assert.AreEqual(results[0].Title, "Certificate Stored"); + Assert.AreEqual("CertificateStorage", results[0].Category); + Assert.IsTrue(results[0].Description.Contains("Certificate stored OK"), $"Unexpected description: '{results[0].Description}'"); + Assert.AreEqual("Certificate Stored", results[0].Title); Assert.IsFalse(results[1].HasError, "This call to StoreAndDeploy() should not have an error adding binding while deploying certificate"); - Assert.AreEqual(results[1].Category, "Deployment.AddBinding"); - Assert.IsTrue(results[1].Description.Contains("Add https binding | | ***:443:test.com SNI**")); - Assert.AreEqual(results[1].Title, "Install Certificate For Binding"); + Assert.AreEqual("Deployment.AddBinding", results[1].Category); + Assert.IsTrue(results[1].Description.Contains("Add https binding | | ***:443:test.com SNI**"), $"Unexpected description: '{results[1].Description}'"); + Assert.AreEqual("Install Certificate For Binding", results[1].Title); Assert.IsFalse(results[2].HasError, "This call to StoreAndDeploy() should not have an error adding binding while deploying certificate"); - Assert.AreEqual(results[2].Category, "Deployment.UpdateBinding"); - Assert.IsTrue(results[2].Description.Contains("Update https binding | | **\\*:443:test.com SNI**")); - Assert.AreEqual(results[2].Title, "Install Certificate For Binding"); + Assert.AreEqual("Deployment.UpdateBinding", results[2].Category); + Assert.IsTrue(results[2].Description.Contains("Update https binding | | **\\*:443:test.com SNI**"), $"Unexpected description: '{results[2].Description}'"); + Assert.AreEqual("Install Certificate For Binding", results[2].Title); } [TestMethod, Description("Test if mixed ipv4+ipv6 bindings are handled when given a bad pfx file path")] @@ -499,7 +497,6 @@ public async Task MixedIPBindingChecksBadPfxPath() new BindingInfo{ Host="www.test.com", IP="[fe80::3c4e:11b7:fe4f:c601%31]", Port=80, Protocol="http" } }; var deployment = new BindingDeploymentManager(); - var dummyCertPath = Environment.CurrentDirectory + "\\Asset\\dummycert.pfx"; var testManagedCert = new ManagedCertificate { Id = Guid.NewGuid().ToString(), @@ -520,13 +517,13 @@ public async Task MixedIPBindingChecksBadPfxPath() } }, ItemType = ManagedCertificateType.SSL_ACME, - CertificatePath = dummyCertPath + CertificatePath = _dummyCertPath }; var mockTarget = new MockBindingDeploymentTarget(); mockTarget.AllBindings = bindings; - await Assert.ThrowsExceptionAsync(async () => await deployment.StoreAndDeploy(mockTarget, testManagedCert, dummyCertPath, pfxPwd: "", false, Certify.Management.CertificateManager.DEFAULT_STORE_NAME)); + await Assert.ThrowsExceptionAsync(async () => await deployment.StoreAndDeploy(mockTarget, testManagedCert, _dummyCertPath.Replace("Assets", "Asset"), pfxPwd: "", false, Certify.Management.CertificateManager.DEFAULT_STORE_NAME)); } [TestMethod, Description("Test if mixed ipv4+ipv6 bindings are handled when given a bad pfx file")] @@ -539,7 +536,7 @@ public async Task MixedIPBindingChecksBadPfxFile() new BindingInfo{ Host="www.test.com", IP="[fe80::3c4e:11b7:fe4f:c601%31]", Port=80, Protocol="http" } }; var deployment = new BindingDeploymentManager(); - var dummyCertPath = Environment.CurrentDirectory + "\\Assets\\badcert.pfx"; + var badCertPath = Path.Combine(Environment.CurrentDirectory, "Assets", "badcert.pfx"); var testManagedCert = new ManagedCertificate { Id = Guid.NewGuid().ToString(), @@ -560,13 +557,13 @@ public async Task MixedIPBindingChecksBadPfxFile() } }, ItemType = ManagedCertificateType.SSL_ACME, - CertificatePath = dummyCertPath + CertificatePath = _dummyCertPath }; var mockTarget = new MockBindingDeploymentTarget(); mockTarget.AllBindings = bindings; - await Assert.ThrowsExceptionAsync(async () => await deployment.StoreAndDeploy(mockTarget, testManagedCert, dummyCertPath, pfxPwd: "", false, Certify.Management.CertificateManager.DEFAULT_STORE_NAME)); + await Assert.ThrowsExceptionAsync(async () => await deployment.StoreAndDeploy(mockTarget, testManagedCert, badCertPath, pfxPwd: "", false, Certify.Management.CertificateManager.DEFAULT_STORE_NAME)); } [TestMethod, Description("Test if mixed ipv4+ipv6 bindings are handled when given a bad pfx password")] @@ -579,7 +576,6 @@ public async Task MixedIPBindingChecksBadPfxPassword() new BindingInfo{ Host="www.test.com", IP="[fe80::3c4e:11b7:fe4f:c601%31]", Port=80, Protocol="http" } }; var deployment = new BindingDeploymentManager(); - var dummyCertPath = Environment.CurrentDirectory + "\\Assets\\dummycert.pfx"; var testManagedCert = new ManagedCertificate { Id = Guid.NewGuid().ToString(), @@ -600,30 +596,30 @@ public async Task MixedIPBindingChecksBadPfxPassword() } }, ItemType = ManagedCertificateType.SSL_ACME, - CertificatePath = dummyCertPath + CertificatePath = _dummyCertPath }; var mockTarget = new MockBindingDeploymentTarget(); mockTarget.AllBindings = bindings; - var results = await deployment.StoreAndDeploy(mockTarget, testManagedCert, dummyCertPath, pfxPwd: "badpass", false, Certify.Management.CertificateManager.DEFAULT_STORE_NAME); + var results = await deployment.StoreAndDeploy(mockTarget, testManagedCert, _dummyCertPath, pfxPwd: "badpass", false, Certify.Management.CertificateManager.DEFAULT_STORE_NAME); Assert.IsTrue(results.Any()); Assert.AreEqual(3, results.Count()); Assert.IsFalse(results[0].HasError, "This call to StoreAndDeploy() should have no errors storing certificate"); - Assert.AreEqual(results[0].Category, "CertificateStorage"); - Assert.IsTrue(results[0].Description.Contains("Certificate stored OK")); - Assert.AreEqual(results[0].Title, "Certificate Stored"); + Assert.AreEqual("CertificateStorage", results[0].Category); + Assert.IsTrue(results[0].Description.Contains("Certificate stored OK"), $"Unexpected description: '{results[0].Description}'"); + Assert.AreEqual("Certificate Stored", results[0].Title); Assert.IsFalse(results[1].HasError, "This call to StoreAndDeploy() should not have an error adding binding while deploying certificate"); - Assert.AreEqual(results[1].Category, "Deployment.AddBinding"); - Assert.IsTrue(results[1].Description.Contains("Add https binding | | ***:443:test.com SNI**")); - Assert.AreEqual(results[1].Title, "Install Certificate For Binding"); + Assert.AreEqual("Deployment.AddBinding", results[1].Category); + Assert.IsTrue(results[1].Description.Contains("Add https binding | | ***:443:test.com SNI**"), $"Unexpected description: '{results[1].Description}'"); + Assert.AreEqual("Install Certificate For Binding", results[1].Title); Assert.IsFalse(results[2].HasError, "This call to StoreAndDeploy() should not have an error adding binding while deploying certificate"); - Assert.AreEqual(results[2].Category, "Deployment.UpdateBinding"); - Assert.IsTrue(results[2].Description.Contains("Update https binding | | **\\*:443:test.com SNI**")); - Assert.AreEqual(results[2].Title, "Install Certificate For Binding"); + Assert.AreEqual("Deployment.UpdateBinding", results[2].Category); + Assert.IsTrue(results[2].Description.Contains("Update https binding | | **\\*:443:test.com SNI**"), $"Unexpected description: '{results[2].Description}'"); + Assert.AreEqual("Install Certificate For Binding", results[2].Title); } [TestMethod, Description("Test if mixed ipv4+ipv6 bindings are handled when given a bad cert store name")] @@ -636,7 +632,6 @@ public async Task MixedIPBindingChecksBadCertStoreName() new BindingInfo{ Host="www.test.com", IP="[fe80::3c4e:11b7:fe4f:c601%31]", Port=80, Protocol="http" } }; var deployment = new BindingDeploymentManager(); - var dummyCertPath = Environment.CurrentDirectory + "\\Assets\\dummycert.pfx"; var testManagedCert = new ManagedCertificate { Id = Guid.NewGuid().ToString(), @@ -657,19 +652,27 @@ public async Task MixedIPBindingChecksBadCertStoreName() } }, ItemType = ManagedCertificateType.SSL_ACME, - CertificatePath = dummyCertPath + CertificatePath = _dummyCertPath }; var mockTarget = new MockBindingDeploymentTarget(); mockTarget.AllBindings = bindings; - var results = await deployment.StoreAndDeploy(mockTarget, testManagedCert, dummyCertPath, pfxPwd: "", false, "BadCertStoreName"); + var results = await deployment.StoreAndDeploy(mockTarget, testManagedCert, _dummyCertPath, pfxPwd: "", false, "BadCertStoreName"); - Assert.AreEqual(results.Count, 1); + Assert.AreEqual(1, results.Count); Assert.IsTrue(results[0].HasError); - Assert.AreEqual(results[0].Category, "CertificateStorage"); - Assert.IsTrue(results[0].Description.Contains("Error storing certificate. The system cannot find the file specified.")); - Assert.AreEqual(results[0].Title, "Certificate Storage Failed"); + Assert.AreEqual("CertificateStorage", results[0].Category); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + Assert.IsTrue(results[0].Description.Contains("Error storing certificate. The system cannot find the file specified."), $"Unexpected description: '{results[0].Description}'"); + } + else + { + Assert.IsTrue(results[0].Description.Contains("Error storing certificate. The specified X509 certificate store does not exist."), $"Unexpected description: '{results[0].Description}'"); + } + + Assert.AreEqual("Certificate Storage Failed", results[0].Title); } [TestMethod, Description("Test if mixed ipv4+ipv6 bindings are handled when DeploymentBindingOption = DeploymentBindingOption.UpdateOnly")] @@ -683,7 +686,6 @@ public async Task MixedIPBindingChecksRequestConfigUpdateOnly() }; var deployment = new BindingDeploymentManager(); - var dummyCertPath = Environment.CurrentDirectory + "\\Assets\\dummycert.pfx"; var testManagedCert = new ManagedCertificate { Id = Guid.NewGuid().ToString(), @@ -705,19 +707,19 @@ public async Task MixedIPBindingChecksRequestConfigUpdateOnly() } }, ItemType = ManagedCertificateType.SSL_ACME, - CertificatePath = dummyCertPath + CertificatePath = _dummyCertPath }; var mockTarget = new MockBindingDeploymentTarget(); mockTarget.AllBindings = bindings; - var results = await deployment.StoreAndDeploy(mockTarget, testManagedCert, dummyCertPath, pfxPwd: "", false, Certify.Management.CertificateManager.DEFAULT_STORE_NAME); + var results = await deployment.StoreAndDeploy(mockTarget, testManagedCert, _dummyCertPath, pfxPwd: "", false, Certify.Management.CertificateManager.DEFAULT_STORE_NAME); - Assert.AreEqual(results.Count, 1); + Assert.AreEqual(1, results.Count); Assert.IsFalse(results[0].HasError); - Assert.AreEqual(results[0].Category, "CertificateStorage"); - Assert.IsTrue(results[0].Description.Contains("Certificate stored OK")); - Assert.AreEqual(results[0].Title, "Certificate Stored"); + Assert.AreEqual("CertificateStorage", results[0].Category); + Assert.IsTrue(results[0].Description.Contains("Certificate stored OK"), $"Unexpected description: '{results[0].Description}'"); + Assert.AreEqual("Certificate Stored", results[0].Title); } [TestMethod, Description("Test if https IP bindings are handled")] @@ -758,14 +760,14 @@ public async Task HttpsIPBindingChecks() Assert.IsTrue(results.Any()); Assert.AreEqual(2, results.Count()); Assert.IsFalse(results[0].HasError, "This call to StoreAndDeploy() should have no errors storing certificate"); - Assert.AreEqual(results[0].Category, "CertificateStorage"); - Assert.IsTrue(results[0].Description.Contains("Certificate will be stored in the computer certificate store")); - Assert.AreEqual(results[0].Title, "Certificate Storage"); + Assert.AreEqual("CertificateStorage", results[0].Category); + Assert.IsTrue(results[0].Description.Contains("Certificate will be stored in the computer certificate store"), $"Unexpected description: '{results[0].Description}'"); + Assert.AreEqual("Certificate Storage", results[0].Title); Assert.IsFalse(results[1].HasError, "This call to StoreAndDeploy() should not have an error adding binding while deploying certificate"); - Assert.AreEqual(results[1].Category, "Deployment.UpdateBinding"); - Assert.IsTrue(results[1].Description.Contains("Update https binding | | **127.0.0.1:80:test.com Non-SNI**")); - Assert.AreEqual(results[1].Title, "Install Certificate For Binding"); + Assert.AreEqual("Deployment.UpdateBinding", results[1].Category); + Assert.IsTrue(results[1].Description.Contains("Update https binding | | **127.0.0.1:80:test.com Non-SNI**"), $"Unexpected description: '{results[1].Description}'"); + Assert.AreEqual("Install Certificate For Binding", results[1].Title); } #if NET462 @@ -783,7 +785,6 @@ public async Task MixedIPBindingChecksCertificateThumbprintHash() }; var deployment = new BindingDeploymentManager(); - var dummyCertPath = Environment.CurrentDirectory + "\\Assets\\dummycert.pfx"; var testManagedCert = new ManagedCertificate { Id = Guid.NewGuid().ToString(), @@ -805,30 +806,30 @@ public async Task MixedIPBindingChecksCertificateThumbprintHash() }, ItemType = ManagedCertificateType.SSL_ACME, CertificateThumbprintHash = cert.Thumbprint, - CertificatePath = dummyCertPath + CertificatePath = _dummyCertPath }; var mockTarget = new MockBindingDeploymentTarget(); mockTarget.AllBindings = bindings; - var results = await deployment.StoreAndDeploy(mockTarget, testManagedCert, dummyCertPath, pfxPwd: "", false, Certify.Management.CertificateManager.DEFAULT_STORE_NAME); + var results = await deployment.StoreAndDeploy(mockTarget, testManagedCert, _dummyCertPath, pfxPwd: "", false, Certify.Management.CertificateManager.DEFAULT_STORE_NAME); Assert.IsTrue(results.Any()); Assert.AreEqual(3, results.Count()); Assert.IsFalse(results[0].HasError, "This call to StoreAndDeploy() should have no errors storing certificate"); - Assert.AreEqual(results[0].Category, "CertificateStorage"); - Assert.IsTrue(results[0].Description.Contains("Certificate stored OK")); - Assert.AreEqual(results[0].Title, "Certificate Stored"); + Assert.AreEqual("CertificateStorage", results[0].Category); + Assert.IsTrue(results[0].Description.Contains("Certificate stored OK"), $"Unexpected description: '{results[0].Description}'"); + Assert.AreEqual("Certificate Stored", results[0].Title); Assert.IsFalse(results[1].HasError, "This call to StoreAndDeploy() should not have an error adding binding while deploying certificate"); - Assert.AreEqual(results[1].Category, "Deployment.AddBinding"); - Assert.IsTrue(results[1].Description.Contains("Add https binding | | ***:443:test.com SNI**")); - Assert.AreEqual(results[1].Title, "Install Certificate For Binding"); + Assert.AreEqual("Deployment.AddBinding", results[1].Category); + Assert.IsTrue(results[1].Description.Contains("Add https binding | | ***:443:test.com SNI**"), $"Unexpected description: '{results[1].Description}'"); + Assert.AreEqual("Install Certificate For Binding", results[1].Title); Assert.IsFalse(results[2].HasError, "This call to StoreAndDeploy() should not have an error adding binding while deploying certificate"); - Assert.AreEqual(results[2].Category, "Deployment.UpdateBinding"); - Assert.IsTrue(results[2].Description.Contains("Update https binding | | **\\*:443:test.com SNI**")); - Assert.AreEqual(results[2].Title, "Install Certificate For Binding"); + Assert.AreEqual("Deployment.UpdateBinding", results[2].Category); + Assert.IsTrue(results[2].Description.Contains("Update https binding | | **\\*:443:test.com SNI**"), $"Unexpected description: '{results[2].Description}'"); + Assert.AreEqual("Install Certificate For Binding", results[2].Title); } [TestMethod, Description("Test if mixed ipv4+ipv6 bindings are handled when CertificatePreviousThumbprintHash is defined")] @@ -846,7 +847,6 @@ public async Task MixedIPBindingChecksCertificatePreviousThumbprintHash() }; var deployment = new BindingDeploymentManager(); - var dummyCertPath = Environment.CurrentDirectory + "\\Assets\\dummycert.pfx"; var testManagedCert = new ManagedCertificate { Id = Guid.NewGuid().ToString(), @@ -868,30 +868,30 @@ public async Task MixedIPBindingChecksCertificatePreviousThumbprintHash() }, ItemType = ManagedCertificateType.SSL_ACME, CertificatePreviousThumbprintHash = cert.Thumbprint, - CertificatePath = dummyCertPath + CertificatePath = _dummyCertPath }; var mockTarget = new MockBindingDeploymentTarget(); mockTarget.AllBindings = bindings; - var results = await deployment.StoreAndDeploy(mockTarget, testManagedCert, dummyCertPath, pfxPwd: "", false, Certify.Management.CertificateManager.DEFAULT_STORE_NAME); + var results = await deployment.StoreAndDeploy(mockTarget, testManagedCert, _dummyCertPath, pfxPwd: "", false, Certify.Management.CertificateManager.DEFAULT_STORE_NAME); Assert.IsTrue(results.Any()); Assert.AreEqual(3, results.Count()); Assert.IsFalse(results[0].HasError, "This call to StoreAndDeploy() should have no errors storing certificate"); - Assert.AreEqual(results[0].Category, "CertificateStorage"); - Assert.IsTrue(results[0].Description.Contains("Certificate stored OK")); - Assert.AreEqual(results[0].Title, "Certificate Stored"); + Assert.AreEqual("CertificateStorage", results[0].Category); + Assert.IsTrue(results[0].Description.Contains("Certificate stored OK"), $"Unexpected description: '{results[0].Description}'"); + Assert.AreEqual("Certificate Stored", results[0].Title); Assert.IsFalse(results[1].HasError, "This call to StoreAndDeploy() should not have an error adding binding while deploying certificate"); - Assert.AreEqual(results[1].Category, "Deployment.AddBinding"); - Assert.IsTrue(results[1].Description.Contains("Add https binding | | ***:443:test.com SNI**")); - Assert.AreEqual(results[1].Title, "Install Certificate For Binding"); + Assert.AreEqual("Deployment.AddBinding", results[1].Category); + Assert.IsTrue(results[1].Description.Contains("Add https binding | | ***:443:test.com SNI**"), $"Unexpected description: '{results[1].Description}'"); + Assert.AreEqual("Install Certificate For Binding", results[1].Title); Assert.IsFalse(results[2].HasError, "This call to StoreAndDeploy() should not have an error adding binding while deploying certificate"); - Assert.AreEqual(results[2].Category, "Deployment.UpdateBinding"); - Assert.IsTrue(results[2].Description.Contains("Update https binding | | **\\*:443:test.com SNI**")); - Assert.AreEqual(results[2].Title, "Install Certificate For Binding"); + Assert.AreEqual("Deployment.UpdateBinding", results[2].Category); + Assert.IsTrue(results[2].Description.Contains("Update https binding | | **\\*:443:test.com SNI**"), $"Unexpected description: '{results[2].Description}'"); + Assert.AreEqual("Install Certificate For Binding", results[2].Title); } #endif @@ -903,7 +903,6 @@ public async Task FtpBindingChecksNoPreview() new BindingInfo{ Host="ftp.test.com", IP="127.0.0.1", Port = 20, Protocol="ftp", IsFtpSite=true }, }; var deployment = new BindingDeploymentManager(); - var dummyCertPath = Environment.CurrentDirectory + "\\Assets\\dummycert.pfx"; var testManagedCert = new ManagedCertificate { Id = Guid.NewGuid().ToString(), @@ -924,30 +923,30 @@ public async Task FtpBindingChecksNoPreview() } }, ItemType = ManagedCertificateType.SSL_ACME, - CertificatePath = dummyCertPath + CertificatePath = _dummyCertPath }; var mockTarget = new MockBindingDeploymentTarget(); mockTarget.AllBindings = bindings; - var results = await deployment.StoreAndDeploy(mockTarget, testManagedCert, dummyCertPath, pfxPwd: "", false, Certify.Management.CertificateManager.DEFAULT_STORE_NAME); + var results = await deployment.StoreAndDeploy(mockTarget, testManagedCert, _dummyCertPath, pfxPwd: "", false, Certify.Management.CertificateManager.DEFAULT_STORE_NAME); Assert.IsTrue(results.Any()); Assert.AreEqual(3, results.Count()); Assert.IsFalse(results[0].HasError, "This call to StoreAndDeploy() should have no errors storing certificate"); - Assert.AreEqual(results[0].Category, "CertificateStorage"); - Assert.IsTrue(results[0].Description.Contains("Certificate stored OK")); - Assert.AreEqual(results[0].Title, "Certificate Stored"); + Assert.AreEqual("CertificateStorage", results[0].Category); + Assert.IsTrue(results[0].Description.Contains("Certificate stored OK"), $"Unexpected description: '{results[0].Description}'"); + Assert.AreEqual("Certificate Stored", results[0].Title); Assert.IsFalse(results[1].HasError, "This call to StoreAndDeploy() should not have an error adding binding while deploying certificate"); - Assert.AreEqual(results[1].Category, "Deployment.AddBinding"); - Assert.IsTrue(results[1].Description.Contains("Add ftp binding | | ***:21:ftp.test.com **")); - Assert.AreEqual(results[1].Title, "Install Certificate For FTP Binding"); + Assert.AreEqual("Deployment.UpdateBinding", results[1].Category); + Assert.IsTrue(results[1].Description.Contains("Update ftp binding | | ***:20:ftp.test.com**"), $"Unexpected description: '{results[1].Description}'"); + Assert.AreEqual("Install Certificate For Binding", results[1].Title); Assert.IsFalse(results[2].HasError, "This call to StoreAndDeploy() should not have an error adding binding while deploying certificate"); - Assert.AreEqual(results[2].Category, "Deployment.AddBinding"); - Assert.IsTrue(results[2].Description.Contains("Add ftp binding | | ***:21:ftp.test.com **")); - Assert.AreEqual(results[2].Title, "Install Certificate For FTP Binding"); + Assert.AreEqual("Deployment.UpdateBinding", results[2].Category); + Assert.IsTrue(results[2].Description.Contains("Update ftp binding | | **127.0.0.1:20:ftp.test.com**"), $"Unexpected description: '{results[2].Description}'"); + Assert.AreEqual("Install Certificate For Binding", results[2].Title); } [TestMethod, Description("Test if ftp bindings are handled when not in preview with Certificate Request that defines a BindingIPAddress")] @@ -958,7 +957,6 @@ public async Task FtpBindingChecksCertReqBindingIPAddr() new BindingInfo{ Host="ftp.test.com", IP="127.0.0.1", Port = 20, Protocol="ftp", IsFtpSite=true }, }; var deployment = new BindingDeploymentManager(); - var dummyCertPath = Environment.CurrentDirectory + "\\Assets\\dummycert.pfx"; var testManagedCert = new ManagedCertificate { Id = Guid.NewGuid().ToString(), @@ -980,30 +978,30 @@ public async Task FtpBindingChecksCertReqBindingIPAddr() } }, ItemType = ManagedCertificateType.SSL_ACME, - CertificatePath = dummyCertPath + CertificatePath = _dummyCertPath }; var mockTarget = new MockBindingDeploymentTarget(); mockTarget.AllBindings = bindings; - var results = await deployment.StoreAndDeploy(mockTarget, testManagedCert, dummyCertPath, pfxPwd: "", false, Certify.Management.CertificateManager.DEFAULT_STORE_NAME); + var results = await deployment.StoreAndDeploy(mockTarget, testManagedCert, _dummyCertPath, pfxPwd: "", false, Certify.Management.CertificateManager.DEFAULT_STORE_NAME); Assert.IsTrue(results.Any()); Assert.AreEqual(3, results.Count()); Assert.IsFalse(results[0].HasError, "This call to StoreAndDeploy() should have no errors storing certificate"); - Assert.AreEqual(results[0].Category, "CertificateStorage"); - Assert.IsTrue(results[0].Description.Contains("Certificate stored OK")); - Assert.AreEqual(results[0].Title, "Certificate Stored"); + Assert.AreEqual("CertificateStorage", results[0].Category); + Assert.IsTrue(results[0].Description.Contains("Certificate stored OK"), $"Unexpected description: {results[0].Description}"); + Assert.AreEqual("Certificate Stored", results[0].Title); Assert.IsFalse(results[1].HasError, "This call to StoreAndDeploy() should not have an error adding binding while deploying certificate"); - Assert.AreEqual(results[1].Category, "Deployment.AddBinding"); - Assert.IsTrue(results[1].Description.Contains("Add ftp binding | | **127.0.0.1:21:ftp.test.com **")); - Assert.AreEqual(results[1].Title, "Install Certificate For FTP Binding"); + Assert.AreEqual("Deployment.UpdateBinding", results[1].Category); + Assert.IsTrue(results[1].Description.Contains("Update ftp binding | | ***:20:ftp.test.com**"), $"Unexpected description: {results[1].Description}"); + Assert.AreEqual("Install Certificate For Binding", results[1].Title); Assert.IsFalse(results[2].HasError, "This call to StoreAndDeploy() should not have an error adding binding while deploying certificate"); - Assert.AreEqual(results[2].Category, "Deployment.AddBinding"); - Assert.IsTrue(results[2].Description.Contains("Add ftp binding | | **127.0.0.1:21:ftp.test.com **")); - Assert.AreEqual(results[2].Title, "Install Certificate For FTP Binding"); + Assert.AreEqual("Deployment.UpdateBinding", results[2].Category); + Assert.IsTrue(results[2].Description.Contains("Update ftp binding | | **127.0.0.1:20:ftp.test.com**"), $"Unexpected description: {results[2].Description}"); + Assert.AreEqual("Install Certificate For Binding", results[2].Title); } [TestMethod, Description("Test if ftp bindings are handled when not in preview with Certificate Request that defines a BindingPort")] @@ -1014,7 +1012,6 @@ public async Task FtpBindingChecksCertReqBindingPort() new BindingInfo{ Host="ftp.test.com", IP="127.0.0.1", Port = 20, Protocol="ftp", IsFtpSite=true }, }; var deployment = new BindingDeploymentManager(); - var dummyCertPath = Environment.CurrentDirectory + "\\Assets\\dummycert.pfx"; var testManagedCert = new ManagedCertificate { Id = Guid.NewGuid().ToString(), @@ -1036,30 +1033,30 @@ public async Task FtpBindingChecksCertReqBindingPort() } }, ItemType = ManagedCertificateType.SSL_ACME, - CertificatePath = dummyCertPath + CertificatePath = _dummyCertPath }; var mockTarget = new MockBindingDeploymentTarget(); mockTarget.AllBindings = bindings; - var results = await deployment.StoreAndDeploy(mockTarget, testManagedCert, dummyCertPath, pfxPwd: "", false, Certify.Management.CertificateManager.DEFAULT_STORE_NAME); + var results = await deployment.StoreAndDeploy(mockTarget, testManagedCert, _dummyCertPath, pfxPwd: "", false, Certify.Management.CertificateManager.DEFAULT_STORE_NAME); Assert.IsTrue(results.Any()); Assert.AreEqual(3, results.Count()); Assert.IsFalse(results[0].HasError, "This call to StoreAndDeploy() should have no errors storing certificate"); - Assert.AreEqual(results[0].Category, "CertificateStorage"); - Assert.IsTrue(results[0].Description.Contains("Certificate stored OK")); - Assert.AreEqual(results[0].Title, "Certificate Stored"); + Assert.AreEqual("CertificateStorage", results[0].Category); + Assert.IsTrue(results[0].Description.Contains("Certificate stored OK"), $"Unexpected description: '{results[0].Description}'"); + Assert.AreEqual("Certificate Stored", results[0].Title); Assert.IsFalse(results[1].HasError, "This call to StoreAndDeploy() should not have an error adding binding while deploying certificate"); - Assert.AreEqual(results[1].Category, "Deployment.AddBinding"); - Assert.IsTrue(results[1].Description.Contains("Add ftp binding | | ***:22:ftp.test.com **")); - Assert.AreEqual(results[1].Title, "Install Certificate For FTP Binding"); + Assert.AreEqual("Deployment.UpdateBinding", results[1].Category); + Assert.IsTrue(results[1].Description.Contains("Update ftp binding | | ***:20:ftp.test.com**"), $"Unexpected description: '{results[1].Description}'"); + Assert.AreEqual("Install Certificate For Binding", results[1].Title); Assert.IsFalse(results[2].HasError, "This call to StoreAndDeploy() should not have an error adding binding while deploying certificate"); - Assert.AreEqual(results[2].Category, "Deployment.AddBinding"); - Assert.IsTrue(results[2].Description.Contains("Add ftp binding | | ***:22:ftp.test.com **")); - Assert.AreEqual(results[2].Title, "Install Certificate For FTP Binding"); + Assert.AreEqual("Deployment.UpdateBinding", results[2].Category); + Assert.IsTrue(results[2].Description.Contains("Update ftp binding | | **127.0.0.1:20:ftp.test.com**"), $"Unexpected description: '{results[2].Description}'"); + Assert.AreEqual("Install Certificate For Binding", results[2].Title); } [TestMethod, Description("Test update bindings are skipped when using a protocol other than http, https, or ftp")] @@ -1069,7 +1066,6 @@ public async Task UpdateBindingSkippedUnsupportedProtocol() new BindingInfo{ Host="smtp.test.com", IP="127.0.0.1", Port = 587, Protocol="smtp" }, }; var deployment = new BindingDeploymentManager(); - var dummyCertPath = Environment.CurrentDirectory + "\\Assets\\dummycert.pfx"; var testManagedCert = new ManagedCertificate { Id = Guid.NewGuid().ToString(), @@ -1090,20 +1086,20 @@ public async Task UpdateBindingSkippedUnsupportedProtocol() } }, ItemType = ManagedCertificateType.SSL_ACME, - CertificatePath = dummyCertPath + CertificatePath = _dummyCertPath }; var mockTarget = new MockBindingDeploymentTarget(); mockTarget.AllBindings = bindings; - var results = await deployment.StoreAndDeploy(mockTarget, testManagedCert, dummyCertPath, pfxPwd: "", false, Certify.Management.CertificateManager.DEFAULT_STORE_NAME); + var results = await deployment.StoreAndDeploy(mockTarget, testManagedCert, _dummyCertPath, pfxPwd: "", false, Certify.Management.CertificateManager.DEFAULT_STORE_NAME); Assert.IsTrue(results.Any()); Assert.AreEqual(1, results.Count()); Assert.IsFalse(results[0].HasError, "This call to StoreAndDeploy() should have no errors storing certificate"); - Assert.AreEqual(results[0].Category, "CertificateStorage"); - Assert.IsTrue(results[0].Description.Contains("Certificate stored OK")); - Assert.AreEqual(results[0].Title, "Certificate Stored"); + Assert.AreEqual("CertificateStorage", results[0].Category); + Assert.IsTrue(results[0].Description.Contains("Certificate stored OK"), $"Unexpected description: '{results[0].Description}'"); + Assert.AreEqual("Certificate Stored", results[0].Title); } [TestMethod, Description("Test if ftp bindings are handled when not in preview")] @@ -1114,7 +1110,6 @@ public async Task FtpBindingChecksUpdateExisting() new BindingInfo{ Host="ftp.test.com", IP="127.0.0.1", Port = 21, Protocol="ftp", IsFtpSite=true }, }; var deployment = new BindingDeploymentManager(); - var dummyCertPath = Environment.CurrentDirectory + "\\Assets\\dummycert.pfx"; var testManagedCert = new ManagedCertificate { Id = Guid.NewGuid().ToString(), @@ -1135,40 +1130,40 @@ public async Task FtpBindingChecksUpdateExisting() } }, ItemType = ManagedCertificateType.SSL_ACME, - CertificatePath = dummyCertPath + CertificatePath = _dummyCertPath }; var mockTarget = new MockBindingDeploymentTarget(); mockTarget.AllBindings = bindings; - var results = await deployment.StoreAndDeploy(mockTarget, testManagedCert, dummyCertPath, pfxPwd: "", false, Certify.Management.CertificateManager.DEFAULT_STORE_NAME); + var results = await deployment.StoreAndDeploy(mockTarget, testManagedCert, _dummyCertPath, pfxPwd: "", false, Certify.Management.CertificateManager.DEFAULT_STORE_NAME); Assert.IsTrue(results.Any()); Assert.AreEqual(1, results.Count()); Assert.IsFalse(results[0].HasError, "This call to StoreAndDeploy() should have no errors storing certificate"); - Assert.AreEqual(results[0].Category, "CertificateStorage"); - Assert.IsTrue(results[0].Description.Contains("Certificate stored OK")); - Assert.AreEqual(results[0].Title, "Certificate Stored"); + Assert.AreEqual("CertificateStorage", results[0].Category); + Assert.IsTrue(results[0].Description.Contains("Certificate stored OK"), $"Unexpected description: '{results[0].Description}'"); + Assert.AreEqual("Certificate Stored", results[0].Title); testManagedCert.RequestConfig.DeploymentSiteOption = DeploymentOption.AllSites; - results = await deployment.StoreAndDeploy(mockTarget, testManagedCert, dummyCertPath, pfxPwd: "", false, Certify.Management.CertificateManager.DEFAULT_STORE_NAME); + results = await deployment.StoreAndDeploy(mockTarget, testManagedCert, _dummyCertPath, pfxPwd: "", false, Certify.Management.CertificateManager.DEFAULT_STORE_NAME); Assert.IsTrue(results.Any()); Assert.AreEqual(3, results.Count()); Assert.IsFalse(results[0].HasError, "This call to StoreAndDeploy() should have no errors storing certificate"); - Assert.AreEqual(results[0].Category, "CertificateStorage"); - Assert.IsTrue(results[0].Description.Contains("Certificate stored OK")); - Assert.AreEqual(results[0].Title, "Certificate Stored"); + Assert.AreEqual("CertificateStorage", results[0].Category); + Assert.IsTrue(results[0].Description.Contains("Certificate stored OK"), $"Unexpected description: '{results[0].Description}'"); + Assert.AreEqual("Certificate Stored", results[0].Title); Assert.IsFalse(results[1].HasError, "This call to StoreAndDeploy() should not have an error adding binding while deploying certificate"); - Assert.AreEqual(results[1].Category, "Deployment.UpdateBinding"); - Assert.IsTrue(results[1].Description.Contains("Update ftp binding | | ***:21:ftp.test.com**")); - Assert.AreEqual(results[1].Title, "Install Certificate For Binding"); + Assert.AreEqual("Deployment.UpdateBinding", results[1].Category); + Assert.IsTrue(results[1].Description.Contains("Update ftp binding | | ***:21:ftp.test.com**"), $"Unexpected description: '{results[1].Description}'"); + Assert.AreEqual("Install Certificate For Binding", results[1].Title); Assert.IsFalse(results[2].HasError, "This call to StoreAndDeploy() should not have an error adding binding while deploying certificate"); - Assert.AreEqual(results[2].Category, "Deployment.UpdateBinding"); - Assert.IsTrue(results[2].Description.Contains("Update ftp binding | | ***:21:ftp.test.com**")); - Assert.AreEqual(results[2].Title, "Install Certificate For Binding"); + Assert.AreEqual("Deployment.UpdateBinding", results[2].Category); + Assert.IsTrue(results[2].Description.Contains("Update ftp binding | | **127.0.0.1:21:ftp.test.com**"), $"Unexpected description: '{results[2].Description}'"); + Assert.AreEqual("Install Certificate For Binding", results[2].Title); } [TestMethod, Description("Test that duplicate https bindings are not created when multiple non-port 443 same-hostname bindings exist")] diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj index 262d6e2de..5dc6ed89f 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj @@ -81,6 +81,10 @@ PackageReference PackageReference + + + portable + x64 @@ -136,7 +140,6 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - @@ -146,6 +149,7 @@ + diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/CertifyManagerAccountTests.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/CertifyManagerAccountTests.cs index 6f59667d3..1aa31f23d 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/CertifyManagerAccountTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/CertifyManagerAccountTests.cs @@ -1,26 +1,312 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; +using System.Net.Http; +using System.Runtime.InteropServices; +using System.Text; using System.Threading.Tasks; using Certify.ACME.Anvil; using Certify.Management; using Certify.Models; +using Certify.Providers.ACME.Anvil; +using DotNet.Testcontainers.Builders; +using DotNet.Testcontainers.Containers; +using DotNet.Testcontainers.Volumes; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Newtonsoft.Json; +using Serilog; namespace Certify.Core.Tests.Unit { [TestClass] - public class CertifyManagerAccountTests + public class CertifyManagerAccountTests : IDisposable { private readonly CertifyManager _certifyManager; + private readonly bool _isContainer = Environment.GetEnvironmentVariable("DOTNET_RUNNING_IN_CONTAINER") == "true"; + private readonly bool _isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + private readonly string _caDomain; + private readonly int _caPort; + private IContainer _caContainer; + private IVolume _stepVolume; + private CertificateAuthority _customCa; + private AccountDetails _customCaAccount; + private readonly Loggy _log; public CertifyManagerAccountTests() { _certifyManager = new CertifyManager(); _certifyManager.Init().Wait(); - var testCredentialsPath = Path.Combine(EnvironmentUtil.GetAppDataFolder(), "Tests", ".env.test_accounts"); - DotNetEnv.Env.Load(testCredentialsPath); + _log = new Loggy(new LoggerConfiguration().WriteTo.Debug().CreateLogger()); + + _caDomain = _isContainer ? "step-ca" : "localhost"; + _caPort = 9000; + + BootstrapStepCa().Wait(); + CheckCustomCaIsRunning().Wait(); + AddCustomCa().Wait(); + AddNewAccount().Wait(); + } + + private async Task BootstrapStepCa() + { + string stepCaFingerprint; + + // If running in a container + if (_isContainer) + { + // Step container volume path containing step-ca config based on OS + var configPath = _isWindows ? "C:\\step_share\\config\\defaults.json" : "/mnt/step_share/config/defaults.json"; + + // Wait till step-ca config file is written + while (!File.Exists(configPath)) { } + + // Read step-ca fingerprint from config file + var stepCaConfigJson = JsonReader.ReadFile(configPath); + stepCaFingerprint = stepCaConfigJson.fingerprint; + } + else + { + // Start new step-ca container + await StartStepCaContainer(); + + // Read step-ca fingerprint from config file + var stepCaConfigBytes = await _caContainer.ReadFileAsync("/home/step/config/defaults.json"); + var stepCaConfigJson = JsonReader.ReadBytes(stepCaConfigBytes); + stepCaFingerprint = stepCaConfigJson.fingerprint; + } + + // Run bootstrap command + //var command = $"ca bootstrap -f --ca-url https://{_caDomain}:{_caPort} --fingerprint {stepCaFingerprint} --install"; + var command = $"ca bootstrap -f --ca-url https://{_caDomain}:{_caPort} --fingerprint {stepCaFingerprint}"; + RunCommand("step", command, "Bootstrap Step CA Script", 1000 * 30); + } + + private async Task StartStepCaContainer() + { + try + { + // Create new volume for step-ca container + _stepVolume = new VolumeBuilder().WithName("step").Build(); + await _stepVolume.CreateAsync(); + + // Create new step-ca container + _caContainer = new ContainerBuilder() + .WithName("step-ca") + // Set the image for the container to "smallstep/step-ca:latest". + .WithImage("smallstep/step-ca:latest") + .WithVolumeMount(_stepVolume, "/home/step") + // Bind port 9000 of the container to port 9000 on the host. + .WithPortBinding(_caPort) + .WithEnvironment("DOCKER_STEPCA_INIT_NAME", "Smallstep") + .WithEnvironment("DOCKER_STEPCA_INIT_DNS_NAMES", _caDomain) + .WithEnvironment("DOCKER_STEPCA_INIT_REMOTE_MANAGEMENT", "true") + .WithEnvironment("DOCKER_STEPCA_INIT_ACME", "true") + // Wait until the HTTPS endpoint of the container is available. + .WithWaitStrategy(Wait.ForUnixContainer().UntilMessageIsLogged($"Serving HTTPS on :{_caPort} ...")) + // Build the container configuration. + .Build(); + + // Start step-ca container + await _caContainer.StartAsync(); + } + catch (Exception) + { + throw; + } + } + + private static class JsonReader + { + public static T ReadFile(string filePath) + { + using (var streamReader = new StreamReader(File.Open(filePath, FileMode.Open))) + { + using (var jsonTextReader = new JsonTextReader(streamReader)) + { + var serializer = new JsonSerializer(); + return serializer.Deserialize(jsonTextReader); + } + } + } + + public static T ReadBytes(byte[] bytes) + { + using (var stringReader = new StringReader(Encoding.UTF8.GetString(bytes))) + { + using (var jsonTextReader = new JsonTextReader(stringReader)) + { + var serializer = new JsonSerializer(); + return serializer.Deserialize(jsonTextReader); + } + } + } + } + + private class StepCaConfig + { + [JsonProperty(PropertyName = "ca-url")] + public string ca_url; + [JsonProperty(PropertyName = "ca-config")] + public string ca_config; + public string fingerprint; + public string root; + } + + private void RunCommand(string program, string args, string description = null, int timeoutMS = 1000 * 5) + { + if (description == null) { description = string.Concat(program, " ", args); } + + var output = ""; + var errorOutput = ""; + + var startInfo = new ProcessStartInfo() + { + FileName = program, + Arguments = args, + RedirectStandardInput = true, + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + }; + + var process = new Process() { StartInfo = startInfo }; + + process.OutputDataReceived += (obj, a) => + { + if (!string.IsNullOrWhiteSpace(a.Data)) + { + _log.Information(a.Data); + output += a.Data; + } + }; + + process.ErrorDataReceived += (obj, a) => + { + if (!string.IsNullOrWhiteSpace(a.Data)) + { + _log.Error($"Error: {a.Data}"); + errorOutput += a.Data; + } + }; + + try + { + process.Start(); + + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + + process.WaitForExit(timeoutMS); + } + catch (Exception exp) + { + _log.Error($"Error Running ${description}: " + exp.ToString()); + throw; + } + + _log.Information($"{description} Successful"); + } + + private async Task CheckCustomCaIsRunning() + { + var httpHandler = new HttpClientHandler(); + + httpHandler.ServerCertificateCustomValidationCallback = (message, certificate, chain, sslPolicyErrors) => true; + + var loggingHandler = new LoggingHandler(httpHandler, _log); + var stepCaHttp = new HttpClient(loggingHandler); + var healthRes = await stepCaHttp.GetAsync($"https://{_caDomain}:{_caPort}/health"); + var healthResStr = await healthRes.Content.ReadAsStringAsync(); + Assert.AreEqual("{\"status\":\"ok\"}\n", (healthResStr)); + } + + private async Task AddCustomCa() + { + _customCa = new CertificateAuthority + { + Id = "step-ca", + Title = "Custom Step CA", + IsCustom = true, + IsEnabled = true, + APIType = CertAuthorityAPIType.ACME_V2.ToString(), + ProductionAPIEndpoint = $"https://{_caDomain}:{_caPort}/acme/acme/directory", + StagingAPIEndpoint = $"https://{_caDomain}:{_caPort}/acme/acme/directory", + RequiresEmailAddress = true, + AllowUntrustedTls = true, + SANLimit = 100, + StandardExpiryDays = 90, + SupportedFeatures = new List + { + CertAuthoritySupportedRequests.DOMAIN_SINGLE.ToString(), + CertAuthoritySupportedRequests.DOMAIN_MULTIPLE_SAN.ToString(), + CertAuthoritySupportedRequests.DOMAIN_WILDCARD.ToString() + }, + SupportedKeyTypes = new List{ + StandardKeyTypes.ECDSA256, + } + }; + var updateCaRes = await _certifyManager.UpdateCertificateAuthority(_customCa); + Assert.IsTrue(updateCaRes.IsSuccess, $"Expected Custom CA creation for CA with ID {_customCa.Id} to be successful"); + } + + private async Task AddNewAccount() + { + if (_customCa?.Id != null) + { + var contactRegistration = new ContactRegistration + { + AgreedToTermsAndConditions = true, + CertificateAuthorityId = _customCa.Id, + EmailAddress = "admin." + Guid.NewGuid().ToString().Substring(0, 6) + "@test.com", + ImportedAccountKey = "", + ImportedAccountURI = "", + IsStaging = true + }; + + // Add account + var addAccountRes = await _certifyManager.AddAccount(contactRegistration); + Assert.IsTrue(addAccountRes.IsSuccess, $"Expected account creation to be successful for {contactRegistration.EmailAddress}"); + _customCaAccount = (await _certifyManager.GetAccountRegistrations()).Find(a => a.Email == contactRegistration.EmailAddress); + } + } + + public void Dispose() => Cleanup().Wait(); + + private async Task Cleanup() + { + if (_customCaAccount != null) + { + await _certifyManager.RemoveAccount(_customCaAccount.StorageKey, true); + } + + if (_customCa != null) + { + await _certifyManager.RemoveCertificateAuthority(_customCa.Id); + } + + if (!_isContainer) + { + await _caContainer.DisposeAsync(); + await _stepVolume.DeleteAsync(); + await _stepVolume.DisposeAsync(); + } + + var stepConfigPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".step", "config"); + if (Directory.Exists(stepConfigPath)) + { + Directory.Delete(stepConfigPath, true); + } + + var stepCertsPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".step", "certs"); + if (Directory.Exists(stepCertsPath)) + { + Directory.Delete(stepCertsPath, true); + } + + _certifyManager?.Dispose(); } [TestMethod, Description("Happy path test for using CertifyManager.GetAccountDetails()")] @@ -52,10 +338,10 @@ public async Task TestCertifyManagerGetAccountDetailsAllowCacheFalse() public async Task TestCertifyManagerGetAccountDetailsDefinedCertificateAuthorityId() { var testUrl = "test.com"; - var dummyManagedCert = (new ManagedCertificate { CurrentOrderUri = testUrl, UseStagingMode = true, CertificateAuthorityId = "letsencrypt.org" }); + var dummyManagedCert = (new ManagedCertificate { CurrentOrderUri = testUrl, UseStagingMode = true, CertificateAuthorityId = _customCa.Id }); var caAccount = await _certifyManager.GetAccountDetails(dummyManagedCert); Assert.IsNotNull(caAccount, "Expected result of CertifyManager.GetAccountDetails() to not be null"); - Assert.AreEqual("letsencrypt.org", caAccount.CertificateAuthorityId, $"Unexpected certificate authority id '{caAccount.CertificateAuthorityId}'"); + Assert.AreEqual(_customCa.Id, caAccount.CertificateAuthorityId, $"Unexpected certificate authority id '{caAccount.CertificateAuthorityId}'"); } [TestMethod, Description("Test for using CertifyManager.GetAccountDetails() when OverrideAccountDetails is defined in CertifyManager")] @@ -68,7 +354,7 @@ public async Task TestCertifyManagerGetAccountDetailsDefinedOverrideAccountDetai AccountURI = "", Title = "Dev", Email = "test@certifytheweb.com", - CertificateAuthorityId = "letsencrypt.org", + CertificateAuthorityId = _customCa.Id, StorageKey = "dev", IsStagingAccount = true, }; @@ -91,7 +377,7 @@ public async Task TestCertifyManagerGetAccountDetailsNoMatches() Assert.IsNull(caAccount, "Expected result of CertifyManager.GetAccountDetails() to be null"); } - [TestMethod, Description("Test for using CertifyManager.GetAccountDetails() when there is no matching account")] + [TestMethod, Description("Test for using CertifyManager.GetAccountDetails() when it is a resume order")] public async Task TestCertifyManagerGetAccountDetailsIsResumeOrder() { var testUrl = "test.com"; @@ -120,7 +406,7 @@ public async Task TestCertifyManagerAddAccount() var contactRegistration = new ContactRegistration { AgreedToTermsAndConditions = true, - CertificateAuthorityId = "letsencrypt.org", + CertificateAuthorityId = _customCa.Id, EmailAddress = contactRegEmail, ImportedAccountKey = "", ImportedAccountURI = "", @@ -151,7 +437,7 @@ public async Task TestCertifyManagerRemoveAccount() var contactRegistration = new ContactRegistration { AgreedToTermsAndConditions = true, - CertificateAuthorityId = "letsencrypt.org", + CertificateAuthorityId = _customCa.Id, EmailAddress = contactRegEmail, ImportedAccountKey = "", ImportedAccountURI = "", @@ -179,7 +465,7 @@ public async Task TestCertifyManagerAddAccountDidNotAgree() var contactRegistration = new ContactRegistration { AgreedToTermsAndConditions = false, - CertificateAuthorityId = "letsencrypt.org", + CertificateAuthorityId = _customCa.Id, EmailAddress = contactRegEmail, ImportedAccountKey = "", ImportedAccountURI = "", @@ -225,10 +511,10 @@ public async Task TestCertifyManagerAddAccountMissingAccountKey() var contactRegistration = new ContactRegistration { AgreedToTermsAndConditions = true, - CertificateAuthorityId = "letsencrypt.org", + CertificateAuthorityId = _customCa.Id, EmailAddress = contactRegEmail, ImportedAccountKey = "", - ImportedAccountURI = "https://acme-staging-v02.api.letsencrypt.org/acme/acct/123403114", + ImportedAccountURI = _customCaAccount.AccountURI, IsStaging = true }; @@ -248,9 +534,9 @@ public async Task TestCertifyManagerAddAccountMissingAccountUri() var contactRegistration = new ContactRegistration { AgreedToTermsAndConditions = true, - CertificateAuthorityId = "letsencrypt.org", + CertificateAuthorityId = _customCa.Id, EmailAddress = contactRegEmail, - ImportedAccountKey = DotNetEnv.Env.GetString("RESTORE_KEY_PEM"), + ImportedAccountKey = _customCaAccount.AccountKey, ImportedAccountURI = "", IsStaging = true }; @@ -271,10 +557,10 @@ public async Task TestCertifyManagerAddAccountBadAccountKey() var contactRegistration = new ContactRegistration { AgreedToTermsAndConditions = true, - CertificateAuthorityId = "letsencrypt.org", + CertificateAuthorityId = _customCa.Id, EmailAddress = contactRegEmail, ImportedAccountKey = "tHiSiSnOtApEm", - ImportedAccountURI = DotNetEnv.Env.GetString("RESTORE_ACCOUNT_URI"), + ImportedAccountURI = _customCaAccount.AccountURI, IsStaging = true }; @@ -289,30 +575,28 @@ public async Task TestCertifyManagerAddAccountBadAccountKey() [TestMethod, Description("Test for CertifyManager.AddAccount() when ImportedAccountKey and ImportedAccountURI are valid")] public async Task TestCertifyManagerAddAccountImport() { + // Remove account + var removeAccountRes = await _certifyManager.RemoveAccount(_customCaAccount.StorageKey); + Assert.IsTrue(removeAccountRes.IsSuccess, $"Expected account removal to be successful for {_customCaAccount.Email}"); + var accountDetails = (await _certifyManager.GetAccountRegistrations()).Find(a => a.Email == _customCaAccount.Email); + Assert.IsNull(accountDetails, $"Did not expect an account for {_customCaAccount.Email} to be returned by CertifyManager.GetAccountRegistrations()"); + // Setup account registration info - //var contactRegEmail = "admin.98b9a6@test.com"; - var contactRegEmail = DotNetEnv.Env.GetString("RESTORE_ACCOUNT_EMAIL"); var contactRegistration = new ContactRegistration { AgreedToTermsAndConditions = true, - CertificateAuthorityId = "letsencrypt.org", - EmailAddress = contactRegEmail, - ImportedAccountKey = DotNetEnv.Env.GetString("RESTORE_KEY_PEM"), - ImportedAccountURI = DotNetEnv.Env.GetString("RESTORE_ACCOUNT_URI"), + CertificateAuthorityId = _customCa.Id, + EmailAddress = _customCaAccount.Email, + ImportedAccountKey = _customCaAccount.AccountKey, + ImportedAccountURI = _customCaAccount.AccountURI, IsStaging = true }; // Add account var addAccountRes = await _certifyManager.AddAccount(contactRegistration); - Assert.IsTrue(addAccountRes.IsSuccess, $"Expected account creation to be successful for {contactRegEmail}"); - var accountDetails = (await _certifyManager.GetAccountRegistrations()).Find(a => a.Email == contactRegEmail); - Assert.IsNotNull(accountDetails, $"Expected one of the accounts returned by CertifyManager.GetAccountRegistrations() to be for {contactRegEmail}"); - - // Remove account - var removeAccountRes = await _certifyManager.RemoveAccount(accountDetails.StorageKey); - Assert.IsTrue(removeAccountRes.IsSuccess, $"Expected account removal to be successful for {contactRegEmail}"); - accountDetails = (await _certifyManager.GetAccountRegistrations()).Find(a => a.Email == contactRegEmail); - Assert.IsNull(accountDetails, $"Did not expect an account for {contactRegEmail} to be returned by CertifyManager.GetAccountRegistrations()"); + Assert.IsTrue(addAccountRes.IsSuccess, $"Expected account creation to be successful for {_customCaAccount.Email}"); + accountDetails = (await _certifyManager.GetAccountRegistrations()).Find(a => a.Email == _customCaAccount.Email); + Assert.IsNotNull(accountDetails, $"Expected one of the accounts returned by CertifyManager.GetAccountRegistrations() to be for {_customCaAccount.Email}"); } [TestMethod, Description("Test for using CertifyManager.RemoveAccount() with a bad storage key")] @@ -336,7 +620,7 @@ public async Task TestCertifyManagerGetAccountAndAcmeProvider() var contactRegistration = new ContactRegistration { AgreedToTermsAndConditions = true, - CertificateAuthorityId = "letsencrypt.org", + CertificateAuthorityId = _customCa.Id, EmailAddress = contactRegEmail, ImportedAccountKey = "", ImportedAccountURI = "", @@ -383,7 +667,7 @@ public async Task TestCertifyManagerUpdateAccountContact() var contactRegistration = new ContactRegistration { AgreedToTermsAndConditions = true, - CertificateAuthorityId = "letsencrypt.org", + CertificateAuthorityId = _customCa.Id, EmailAddress = contactRegEmail, ImportedAccountKey = "", ImportedAccountURI = "", @@ -401,7 +685,7 @@ public async Task TestCertifyManagerUpdateAccountContact() var newContactRegistration = new ContactRegistration { AgreedToTermsAndConditions = true, - CertificateAuthorityId = "letsencrypt.org", + CertificateAuthorityId = _customCa.Id, EmailAddress = newContactRegEmail, ImportedAccountKey = "", ImportedAccountURI = "", @@ -424,7 +708,7 @@ public async Task TestCertifyManagerUpdateAccountContactNoAgreement() var contactRegistration = new ContactRegistration { AgreedToTermsAndConditions = true, - CertificateAuthorityId = "letsencrypt.org", + CertificateAuthorityId = _customCa.Id, EmailAddress = contactRegEmail, ImportedAccountKey = "", ImportedAccountURI = "", @@ -442,7 +726,7 @@ public async Task TestCertifyManagerUpdateAccountContactNoAgreement() var newContactRegistration = new ContactRegistration { AgreedToTermsAndConditions = false, - CertificateAuthorityId = "letsencrypt.org", + CertificateAuthorityId = _customCa.Id, EmailAddress = newContactRegEmail, ImportedAccountKey = "", ImportedAccountURI = "", @@ -468,7 +752,7 @@ public async Task TestCertifyManagerUpdateAccountContactBadKey() var contactRegistration = new ContactRegistration { AgreedToTermsAndConditions = true, - CertificateAuthorityId = "letsencrypt.org", + CertificateAuthorityId = _customCa.Id, EmailAddress = contactRegEmail, ImportedAccountKey = "", ImportedAccountURI = "", @@ -486,7 +770,7 @@ public async Task TestCertifyManagerUpdateAccountContactBadKey() var newContactRegistration = new ContactRegistration { AgreedToTermsAndConditions = true, - CertificateAuthorityId = "letsencrypt.org", + CertificateAuthorityId = _customCa.Id, EmailAddress = newContactRegEmail, ImportedAccountKey = "", ImportedAccountURI = "", @@ -582,7 +866,7 @@ public async Task TestCertifyManagerChangeAccountKeyBadStorageKey() var contactRegistration = new ContactRegistration { AgreedToTermsAndConditions = true, - CertificateAuthorityId = "letsencrypt.org", + CertificateAuthorityId = _customCa.Id, EmailAddress = contactRegEmail, ImportedAccountKey = "", ImportedAccountURI = "", @@ -618,7 +902,7 @@ public async Task TestCertifyManagerChangeAccountKeyBadAccountKey() var contactRegistration = new ContactRegistration { AgreedToTermsAndConditions = true, - CertificateAuthorityId = "letsencrypt.org", + CertificateAuthorityId = _customCa.Id, EmailAddress = contactRegEmail, ImportedAccountKey = "", ImportedAccountURI = "", @@ -801,7 +1085,7 @@ public async Task TestCertifyManagerRemoveCertificateAuthorityBadId() // Delete custom CA var deleteCaRes = await _certifyManager.RemoveCertificateAuthority(badId); Assert.IsFalse(deleteCaRes.IsSuccess, $"Expected Custom CA deletion for CA with ID {badId} to be unsuccessful"); - Assert.AreEqual(deleteCaRes.Message, "An error occurred removing the indicated Custom CA from the Certificate Authorities list.", "Unexpected result message for CertifyManager.RemoveCertificateAuthority() failure"); + Assert.AreEqual(deleteCaRes.Message, $"The certificate authority {badId} was not found in the list of custom CAs and could not be removed.", "Unexpected result message for CertifyManager.RemoveCertificateAuthority() failure"); } } } diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/Docker.md b/src/Certify.Tests/Certify.Core.Tests.Unit/Docker.md new file mode 100644 index 000000000..86c5d8c65 --- /dev/null +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/Docker.md @@ -0,0 +1,169 @@ +# Certify Core Unit Test Docker Images and Containers + +## Building Certify Core Unit Test Docker Images + +To build the Docker Images for the Certify Core Unit Tests, first navigate to `certify\src\Certify.Tests\Certify.Core.Tests.Unit` in a console window or Visual Studio's Developer Powershell Panel + +### Linux Images + +To build the Linux images, use the following Docker build commands (make sure Docker Desktop is switched to Linux containers): + +``` +// For .NET Core 8.0 +docker build ..\..\..\..\ -t certify-core-tests-unit-8_0-linux -f .\certify-core-tests-unit-8_0-linux.dockerfile +``` + +### Windows Images + +To build the Windows images, use the following Docker build commands (make sure Docker Desktop is switched to Windows containers): + +``` +// For .NET 4.6.2 +docker build ..\..\..\..\ -t certify-core-tests-unit-4_6_2-win -f .\certify-core-tests-unit-4_6_2-win.dockerfile -m 8GB + +// For .NET Core 8.0 +docker build ..\..\..\..\ -t certify-core-tests-unit-8_0-win -f .\certify-core-tests-unit-8_0-win.dockerfile -m 8GB + +// For the step-ca-win image +docker build . -t step-ca-win -f .\step-ca-win.dockerfile +``` + + +Since the context built for the Docker Daemon is quite large for the Certify images in Windows (depending on the size of your Certify workspace), you may need to run this in a Powershell terminal outside of Visual Studio with the IDE and other memory-heavy apps closed down (especailly if you have low RAM). + + +## Running Certify Core Unit Test Containers with Docker Compose + +### Linux Test Runs + +To run the Linux Tests in Docker, use the following Docker Compose command: + +``` +docker compose -f linux_compose.yaml up -d +``` + +To stop the Linux Tests in Docker, use the following Docker Compose command: + +``` +docker compose -f linux_compose.yaml down -v +``` + +### Windows Test Runs + +To run the Windows Tests in Docker, use the following Docker Compose command: + +``` +// For .NET 4.6.2 +docker compose --profile 4_6_2 -f windows_compose.yaml up -d + +// For .NET Core 8.0 +docker compose --profile 8_0 -f windows_compose.yaml up -d +``` + +To stop the Windows Tests in Docker, use the following Docker Compose command: + +``` +// For .NET 4.6.2 +docker compose --profile 4_6_2 -f windows_compose.yaml down -v + +// For .NET Core 8.0 +docker compose --profile 8_0 -f windows_compose.yaml down -v +``` + +### Debugging Tests Running in Containers with Docker Compose in Visual Studio + +Within each test Docker Compose file are commented out lines for debugging subsections of the Certify Core Unit Test code base. + +To run an individual class of tests, uncomment the following section of the corresponding Docker Compose file, with the name of the test class you wish to run following `ClassName=`: + +``` + entrypoint: "dotnet test Certify.Core.Tests.Unit.dll -f net8.0 --filter 'ClassName=Certify.Core.Tests.Unit.CertifyManagerAccountTests'" +``` + +To run an individual test, uncomment the following section of the corresponding Docker Compose file, with +the name of the test you wish to run following `Name=`: + +``` + entrypoint: "dotnet test Certify.Core.Tests.Unit.dll -f net8.0 --filter 'Name=TestCertifyManagerGetAccountDetailsDefinedCertificateAuthorityId'" +``` + +To run tests using the Visual Studio debugger, first ensure that you have the `Containers` window visible (`View -> Other Windows -> Containers`) + +Then, uncomment the following section of the corresponding Docker Compose file you wish to run: +``` + environment: + VSTEST_HOST_DEBUG: 1 +``` + +After starting the Docker Compose file with the `up` command, the container for the Unit Tests will show in the logs a message like this, showing the debug process to attach to (this may take a second while it waits for the health check of the Step-CA container): + +``` +Host debugging is enabled. Please attach debugger to testhost process to continue. +Process Id: 2044, Name: testhost +Waiting for debugger attach... +Process Id: 2044, Name: testhost +``` + +To attach to the process, right-click on the Unit Test container in the Visual Studio Container window, and select `Attach To Process`. Visual Studio may download the Debug tool to your container if missing. + +Visual Studio will then bring up a new window showing the running Processes on the selected container. Double-click the Process with the matching ID number from the logging. + +![Screenshot of the Visual Studio Attach to Process Window](../../../docs/images/VS_Container_Debug_Attach_To_Process_Window.png) + +For Linux containers, you may additionally have to select the proper code type for debugging in the following window (Always choose `Managed (.NET Core for Unix)`): + +![Screenshot of the Visual Studio Select Code Type Window](../../../docs/images/VS_Container_Debug_Select_Code_Type_Window.png) + +The Visual Studio's debugger will then take a moment to attach. Once ready, you will need to click the `Continue` button to start test code execution. + +**Be sure to uncomment any debugging lines from the compose files before committing changes to the `certify` repo.** + +## Running Certify Core Unit Tests with a Base Docker Image (No Building) + +Since building a custom image can take time while doing local development, you can also use a the base images referenced in the Dockerfiles for this project to run your code on your machine in a container. + +To do this, first navigate to `certify\src\Certify.Tests\Certify.Core.Tests.Unit` in a console window or Visual Studio's Developer Powershell Panel. + +### Running Certify Core Unit Tests with a Linux Base Image + +**Note: CertifyManagerAccountTests tests will not work properly unless a Docker container for step-ca has been started with the hostname `step-ca`** + +To run all of the Certify Core Unit Tests in a Linux container, use the following command: + +``` +docker run --name core-tests-unit-8_0-linux --rm -it -v ${pwd}\bin\Debug\net8.0:/app -w /app mcr.microsoft.com/dotnet/sdk:8.0 dotnet test Certify.Core.Tests.Unit.dll -f net8.0 +``` + +To run a specifi class of Certify Core Unit Tests in a Linux container, use the following command, substituting the Class Name of the tests after `--filter "ClassName=`: + +``` +docker run --name core-tests-unit-8_0-linux --rm -it -v ${pwd}\bin\Debug\net8.0:/app -w /app mcr.microsoft.com/dotnet/sdk:8.0 dotnet test Certify.Core.Tests.Unit.dll -f net8.0 --filter "ClassName=Certify.Core.Tests.Unit.DnsQueryTests" +``` + +To run a single Certify Core Unit Test in a Linux container, use the following command, substituting the Test Name of the tests after `--filter "Name=`: + +``` +docker run --name core-tests-unit-8_0-linux --rm -it -v ${pwd}\bin\Debug\net8.0:/app -w /app mcr.microsoft.com/dotnet/sdk:8.0 dotnet test Certify.Core.Tests.Unit.dll -f net8.0 --filter "Name=MixedIPBindingChecksNoPreview" +``` + +To run Certify Core Unit Tests with Debugging in a Linux container, use the following command, add `-e VSTEST_HOST_DEBUG=1` as a `docker run` parameter like so: + +``` +docker run --name core-tests-unit-8_0-linux --rm -it -e VSTEST_HOST_DEBUG=1 -v ${pwd}\bin\Debug\net8.0:/app -w /app mcr.microsoft.com/dotnet/sdk:8.0 dotnet test Certify.Core.Tests.Unit.dll -f net8.0" +``` + +### Running Certify Core Unit Tests with a Windows Base Image + +**Note: CertifyManagerAccountTests tests will not work properly unless a Docker container for step-ca-win has been started with the hostname `step-ca`** + +To run all of the Certify Core Unit Tests in a Windows container, use the following command: + +``` +// For .NET 4.6.2 +docker run --name core-tests-unit-4_6_2-win --rm -it -v ${pwd}\bin\Debug\net462:C:\app -w C:\app mcr.microsoft.com/dotnet/sdk:8.0-windowsservercore-ltsc2022 dotnet test Certify.Core.Tests.Unit.dll -f net462 + +// For .NET Core 8.0 +docker run --name core-tests-unit-8_0-win --rm -it -v ${pwd}\bin\Debug\net8.0:C:\app -w C:\app mcr.microsoft.com/dotnet/sdk:8.0-windowsservercore-ltsc2022 dotnet test Certify.Core.Tests.Unit.dll -f net8.0 +``` + +See the above Linux examples to see how to run tests selectively or with debugging enabled. diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/GetDnsProviderTests.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/GetDnsProviderTests.cs index cc5e74f9d..2d24d18c8 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/GetDnsProviderTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/GetDnsProviderTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Runtime.InteropServices; using System.Threading.Tasks; using Certify.Core.Management.Challenges; using Certify.Management; @@ -19,7 +20,7 @@ public GetDnsProviderTests() var pluginManager = new PluginManager(); pluginManager.LoadPlugins(new List { PluginManager.PLUGINS_DNS_PROVIDERS }); var TEST_PATH = "Tests\\credentials"; - credentialsManager = new SQLiteCredentialStore(storageSubfolder: TEST_PATH); + credentialsManager = new SQLiteCredentialStore(RuntimeInformation.IsOSPlatform(OSPlatform.Windows), TEST_PATH); dnsHelper = new DnsChallengeHelper(credentialsManager); } @@ -58,6 +59,9 @@ public async Task TestGetDnsProvidersEmptyProviderTypeId() Assert.AreEqual("DNS Challenge API Provider not set or could not load.", result.Result.Message); Assert.IsFalse(result.Result.IsSuccess); Assert.IsFalse(result.Result.IsWarning); + + // Cleanup credentials + await credentialsManager.Delete(null, testCredential.StorageKey); } [TestMethod, Description("Test Getting DNS Provider with a bad CredentialId")] @@ -82,6 +86,9 @@ public async Task TestGetDnsProvidersBadCredentialId() Assert.AreEqual("DNS Challenge API Credentials could not be decrypted or no longer exists. The original user must be used for decryption.", result.Result.Message); Assert.IsFalse(result.Result.IsSuccess); Assert.IsFalse(result.Result.IsWarning); + + // Cleanup credentials + await credentialsManager.Delete(null, testCredential.StorageKey); } [TestMethod, Description("Test Getting DNS Provider")] @@ -107,6 +114,9 @@ public async Task TestGetDnsProviders() Assert.IsTrue(result.Result.IsSuccess); Assert.IsFalse(result.Result.IsWarning); Assert.AreEqual(testCredential.ProviderType, result.Provider.ProviderId); + + // Cleanup credentials + await credentialsManager.Delete(null, testCredential.StorageKey); } [TestMethod, Description("Test Getting Challenge API Providers")] diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/LoggyTests.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/LoggyTests.cs index a3283b9a4..226a37766 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/LoggyTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/LoggyTests.cs @@ -9,12 +9,25 @@ namespace Certify.Core.Tests.Unit [TestClass] public class LoggyTests { - private string logFilePath = "C:\\ProgramData\\certify\\Tests\\test.log"; + private string testsDataPath; + private string logFilePath; [TestInitialize] public void TestInitialize() { - File.Delete(this.logFilePath); + testsDataPath = Path.Combine(EnvironmentUtil.GetAppDataFolder(), "Tests"); + logFilePath = Path.Combine(testsDataPath, "test.log"); + + if (!Directory.Exists(testsDataPath)) + { + Directory.CreateDirectory(testsDataPath); + + } + + if (File.Exists(logFilePath)) + { + File.Delete(this.logFilePath); + } } [TestCleanup] @@ -56,10 +69,11 @@ public void TestLoggyErrorException() // Trigger an exception error and log it using Loggy.Error() var logMessage = "New Loggy Exception Error"; - var exceptionError = "System.IO.FileNotFoundException: Could not find file 'C:\\ProgramData\\certify\\Tests\\test1.log'."; + var badFilePath = Path.Combine(EnvironmentUtil.GetAppDataFolder(), "Tests", "test1.log"); + + var exceptionError = $"System.IO.FileNotFoundException: Could not find file '{badFilePath}'."; try { - var badFilePath = "C:\\ProgramData\\certify\\Tests\\test1.log"; var nullObject = File.ReadAllBytes(badFilePath); } catch (Exception e) diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/certify-core-tests-unit-4_6_2-win.dockerfile b/src/Certify.Tests/Certify.Core.Tests.Unit/certify-core-tests-unit-4_6_2-win.dockerfile new file mode 100644 index 000000000..46c6672da --- /dev/null +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/certify-core-tests-unit-4_6_2-win.dockerfile @@ -0,0 +1,29 @@ +FROM mcr.microsoft.com/dotnet/sdk:8.0-windowsservercore-ltsc2022 AS base +WORKDIR /app +EXPOSE 80 +EXPOSE 443 +EXPOSE 9696 + +# define build and copy required source files +FROM mcr.microsoft.com/dotnet/sdk:8.0-windowsservercore-ltsc2022 AS build +WORKDIR /src +COPY ./certify/src ./certify/src +COPY ./certify-plugins/src ./certify-plugins/src +COPY ./certify-internal/src/Certify.Plugins ./certify-internal/src/Certify.Plugins +COPY ./libs/anvil ./libs/anvil +RUN dotnet build ./certify/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj -f net462 -c Debug -o /app/build + +# build and publish (as Debug mode) to /app/publish +FROM build AS publish +COPY --from=build /app/build/x64/SQLite.Interop.dll /app/publish/x64/ +RUN dotnet publish ./certify/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj -f net462 -c Debug -o /app/publish +RUN dotnet publish ./certify-internal/src/Certify.Plugins/Plugins.All/Plugins.All.csproj -f net462 -c Debug -o /app/publish/Plugins +COPY ./libs/Posh-ACME/Posh-ACME /app/publish/Scripts/DNS/PoshACME + +# copy build from /app/publish in sdk image to final image +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . + +# run the service, alternatively we could runs tests etc +ENTRYPOINT ["dotnet", "test", "Certify.Core.Tests.Unit.dll", "-f", "net462"] diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/certify-core-tests-unit-8_0-linux.dockerfile b/src/Certify.Tests/Certify.Core.Tests.Unit/certify-core-tests-unit-8_0-linux.dockerfile new file mode 100644 index 000000000..5eb8e2c83 --- /dev/null +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/certify-core-tests-unit-8_0-linux.dockerfile @@ -0,0 +1,28 @@ +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS base +WORKDIR /app +EXPOSE 80 +EXPOSE 443 +EXPOSE 9696 +RUN wget https://dl.smallstep.com/gh-release/cli/docs-cli-install/v0.23.0/step-cli_0.23.0_amd64.deb && dpkg -i step-cli_0.23.0_amd64.deb + +# define build and copy required source files +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +WORKDIR /src +COPY ./certify/src ./certify/src +COPY ./certify-plugins/src ./certify-plugins/src +COPY ./certify-internal/src/Certify.Plugins ./certify-internal/src/Certify.Plugins +COPY ./libs/anvil ./libs/anvil + +# build and publish (as Release mode) to /app/publish +FROM build AS publish +RUN dotnet publish ./certify/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj -f net8.0 -c Debug -o /app/publish +RUN dotnet publish ./certify-internal/src/Certify.Plugins/Plugins.All/Plugins.All.csproj -f net8.0 -c Debug -o /app/publish/plugins +COPY ./libs/Posh-ACME/Posh-ACME /app/publish/Scripts/DNS/PoshACME + +# copy build from /app/publish in sdk image to final image +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . + +# run the service, alternatively we could runs tests etc +ENTRYPOINT ["dotnet", "test", "Certify.Core.Tests.Unit.dll", "-f", "net8.0"] diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/certify-core-tests-unit-8_0-win.dockerfile b/src/Certify.Tests/Certify.Core.Tests.Unit/certify-core-tests-unit-8_0-win.dockerfile new file mode 100644 index 000000000..feacfe1bf --- /dev/null +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/certify-core-tests-unit-8_0-win.dockerfile @@ -0,0 +1,31 @@ +FROM mcr.microsoft.com/dotnet/sdk:8.0-windowsservercore-ltsc2022 AS base +WORKDIR /app +EXPOSE 80 +EXPOSE 443 +EXPOSE 9696 +RUN mkdir C:\temp && pwsh -Command "Invoke-WebRequest -Method 'GET' -uri 'https://dl.smallstep.com/gh-release/cli/docs-cli-install/v0.24.4/step_windows_0.24.4_amd64.zip' -Outfile 'C:\temp\step_windows_0.24.4_amd64.zip'" && tar -oxzf C:\temp\step_windows_0.24.4_amd64.zip -C "C:\Program Files" && rmdir /s /q C:\temp +USER ContainerAdministrator +RUN setx /M PATH "%PATH%;C:\Program Files\step_0.24.4\bin" +USER ContainerUser + +# define build and copy required source files +FROM mcr.microsoft.com/dotnet/sdk:8.0-windowsservercore-ltsc2022 AS build +WORKDIR /src +COPY ./certify/src ./certify/src +COPY ./certify-plugins/src ./certify-plugins/src +COPY ./certify-internal/src/Certify.Plugins ./certify-internal/src/Certify.Plugins +COPY ./libs/anvil ./libs/anvil + +# build and publish (as Debug mode) to /app/publish +FROM build AS publish +RUN dotnet publish ./certify/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj -f net8.0 -c Debug -o /app/publish +RUN dotnet publish ./certify-internal/src/Certify.Plugins/Plugins.All/Plugins.All.csproj -f net8.0 -c Debug -o /app/publish/plugins +COPY ./libs/Posh-ACME/Posh-ACME /app/publish/Scripts/DNS/PoshACME + +# copy build from /app/publish in sdk image to final image +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . + +# run the service, alternatively we could runs tests etc +ENTRYPOINT ["dotnet", "test", "Certify.Core.Tests.Unit.dll", "-f", "net8.0"] diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/linux_compose.yaml b/src/Certify.Tests/Certify.Core.Tests.Unit/linux_compose.yaml new file mode 100644 index 000000000..d7274b88a --- /dev/null +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/linux_compose.yaml @@ -0,0 +1,37 @@ +name: certify-core-tests-unit-linux +services: + + certify-core-tests-unit-8_0: + image: certify-core-tests-unit-8_0-linux:latest + build: + context: ../../../../ + dockerfile: ./certify/src/Certify.Tests/Certify.Core.Tests.Unit/certify-core-tests-unit-8_0-linux.dockerfile + ports: + - 80:80 + - 443:443 + - 9696:9696 + # environment: + # VSTEST_HOST_DEBUG: 1 + volumes: + - step:/mnt/step_share + # entrypoint: "dotnet test Certify.Core.Tests.Unit.dll -f net8.0 --filter 'ClassName=Certify.Core.Tests.Unit.CertifyManagerAccountTests'" + # entrypoint: "dotnet test Certify.Core.Tests.Unit.dll -f net8.0 --filter 'Name=TestCertifyManagerGetAccountDetails'" + depends_on: + step-ca: + condition: service_healthy + + step-ca: + image: smallstep/step-ca:latest + hostname: step-ca + ports: + - 9000:9000 + environment: + DOCKER_STEPCA_INIT_NAME: Smallstep + DOCKER_STEPCA_INIT_DNS_NAMES: localhost,step-ca + DOCKER_STEPCA_INIT_REMOTE_MANAGEMENT: true + DOCKER_STEPCA_INIT_ACME: true + volumes: + - step:/home/step + +volumes: + step: {} diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/step-ca-win-init.bat b/src/Certify.Tests/Certify.Core.Tests.Unit/step-ca-win-init.bat new file mode 100644 index 000000000..c2a262060 --- /dev/null +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/step-ca-win-init.bat @@ -0,0 +1,23 @@ +FOR /F "tokens=* USEBACKQ" %%F IN (`step path`) DO ( + SET STEPPATH=%%F +) + +pwsh -Command "$psw = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'.tochararray() | Get-Random -Count 40 | Join-String;"^ + "echo $psw;"^ + "Out-File -FilePath "$Env:STEPPATH\password" -InputObject $psw;"^ + "Out-File -FilePath "$Env:STEPPATH\provisioner_password" -InputObject $psw;"^ + "Remove-Variable psw" + +step ca init --deployment-type standalone --name Smallstep --dns localhost --provisioner admin ^ +--password-file %STEPPATH%\password --provisioner-password-file %STEPPATH%\provisioner_password ^ +--address :9000 --remote-management --admin-subject step + +sdelete64 -accepteula -nobanner -q %STEPPATH%\provisioner_password + +move "%STEPPATH%\password" "%STEPPATH%\secrets\password" + +rmdir /s /q %STEPPATH%\db + +step ca provisioner add acme --type ACME + +step-ca --password-file %STEPPATH%\secrets\password %STEPPATH%\config\ca.json diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/step-ca-win.dockerfile b/src/Certify.Tests/Certify.Core.Tests.Unit/step-ca-win.dockerfile new file mode 100644 index 000000000..37cba87d9 --- /dev/null +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/step-ca-win.dockerfile @@ -0,0 +1,21 @@ +FROM mcr.microsoft.com/dotnet/sdk:8.0-nanoserver-ltsc2022 AS base +WORKDIR /app +EXPOSE 9000 +RUN mkdir C:\temp +RUN pwsh -Command "Invoke-WebRequest -Method 'GET' -uri 'https://dl.smallstep.com/gh-release/cli/docs-cli-install/v0.24.4/step_windows_0.24.4_amd64.zip' -Outfile 'C:\temp\step_windows_0.24.4_amd64.zip'" && tar -oxzf C:\temp\step_windows_0.24.4_amd64.zip -C "C:\Program Files" +RUN pwsh -Command "Invoke-WebRequest -Method 'GET' -uri 'https://dl.smallstep.com/gh-release/certificates/gh-release-header/v0.24.2/step-ca_windows_0.24.2_amd64.zip' -Outfile 'C:\temp\step-ca_windows_0.24.2_amd64.zip'" && tar -oxzf C:\temp\step-ca_windows_0.24.2_amd64.zip -C "C:\Program Files" +RUN mkdir "C:\Program Files\SDelete" && pwsh -Command "Invoke-WebRequest -Method 'GET' -uri 'https://download.sysinternals.com/files/SDelete.zip' -Outfile 'C:\temp\SDelete.zip'" && tar -oxzf C:\temp\SDelete.zip -C "C:\Program Files\SDelete" +RUN rmdir /s /q C:\temp +USER ContainerAdministrator +RUN setx /M PATH "%PATH%;C:\Program Files\step_0.24.4\bin;C:\Program Files\step-ca_0.24.2;C:\Program Files\SDelete" +USER ContainerUser + +FROM mcr.microsoft.com/dotnet/sdk:8.0-windowsservercore-ltsc2022 AS netapi + +FROM base AS final +COPY ./step-ca-win-init.bat . +COPY --from=netapi /Windows/System32/netapi32.dll /Windows/System32/netapi32.dll + +HEALTHCHECK CMD curl -Method GET -f http://localhost:9000/health || exit 1 + +CMD step-ca-win-init.bat && cmd diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/windows_compose.yaml b/src/Certify.Tests/Certify.Core.Tests.Unit/windows_compose.yaml new file mode 100644 index 000000000..80e4f500c --- /dev/null +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/windows_compose.yaml @@ -0,0 +1,57 @@ +name: certify-core-tests-unit-win +services: + + certify-core-tests-unit-8_0: + image: certify-core-tests-unit-8_0-win:latest + build: + context: ../../../../ + dockerfile: ./certify/src/Certify.Tests/Certify.Core.Tests.Unit/certify-core-tests-unit-8_0-win.dockerfile + # environment: + # VSTEST_HOST_DEBUG: 1 + ports: + - 80:80 + - 443:443 + - 9696:9696 + # entrypoint: "dotnet test Certify.Core.Tests.Unit.dll -f net8.0 --filter 'ClassName=Certify.Core.Tests.Unit.CertifyManagerAccountTests'" + # entrypoint: "dotnet test Certify.Core.Tests.Unit.dll -f net8.0 --filter 'Name=TestCertifyManagerGetAccountDetailsDefinedCertificateAuthorityId'" + volumes: + - step:C:\step_share + profiles: ["8_0"] + depends_on: + step-ca: + condition: service_healthy + + certify-core-tests-unit-4_6_2: + image: certify-core-tests-unit-4_6_2-win:latest + build: + context: ../../../../ + dockerfile: ./certify/src/Certify.Tests/Certify.Core.Tests.Unit/certify-core-tests-unit-4_6_2-win.dockerfile + # environment: + # VSTEST_HOST_DEBUG: 1 + ports: + - 80:80 + - 443:443 + - 9696:9696 + # entrypoint: "dotnet test Certify.Core.Tests.Unit.dll -f net462 --filter 'ClassName=Certify.Core.Tests.Unit.CertifyManagerAccountTests'" + # entrypoint: "dotnet test Certify.Core.Tests.Unit.dll -f net462 --filter 'Name=TestCertifyManagerGetAccountDetailsDefinedCertificateAuthorityId'" + volumes: + - step:C:\step_share + profiles: ["4_6_2"] + depends_on: + step-ca: + condition: service_healthy + + step-ca: + image: step-ca-win:latest + build: + context: . + dockerfile: ./step-ca-win.dockerfile + hostname: step-ca + profiles: ["4_6_2", "8_0"] + ports: + - 9000:9000 + volumes: + - step:C:\Users\ContainerUser\.step + +volumes: + step: {} From 20f5330755766db1d751669f302e63fc3a275dcc Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Mon, 11 Dec 2023 13:35:52 +0800 Subject: [PATCH 092/328] Package updates --- src/Certify.Models/Certify.Models.csproj | 2 +- .../DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj | 2 +- .../Certify.Server.Api.Public.csproj | 2 +- src/Certify.Service/App.config | 2 +- src/Certify.Service/Certify.Service.csproj | 2 +- .../Certify.Shared.Extensions.csproj | 2 +- src/Certify.Shared/Certify.Shared.Core.csproj | 2 +- .../Certify.Core.Tests.Integration.csproj | 6 +++--- .../Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj | 2 +- .../Certify.Service.Tests.Integration.csproj | 2 +- .../Certify.UI.Tests.Integration.csproj | 2 +- 11 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Certify.Models/Certify.Models.csproj b/src/Certify.Models/Certify.Models.csproj index a918579d1..9839a6be6 100644 --- a/src/Certify.Models/Certify.Models.csproj +++ b/src/Certify.Models/Certify.Models.csproj @@ -22,7 +22,7 @@ runtime; build; native; contentfiles; analyzers - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj index 6cc8ed027..76b0fc8d9 100644 --- a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj +++ b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj index 9f25dd140..c394d382e 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj +++ b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj @@ -13,7 +13,7 @@ - + diff --git a/src/Certify.Service/App.config b/src/Certify.Service/App.config index ec7fc9391..c1a021fc2 100644 --- a/src/Certify.Service/App.config +++ b/src/Certify.Service/App.config @@ -33,7 +33,7 @@ - + diff --git a/src/Certify.Service/Certify.Service.csproj b/src/Certify.Service/Certify.Service.csproj index 25b0969b0..d92277c5a 100644 --- a/src/Certify.Service/Certify.Service.csproj +++ b/src/Certify.Service/Certify.Service.csproj @@ -54,7 +54,7 @@ - + diff --git a/src/Certify.Shared.Extensions/Certify.Shared.Extensions.csproj b/src/Certify.Shared.Extensions/Certify.Shared.Extensions.csproj index e7fe84833..d64920bd5 100644 --- a/src/Certify.Shared.Extensions/Certify.Shared.Extensions.csproj +++ b/src/Certify.Shared.Extensions/Certify.Shared.Extensions.csproj @@ -23,7 +23,7 @@ - + diff --git a/src/Certify.Shared/Certify.Shared.Core.csproj b/src/Certify.Shared/Certify.Shared.Core.csproj index e28907b12..a2d5d4dae 100644 --- a/src/Certify.Shared/Certify.Shared.Core.csproj +++ b/src/Certify.Shared/Certify.Shared.Core.csproj @@ -19,7 +19,7 @@ - + diff --git a/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj b/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj index 74d0ec454..7f8024375 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj @@ -92,9 +92,9 @@ - - - + + + diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj index 5dc6ed89f..4cdfda0e1 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj @@ -141,7 +141,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj b/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj index d155c9add..14c598d36 100644 --- a/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj @@ -75,7 +75,7 @@ - + diff --git a/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj b/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj index 618dabff8..2a5f58f27 100644 --- a/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj @@ -71,7 +71,7 @@ - + From 74138c6d0058ac4972d304d920eeef88fd81ea76 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Tue, 12 Dec 2023 15:54:03 +0800 Subject: [PATCH 093/328] Core: implement queue for batch submission of status reports --- .../CertifyManager/CertifyManager.ManagedCertificates.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagedCertificates.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagedCertificates.cs index 95585ace2..64070d176 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagedCertificates.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagedCertificates.cs @@ -8,6 +8,7 @@ using Certify.Models.API; using Certify.Models.Providers; using Certify.Models.Reporting; +using Certify.Models.Shared; namespace Certify.Management { From 670c7ca7e7e9aa76754e7008277aaf33d2055f94 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Tue, 12 Dec 2023 17:17:55 +0800 Subject: [PATCH 094/328] Update choco scripts --- scripts/chocolatey/tools/chocolateyinstall.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/chocolatey/tools/chocolateyinstall.ps1 b/scripts/chocolatey/tools/chocolateyinstall.ps1 index 8ef9065d2..b12117449 100644 --- a/scripts/chocolatey/tools/chocolateyinstall.ps1 +++ b/scripts/chocolatey/tools/chocolateyinstall.ps1 @@ -1,4 +1,4 @@ -$ErrorActionPreference = 'Stop'; +$ErrorActionPreference = 'Stop'; $toolsDir = "$(Split-Path -parent $MyInvocation.MyCommand.Definition)" $url64 = 'https://certifytheweb.s3.amazonaws.com/downloads/archive/CertifyTheWebSetup_V6.1.2.exe' From 8c2ea56cc5725a058d10846173eb1e2022397678 Mon Sep 17 00:00:00 2001 From: Justin Nelson Date: Wed, 13 Dec 2023 20:09:16 -0800 Subject: [PATCH 095/328] First attempt adding testing Github Actions for Certify.Core.Tests.Unit Fix to the .NET Version number needed to download SDK Changes for pulling dependencies, and splitting 8.0 into two workflows Change ref type for plugins dependency branch More changes to cloning dependencies in GitActions Fix path for cloning certify-internal repo Testing actions without internal repo clone Fixing relative placement of cloning paths for dependency repos Setting repos for "side by side" checkout https://github.com/actions/checkout#checkout-multiple-repos-side-by-side Fix paths on dotnet tool steps Disable .NET 4.6.2 Test Workflow until cause of test stalls is fixed Default pull step for cloning webprofusion/certify-plugins Add step to setup Step CLI on Windows Test Runner Add Github Actions step to pull step-ca docker image on Windows runner Add Github Actions step to pull step-ca docker image on Linux runner Fix Setup Step CLI GitHub Actions step for Windows runner Fix Setup Step CLI GitHub Actions step for Linux runner Refactor step-ca-win to be more like step-ca Linux image with env args Changes to CertifyManagerAccountTests for Windows GitHub Actions runner Fix to CertifyManagerAccountTests for Windows GitHub Actions runner Fixes mounting Docker volume Adding Step-CA and Step CLI dependencies to .NET 4.6.2 test GitHub flow Fix to CertifyManagerAccountTests for Windows GitHub Actions runner Debug Windows runner on GitHub for CertifyManagerAccountTests Fixes for running CertifyManagerAccountTests in Windows GitHub Runner Tweak to command for Setup Step CLI step in Windows GitHub Runner More Fixes for missing LE Accounts expected by Unit Tests Debug step for Step CLI setup in Windows GitHub Runner More fixes to running tests in GitLab Runners More debugging info for fixing GitLab Runners More debugging info for Step CLI step in GitLab Runners Tweak for adding Step CLI to Windows GitLab Runner PATH Another tweak for adding Step CLI to Windows GitLab Runner PATH Path update for Step CLI in Windows GitLab Runner More GitHub Runner Tweaks Add to Windows GitHub Path using GITHUB_PATH Finishing up adding Step CLI to PATH in Windows GitHub Runners Testing adding code coverage report to GitHub Actions markup output Update sticky pull request comment version in Linux GitHub Action Try using EnricoMi/publish-unit-test-result-action GitHub action Make coverage report without GitHub Action, using ReportGenerator CLI Debug output files to Testresults folder from dotnet test Further debugging of test results output files Fix path for cobertura coverage file in report generation Add Coverlet Collector Package at runtime of GitHub Action for coverage Specify working directory when adding Coverlet Collector package Add coverlet.collector through project dependencies rather than CLI Changes to fix loading Coverlet Collector without running dotnet publish Remove coverage directory debug step Remove coverage directory debug step on Linux Tweaks to fix PR GitHub Actions Test jobs for Certify More tweaks to fix PR GitHub Actions Test jobs for Certify Edit to generate HTML Test Results Artifact Add .NET Core 3.1.x to GitHub Actions for Trx-To-HTML Tool Generate HTML Test Results report with built-in Logger Attempting to output CLI reports to Markdown in GITHUB_STEP_SUMMARY Fix for generating Test Results Markdown Tweaks to generating Markdown reports for Windows Runners Change Markup Report Titles and Display Order Add Folder parameter to Test Results Generation Trying to suppress build warnings from being added to annotations Trying out dorny/test-reporter action on Linux runner Fix to dorny/test-reporter step for Linux GitHub Actions runner Trying out Tyrrrz/GitHubActionsTestLogger on Linux Runner Fix for trying out Tyrrrz/GitHubActionsTestLogger on Linux Runner Another Fix for Tyrrrz/GitHubActionsTestLogger on Linux Runner Debug GITHUB_WORKSPACE value for Tyrrrz/GitHubActionsTestLogger Debug Tyrrrz/GitHubActionsTestLogger source link generation More debugging Tyrrrz/GitHubActionsTestLogger source link generation More debugging Tyrrrz/GitHubActionsTestLogger source link generation Cleanup GitHub Action Workflow YAML for .NET Core 8.0 on Windows & Linux Fix GitHub Action Workflow YAML for .NET Core 8.0 on Windows & Linux Tweak to GitHub Action Workflow YAML for .NET Core 8.0 on Windows Fix paths in dotnet test step for Windows .NET Core Action Debug Windows .NET Core 8.0 Test Result Generation Fix Windows .NET Core 8.0 Test Result Generation Separate Test Run and Test Results step for Windows Runner .NET Core 8.0 Restore working directory info to Windows test run step More YAML action updates for Linux and Windows .NET Core 8.0 runners More tweaks to GitHub Actions for test runs on Windows and Linux Fix to running tests in Linux GitHub Action Fix to path to Coverlet on Linux test runner Lint cleanup for ChallengeConfigMatchTests Certify Core Unit Tests Refactors to speed up execution of CertifyManagerAccountTests Unit Tests Test out using Linux Docker containers on Windows GitHub Action Runner Debug Docker location in GitHub Actions Windows Runner More debugging DockerCli.exe location in GitHub Actions Windows Runner More debugging DockerCli.exe location in GitHub Actions Windows Runner More debugging DockerCli.exe location in GitHub Actions Windows Runner More debugging DockerCli.exe location in GitHub Actions Windows Runner Simplify Docker Info command output in CertifyManagerAccountTests More debugging DockerCli.exe location in GitHub Actions Windows Runner More debugging DockerCli.exe location in GitHub Actions Windows Runner More debugging DockerCli.exe location in GitHub Actions Windows Runner More debugging DockerCli.exe location in GitHub Actions Windows Runner More debugging DockerCli.exe location in GitHub Actions Windows Runner Restore using Windows Docker Image for Windows Runners in GitHub Actions Test Caching NuGet Dependencies for Linux and Windows GitHub Actions Remove blank Constructor methods to enable debugging in .NET 4.6.2 Fix Unit/Integration tests stalling in 4.6.2 when using CertifyManager Re-enable Windows 4.6.2 Certify Core Unit Test GitHub Action Workflow Fix to Windows .NET 4.6.2 test run GitHub Action Workflow Disable Coverage Report for .NET 4.6.2 until issue can be determined Fix Test Result generation for Windows .NET 4.6.2 runs Fix for enabling Coverage Report on 4.6.2 Windows Test Runs Another fix for enabling Coverage Report on 4.6.2 Windows Test Runs Script fix for enabling Coverage Report on 4.6.2 Windows Test Runs YAML fixes for caching NuGet dependencies Fixes to Windows NuGet dependency caching Disable archiving Test Artifacts on all GitHub Actions Test Jobs More tweaks to NuGet Dependency caching in GitHub Actions Simplify Code Coverage Generation for 4.6.2 runs Simplify testing commands for GitHub Actions using .runsettings files Further fixes to GitHub Actions report generation Fix Linux Test Results Directory to match value from ${{ runner.os }} Filter unused DataStore Plugins from Unit Test Reports with Coverlet Add basic Unit Test for Certify.Service so it shows on 4.6.2 coverage Expand tests for Certify.Service Additional Certify.Service tests Update 4_6_2_Core_Unit_Tests_Win.yaml add conditions for plugin checkout to match current release and development branch names Update 8_0_Core_Unit_Tests_Linux.yaml Update conditional for plugin repo checkout Update 8_0_Core_Unit_Tests_Win.yaml Update conditional for plugin repo checkout --- .../workflows/4_6_2_Core_Unit_Tests_Win.yaml | 114 +++++ .../workflows/8_0_Core_Unit_Tests_Linux.yaml | 114 +++++ .../workflows/8_0_Core_Unit_Tests_Win.yaml | 114 +++++ .../CertifyManager/CertifyManager.cs | 11 +- .../CertRequestTests.cs | 3 +- .../CertifyManagerServerTypeTests.cs | 1 + .../CertifyManagerTests.cs | 5 + .../DeploymentPreviewTests.cs | 1 + .../DeploymentTaskTests.cs | 6 + .../RdapTests.cs | 6 - .../CAFailoverTests.cs | 4 +- .../Certify.Core.Tests.Unit.csproj | 10 + .../CertifyManagerAccountTests.cs | 249 ++++++---- .../CertifyServiceTests.cs | 430 ++++++++++++++++++ .../ChallengeConfigMatchTests.cs | 2 - .../Certify.Core.Tests.Unit/MiscTests.cs | 8 +- .../Certify.Core.Tests.Unit/RdapTests.cs | 6 - .../step-ca-win-init.bat | 52 ++- .../step-ca-win.dockerfile | 2 +- .../unit-test-462.runsettings | 48 ++ .../unit-test-8-0-linux.runsettings | 36 ++ .../unit-test-8-0.runsettings | 36 ++ .../windows_compose.yaml | 5 + 23 files changed, 1149 insertions(+), 114 deletions(-) create mode 100644 .github/workflows/4_6_2_Core_Unit_Tests_Win.yaml create mode 100644 .github/workflows/8_0_Core_Unit_Tests_Linux.yaml create mode 100644 .github/workflows/8_0_Core_Unit_Tests_Win.yaml create mode 100644 src/Certify.Tests/Certify.Core.Tests.Unit/CertifyServiceTests.cs create mode 100644 src/Certify.Tests/Certify.Core.Tests.Unit/unit-test-462.runsettings create mode 100644 src/Certify.Tests/Certify.Core.Tests.Unit/unit-test-8-0-linux.runsettings create mode 100644 src/Certify.Tests/Certify.Core.Tests.Unit/unit-test-8-0.runsettings diff --git a/.github/workflows/4_6_2_Core_Unit_Tests_Win.yaml b/.github/workflows/4_6_2_Core_Unit_Tests_Win.yaml new file mode 100644 index 000000000..eb7405068 --- /dev/null +++ b/.github/workflows/4_6_2_Core_Unit_Tests_Win.yaml @@ -0,0 +1,114 @@ +name: build and test .NET 4.6.2 Windows + +on: + push: + pull_request: + branches: [ release, development ] + paths: + - '**.cs' + - '**.csproj' + +env: + DOTNET_VERSION: '8.0.100' # The .NET SDK version to use + +jobs: + build-and-test: + # if: ${{ ! always() }} + name: build-and-test-windows + runs-on: windows-latest + steps: + - name: Clone webprofusion/certify + uses: actions/checkout@master + with: + path: ./certify + + - name: Clone webprofusion/anvil + uses: actions/checkout@master + with: + repository: webprofusion/anvil + ref: refs/heads/main + path: ./libs/anvil + + - name: Clone webprofusion/certify-plugins (development branch push) + if: ${{ github.event_name == 'push' && (contains(github.ref_name, '_dev') || github.ref_name == 'development') }} + uses: actions/checkout@master + with: + repository: webprofusion/certify-plugins + ref: refs/heads/development + path: ./certify-plugins + + - name: Clone webprofusion/certify-plugins (release branch push) + if: ${{ github.event_name == 'push' && (contains(github.ref_name, '_rel') || github.ref_name == 'release') }} + uses: actions/checkout@master + with: + repository: webprofusion/certify-plugins + ref: refs/heads/release + path: ./certify-plugins + + - name: Clone webprofusion/certify-plugins (pull request) + if: ${{ github.event_name == 'pull_request' }} + uses: actions/checkout@master + with: + repository: webprofusion/certify-plugins + ref: ${{ github.base_ref }} + path: ./certify-plugins + + - name: Setup .NET Core + uses: actions/setup-dotnet@master + with: + dotnet-version: ${{ env.DOTNET_VERSION }} + + - name: Setup Step CLI + run: | + Invoke-WebRequest -Method 'GET' -uri 'https://dl.smallstep.com/gh-release/cli/docs-cli-install/v0.24.4/step_windows_0.24.4_amd64.zip' -Outfile 'C:\temp\step_windows_0.24.4_amd64.zip' + tar -oxzf C:\temp\step_windows_0.24.4_amd64.zip -C "C:\Program Files" + echo "C:\Program Files\step_0.24.4\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + + - name: Pull step-ca Docker Image + run: docker pull jrnelson90/step-ca-win + + - name: Cache NuGet Dependencies + uses: actions/cache@v3 + with: + path: ~/.nuget/packages + # Look to see if there is a cache hit for the corresponding requirements file + key: ${{ runner.os }}-4.6.2-nuget-${{ hashFiles('./certify/src/Certify.Tests/Certify.Core.Tests.Unit/*.csproj') }} + restore-keys: | + ${{ runner.os }}-4.6.2-nuget + + - name: Install Dependencies & Build Certify.Core.Tests.Unit + run: | + dotnet tool install --global dotnet-reportgenerator-globaltool --version 5.2.0 + dotnet add package GitHubActionsTestLogger + dotnet add package coverlet.collector + dotnet build -c Debug -f net462 --property WarningLevel=0 /clp:ErrorsOnly + working-directory: ./certify/src/Certify.Tests/Certify.Core.Tests.Unit + + - name: Run Certify.Core.Tests.Unit Tests + run: | + $env:GITHUB_WORKSPACE="$env:GITHUB_WORKSPACE\certify" + $env:GITHUB_STEP_SUMMARY=".\TestResults-4_6_2-${{ runner.os }}\test-summary.md" + dotnet test --no-build -f net462 -l "GitHubActions;summary.includePassedTests=true;summary.includeSkippedTests=true;annotations.messageFormat=@error\n@trace" + working-directory: ./certify/src/Certify.Tests/Certify.Core.Tests.Unit + + - name: Generated Test Results Report + run: | + echo "# Test Results" | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Encoding utf8 + (Get-Content -Path .\TestResults-4_6_2-${{ runner.os }}\test-summary.md).Replace('
', '
') | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Encoding utf8 -Append + working-directory: ./certify/src/Certify.Tests/Certify.Core.Tests.Unit + if: ${{ always() }} + + - name: Generated Test Coverage Report + run: | + reportgenerator -reports:./TestResults-4_6_2-${{ runner.os }}/*/*.cobertura.xml -targetdir:./TestResults-4_6_2-${{ runner.os }} -reporttypes:MarkdownSummaryGithub "-title:Test Coverage" + Get-Content -Path ./TestResults-4_6_2-${{ runner.os }}/SummaryGithub.md | Out-File -FilePath $env:GITHUB_STEP_SUMMARY + working-directory: ./certify/src/Certify.Tests/Certify.Core.Tests.Unit + if: ${{ always() }} + + # - name: Upload dotnet test Artifacts + # uses: actions/upload-artifact@master + # with: + # name: dotnet-results-${{ runner.os }}-${{ env.DOTNET_VERSION }} + # path: ./certify/src/Certify.Tests/Certify.Core.Tests.Unit/TestResults-4_6_2-${{ runner.os }} + # # Use always() to always run this step to publish test results when there are test failures + # if: ${{ always() }} diff --git a/.github/workflows/8_0_Core_Unit_Tests_Linux.yaml b/.github/workflows/8_0_Core_Unit_Tests_Linux.yaml new file mode 100644 index 000000000..a55ee9b9a --- /dev/null +++ b/.github/workflows/8_0_Core_Unit_Tests_Linux.yaml @@ -0,0 +1,114 @@ +name: build and test .NET Core 8.0 Linux + +on: + push: + pull_request: + branches: [ release, development ] + paths: + - '**.cs' + - '**.csproj' + +env: + DOTNET_VERSION: '8.0.100' # The .NET SDK version to use + +jobs: + build-and-test: + + name: build-and-test-linux + runs-on: ubuntu-latest + steps: + - name: Clone webprofusion/certify + uses: actions/checkout@master + with: + path: ./certify + + - name: Clone webprofusion/anvil + uses: actions/checkout@master + with: + repository: webprofusion/anvil + ref: refs/heads/main + path: ./libs/anvil + + - name: Clone webprofusion/certify-plugins (development branch push) + if: ${{ github.event_name == 'push' && (contains(github.ref_name, '_dev') || github.ref_name == 'development') }} + uses: actions/checkout@master + with: + repository: webprofusion/certify-plugins + ref: refs/heads/development + path: ./certify-plugins + + - name: Clone webprofusion/certify-plugins (release branch push) + if: ${{ github.event_name == 'push' && (contains(github.ref_name, '_rel') || github.ref_name == 'release') }} + uses: actions/checkout@master + with: + repository: webprofusion/certify-plugins + ref: refs/heads/release + path: ./certify-plugins + + - name: Clone webprofusion/certify-plugins (pull request) + if: ${{ github.event_name == 'pull_request' }} + uses: actions/checkout@master + with: + repository: webprofusion/certify-plugins + ref: ${{ github.base_ref }} + path: ./certify-plugins + + - name: Setup .NET Core + uses: actions/setup-dotnet@master + with: + dotnet-version: ${{ env.DOTNET_VERSION }} + + - name: Setup Step CLI + run: | + wget https://dl.smallstep.com/gh-release/cli/docs-cli-install/v0.23.0/step-cli_0.23.0_amd64.deb + sudo dpkg -i step-cli_0.23.0_amd64.deb + + - name: Pull step-ca Docker Image + run: docker pull smallstep/step-ca + + - name: Cache NuGet Dependencies + uses: actions/cache@v3 + with: + path: ~/.nuget/packages + # Look to see if there is a cache hit for the corresponding requirements file + key: ${{ runner.os }}-${{ env.DOTNET_VERSION }}-nuget-${{ hashFiles('./certify/src/Certify.Tests/Certify.Core.Tests.Unit/*.csproj') }} + restore-keys: | + ${{ runner.os }}-${{ env.DOTNET_VERSION }}-nuget + + - name: Install Dependencies & Build Certify.Core.Tests.Unit + run: | + dotnet tool install --global dotnet-reportgenerator-globaltool --version 5.2.0 + dotnet add package GitHubActionsTestLogger + dotnet add package coverlet.collector + dotnet build -c Debug -f net8.0 --property WarningLevel=0 /clp:ErrorsOnly + working-directory: ./certify/src/Certify.Tests/Certify.Core.Tests.Unit + + - name: Run Certify.Core.Tests.Unit Tests + run: | + export GITHUB_WORKSPACE="$GITHUB_WORKSPACE/certify" + export GITHUB_STEP_SUMMARY="./TestResults-8_0-${{ runner.os }}/test-summary.md" + dotnet test --no-build -f net8.0 -l "GitHubActions;summary.includePassedTests=true;summary.includeSkippedTests=true;annotations.messageFormat=@error\n@trace" + working-directory: ./certify/src/Certify.Tests/Certify.Core.Tests.Unit + + - name: Generate Test Results Report + run: | + echo "# Test Results" > $GITHUB_STEP_SUMMARY + sed -i 's/
/
/g' ./TestResults-8_0-${{ runner.os }}/test-summary.md + cat ./TestResults-8_0-${{ runner.os }}/test-summary.md >> $GITHUB_STEP_SUMMARY + working-directory: ./certify/src/Certify.Tests/Certify.Core.Tests.Unit + if: ${{ always() }} + + - name: Generated Test Coverage Report + run: | + reportgenerator -reports:./TestResults-8_0-${{ runner.os }}/*/coverage.cobertura.xml -targetdir:./TestResults-8_0-${{ runner.os }} -reporttypes:MarkdownSummaryGithub "-title:Test Coverage" + cat ./TestResults-8_0-${{ runner.os }}/SummaryGithub.md > $GITHUB_STEP_SUMMARY + working-directory: ./certify/src/Certify.Tests/Certify.Core.Tests.Unit + if: ${{ always() }} + + # - name: Upload dotnet test Artifacts + # uses: actions/upload-artifact@master + # with: + # name: dotnet-results-${{ runner.os }}-${{ env.DOTNET_VERSION }} + # path: ./certify/src/Certify.Tests/Certify.Core.Tests.Unit/TestResults-8_0-${{ runner.os }} + # # Use always() to always run this step to publish test results when there are test failures + # if: ${{ always() }} diff --git a/.github/workflows/8_0_Core_Unit_Tests_Win.yaml b/.github/workflows/8_0_Core_Unit_Tests_Win.yaml new file mode 100644 index 000000000..740d58ad1 --- /dev/null +++ b/.github/workflows/8_0_Core_Unit_Tests_Win.yaml @@ -0,0 +1,114 @@ +name: build and test .NET Core 8.0 Windows + +on: + push: + pull_request: + branches: [ release, development ] + paths: + - '**.cs' + - '**.csproj' + +env: + DOTNET_VERSION: '8.0.100' # The .NET SDK version to use + +jobs: + build-and-test: + + name: build-and-test-windows + runs-on: windows-latest + steps: + - name: Clone webprofusion/certify + uses: actions/checkout@master + with: + path: ./certify + + - name: Clone webprofusion/anvil + uses: actions/checkout@master + with: + repository: webprofusion/anvil + ref: refs/heads/main + path: ./libs/anvil + + - name: Clone webprofusion/certify-plugins (development branch push) + if: ${{ github.event_name == 'push' && (contains(github.ref_name, '_dev') || github.ref_name == 'development') }} + uses: actions/checkout@master + with: + repository: webprofusion/certify-plugins + ref: refs/heads/development + path: ./certify-plugins + + - name: Clone webprofusion/certify-plugins (release branch push) + if: ${{ github.event_name == 'push' && (contains(github.ref_name, '_rel') || github.ref_name == 'release') }} + uses: actions/checkout@master + with: + repository: webprofusion/certify-plugins + ref: refs/heads/release + path: ./certify-plugins + + - name: Clone webprofusion/certify-plugins (pull request) + if: ${{ github.event_name == 'pull_request' }} + uses: actions/checkout@master + with: + repository: webprofusion/certify-plugins + ref: ${{ github.base_ref }} + path: ./certify-plugins + + - name: Setup .NET Core + uses: actions/setup-dotnet@master + with: + dotnet-version: ${{ env.DOTNET_VERSION }} + + - name: Setup Step CLI + run: | + Invoke-WebRequest -Method 'GET' -uri 'https://dl.smallstep.com/gh-release/cli/docs-cli-install/v0.24.4/step_windows_0.24.4_amd64.zip' -Outfile 'C:\temp\step_windows_0.24.4_amd64.zip' + tar -oxzf C:\temp\step_windows_0.24.4_amd64.zip -C "C:\Program Files" + echo "C:\Program Files\step_0.24.4\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + + - name: Pull step-ca Docker Image + run: docker pull jrnelson90/step-ca-win + + - name: Cache NuGet Dependencies + uses: actions/cache@v3 + with: + path: ~/.nuget/packages + # Look to see if there is a cache hit for the corresponding requirements file + key: ${{ runner.os }}-${{ env.DOTNET_VERSION }}-nuget-${{ hashFiles('./certify/src/Certify.Tests/Certify.Core.Tests.Unit/*.csproj') }} + restore-keys: | + ${{ runner.os }}-${{ env.DOTNET_VERSION }}-nuget + + - name: Install Dependencies & Build Certify.Core.Tests.Unit + run: | + dotnet tool install --global dotnet-reportgenerator-globaltool --version 5.2.0 + dotnet add package GitHubActionsTestLogger + dotnet add package coverlet.collector + dotnet build -c Debug -f net8.0 --property WarningLevel=0 /clp:ErrorsOnly + working-directory: ./certify/src/Certify.Tests/Certify.Core.Tests.Unit + + - name: Run Certify.Core.Tests.Unit Tests + run: | + $env:GITHUB_WORKSPACE="$env:GITHUB_WORKSPACE\certify" + $env:GITHUB_STEP_SUMMARY=".\TestResults-8_0-${{ runner.os }}\test-summary.md" + dotnet test --no-build -f net8.0 -l "GitHubActions;summary.includePassedTests=true;summary.includeSkippedTests=true;annotations.messageFormat=@error\n@trace" + working-directory: ./certify/src/Certify.Tests/Certify.Core.Tests.Unit + + - name: Generate Test Results Report + run: | + echo "# Test Results" | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Encoding utf8 + (Get-Content -Path .\TestResults-8_0-${{ runner.os }}\test-summary.md).Replace('
', '
') | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Encoding utf8 -Append + working-directory: ./certify/src/Certify.Tests/Certify.Core.Tests.Unit + if: ${{ always() }} + + - name: Generated Test Coverage Report + run: | + reportgenerator -reports:./TestResults-8_0-${{ runner.os }}/*/coverage.cobertura.xml -targetdir:./TestResults-8_0-${{ runner.os }} -reporttypes:MarkdownSummaryGithub "-title:Test Coverage" + Get-Content -Path ./TestResults-8_0-${{ runner.os }}/SummaryGithub.md | Out-File -FilePath $env:GITHUB_STEP_SUMMARY + working-directory: ./certify/src/Certify.Tests/Certify.Core.Tests.Unit + if: ${{ always() }} + + # - name: Upload dotnet test Artifacts + # uses: actions/upload-artifact@master + # with: + # name: dotnet-results-${{ runner.os }}-${{ env.DOTNET_VERSION }} + # path: ./certify/src/Certify.Tests/Certify.Core.Tests.Unit/TestResults-8_0-${{ runner.os }} + # # Use always() to always run this step to publish test results when there are test failures + # if: ${{ always() }} diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.cs index 1fd40af2f..8917da2e2 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.cs @@ -533,7 +533,16 @@ public async Task PerformRenewalTasks() return await Task.FromResult(true); } - public void Dispose() => ManagedCertificateLog.DisposeLoggers(); + public void Dispose() => Cleanup(); + + private void Cleanup() + { + ManagedCertificateLog.DisposeLoggers(); + if(_tc != null) + { + _tc.Dispose(); + } + } /// /// Perform (or preview) an import of settings from another instance diff --git a/src/Certify.Tests/Certify.Core.Tests.Integration/CertRequestTests.cs b/src/Certify.Tests/Certify.Core.Tests.Integration/CertRequestTests.cs index 230c91ddc..b68c6a6b0 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Integration/CertRequestTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Integration/CertRequestTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; @@ -81,6 +81,7 @@ public async Task TeardownIIS() { await iisManager.DeleteSite(testSiteName); Assert.IsFalse(await iisManager.SiteExists(testSiteName)); + certifyManager.Dispose(); } [TestMethod, TestCategory("MegaTest")] diff --git a/src/Certify.Tests/Certify.Core.Tests.Integration/CertifyManagerServerTypeTests.cs b/src/Certify.Tests/Certify.Core.Tests.Integration/CertifyManagerServerTypeTests.cs index cfa981f6b..8fcc9840e 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Integration/CertifyManagerServerTypeTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Integration/CertifyManagerServerTypeTests.cs @@ -46,6 +46,7 @@ public async Task TeardownIIS() { await _iisManager.DeleteSite(_testSiteName); Assert.IsFalse(await _iisManager.SiteExists(_testSiteName)); + _certifyManager.Dispose(); } [TestMethod, Description("Happy path test for using CertifyManager.GetPrimaryWebSites() for IIS")] diff --git a/src/Certify.Tests/Certify.Core.Tests.Integration/CertifyManagerTests.cs b/src/Certify.Tests/Certify.Core.Tests.Integration/CertifyManagerTests.cs index 9f3f4c649..d5052a534 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Integration/CertifyManagerTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Integration/CertifyManagerTests.cs @@ -19,6 +19,11 @@ public CertifyManagerTests() _certifyManager.Init().Wait(); } + [TestCleanup] public void Cleanup() + { + _certifyManager.Dispose(); + } + [TestMethod, Description("Happy path test for using CertifyManager.GetACMEProvider()")] public async Task TestCertifyManagerGetACMEProvider() { diff --git a/src/Certify.Tests/Certify.Core.Tests.Integration/DeploymentPreviewTests.cs b/src/Certify.Tests/Certify.Core.Tests.Integration/DeploymentPreviewTests.cs index 038c76688..2970281ff 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Integration/DeploymentPreviewTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Integration/DeploymentPreviewTests.cs @@ -74,6 +74,7 @@ public async Task TeardownIIS() { await iisManager.DeleteSite(testSiteName); Assert.IsFalse(await iisManager.SiteExists(testSiteName)); + certifyManager.Dispose(); } [TestMethod] diff --git a/src/Certify.Tests/Certify.Core.Tests.Integration/DeploymentTaskTests.cs b/src/Certify.Tests/Certify.Core.Tests.Integration/DeploymentTaskTests.cs index 174315295..5be15834e 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Integration/DeploymentTaskTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Integration/DeploymentTaskTests.cs @@ -33,6 +33,12 @@ public DeploymentTaskTests() certifyManager.Init().Wait(); } + [TestCleanup] + public void Cleanup() + { + certifyManager?.Dispose(); + } + private DeploymentTaskConfig GetMockTaskConfig( string name, string msg = "Hello World", diff --git a/src/Certify.Tests/Certify.Core.Tests.Integration/RdapTests.cs b/src/Certify.Tests/Certify.Core.Tests.Integration/RdapTests.cs index 743806cf8..62292dba2 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Integration/RdapTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Integration/RdapTests.cs @@ -8,12 +8,6 @@ namespace Certify.Core.Tests [TestClass] public class RdapTests { - - public RdapTests() - { - - } - [TestMethod, Description("Test Rdap Query")] [DataTestMethod] [DataRow("example.com", "OK", null)] diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/CAFailoverTests.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/CAFailoverTests.cs index c163e6d27..d6bbbabfd 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/CAFailoverTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/CAFailoverTests.cs @@ -203,7 +203,7 @@ public void TestBasicNoFallbacks() var selectedAccount = RenewalManager.SelectCAWithFailover(caList, accounts.FindAll(a => a.IsStagingAccount == false), managedCertificate, defaultCAAccount); // assert result - Assert.IsTrue(selectedAccount.CertificateAuthorityId == DEFAULTCA, "Default CA should be selected"); + Assert.IsTrue(selectedAccount.CertificateAuthorityId == DEFAULTCA, $"Default CA should be selected: returned CA {selectedAccount.CertificateAuthorityId}"); Assert.IsFalse(selectedAccount.IsFailoverSelection, "Account should not be marked as a failover choice"); } @@ -231,7 +231,7 @@ public void TestBasicNextFallbackNull() var selectedAccount = RenewalManager.SelectCAWithFailover(caList, accounts, managedCertificate, defaultCAAccount); // assert result - Assert.IsTrue(selectedAccount.CertificateAuthorityId == DEFAULTCA, "Default CA should be selected"); + Assert.IsTrue(selectedAccount.CertificateAuthorityId == DEFAULTCA, $"Default CA should be selected: returned CA {selectedAccount.CertificateAuthorityId}"); Assert.IsFalse(selectedAccount.IsFailoverSelection, "Account should not be marked as a failover choice"); } diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj index 4cdfda0e1..4800cb76e 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj @@ -101,6 +101,15 @@ 1701;1702;NU1701 + + .\unit-test-462.runsettings + + + .\unit-test-8-0.runsettings + + + .\unit-test-8-0-linux.runsettings + @@ -140,6 +149,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/CertifyManagerAccountTests.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/CertifyManagerAccountTests.cs index 1aa31f23d..44ca2ef33 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/CertifyManagerAccountTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/CertifyManagerAccountTests.cs @@ -6,6 +6,7 @@ using System.Net.Http; using System.Runtime.InteropServices; using System.Text; +using System.Threading; using System.Threading.Tasks; using Certify.ACME.Anvil; using Certify.Management; @@ -21,35 +22,90 @@ namespace Certify.Core.Tests.Unit { [TestClass] - public class CertifyManagerAccountTests : IDisposable + public class CertifyManagerAccountTests { - private readonly CertifyManager _certifyManager; - private readonly bool _isContainer = Environment.GetEnvironmentVariable("DOTNET_RUNNING_IN_CONTAINER") == "true"; - private readonly bool _isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); - private readonly string _caDomain; - private readonly int _caPort; - private IContainer _caContainer; - private IVolume _stepVolume; + private static readonly bool _isContainer = Environment.GetEnvironmentVariable("DOTNET_RUNNING_IN_CONTAINER") == "true"; + private static readonly bool _isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + private static readonly string _winRunnerTempDir = "C:\\Temp\\.step"; + private static string _caDomain; + private static int _caPort; + private static IContainer _caContainer; + private static IVolume _stepVolume; + private static Loggy _log; + private CertifyManager _certifyManager; private CertificateAuthority _customCa; private AccountDetails _customCaAccount; - private readonly Loggy _log; - public CertifyManagerAccountTests() + [ClassInitialize] + public static async Task ClassInit(TestContext context) { - _certifyManager = new CertifyManager(); - _certifyManager.Init().Wait(); _log = new Loggy(new LoggerConfiguration().WriteTo.Debug().CreateLogger()); _caDomain = _isContainer ? "step-ca" : "localhost"; _caPort = 9000; - BootstrapStepCa().Wait(); - CheckCustomCaIsRunning().Wait(); - AddCustomCa().Wait(); - AddNewAccount().Wait(); + await BootstrapStepCa(); + await CheckCustomCaIsRunning(); } - private async Task BootstrapStepCa() + [TestInitialize] + public async Task TestInit() + { + _certifyManager = new CertifyManager(); + _certifyManager.Init().Wait(); + + await AddCustomCa(); + await AddNewCustomCaAccount(); + await CheckForExistingLeAccount(); + } + + [TestCleanup] + public async Task Cleanup() + { + if (_customCaAccount != null) + { + await _certifyManager.RemoveAccount(_customCaAccount.StorageKey, true); + } + + if (_customCa != null) + { + await _certifyManager.RemoveCertificateAuthority(_customCa.Id); + } + + _certifyManager?.Dispose(); + } + + [ClassCleanup(ClassCleanupBehavior.EndOfClass)] + public static async Task ClassCleanup() + { + if (!_isContainer) + { + await _caContainer.DisposeAsync(); + if (_stepVolume != null) + { + await _stepVolume.DeleteAsync(); + await _stepVolume.DisposeAsync(); + } + else + { + Directory.Delete(_winRunnerTempDir, true); + } + } + + var stepConfigPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".step", "config"); + if (Directory.Exists(stepConfigPath)) + { + Directory.Delete(stepConfigPath, true); + } + + var stepCertsPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".step", "certs"); + if (Directory.Exists(stepCertsPath)) + { + Directory.Delete(stepCertsPath, true); + } + } + + private static async Task BootstrapStepCa() { string stepCaFingerprint; @@ -68,45 +124,81 @@ private async Task BootstrapStepCa() } else { + var dockerInfo = RunCommand("docker", "info --format \"{{ .OSType }}\"", "Get Docker Info"); + var runningWindowsDockerEngine = dockerInfo.output.Contains("windows"); + // Start new step-ca container - await StartStepCaContainer(); + await StartStepCaContainer(runningWindowsDockerEngine); // Read step-ca fingerprint from config file - var stepCaConfigBytes = await _caContainer.ReadFileAsync("/home/step/config/defaults.json"); - var stepCaConfigJson = JsonReader.ReadBytes(stepCaConfigBytes); - stepCaFingerprint = stepCaConfigJson.fingerprint; + if (_isWindows && runningWindowsDockerEngine) + { + // Read step-ca fingerprint from config file + var stepCaConfigJson = JsonReader.ReadFile($"{_winRunnerTempDir}\\config\\defaults.json"); + stepCaFingerprint = stepCaConfigJson.fingerprint; + } else + { + var stepCaConfigBytes = await _caContainer.ReadFileAsync("/home/step/config/defaults.json"); + var stepCaConfigJson = JsonReader.ReadBytes(stepCaConfigBytes); + stepCaFingerprint = stepCaConfigJson.fingerprint; + } } // Run bootstrap command - //var command = $"ca bootstrap -f --ca-url https://{_caDomain}:{_caPort} --fingerprint {stepCaFingerprint} --install"; - var command = $"ca bootstrap -f --ca-url https://{_caDomain}:{_caPort} --fingerprint {stepCaFingerprint}"; - RunCommand("step", command, "Bootstrap Step CA Script", 1000 * 30); + var args = $"ca bootstrap -f --ca-url https://{_caDomain}:{_caPort} --fingerprint {stepCaFingerprint}"; + RunCommand("step", args, "Bootstrap Step CA Script", 1000 * 30); } - private async Task StartStepCaContainer() + private static async Task StartStepCaContainer(bool runningWindowsDockerEngine) { try { - // Create new volume for step-ca container - _stepVolume = new VolumeBuilder().WithName("step").Build(); - await _stepVolume.CreateAsync(); - - // Create new step-ca container - _caContainer = new ContainerBuilder() - .WithName("step-ca") - // Set the image for the container to "smallstep/step-ca:latest". - .WithImage("smallstep/step-ca:latest") - .WithVolumeMount(_stepVolume, "/home/step") - // Bind port 9000 of the container to port 9000 on the host. - .WithPortBinding(_caPort) - .WithEnvironment("DOCKER_STEPCA_INIT_NAME", "Smallstep") - .WithEnvironment("DOCKER_STEPCA_INIT_DNS_NAMES", _caDomain) - .WithEnvironment("DOCKER_STEPCA_INIT_REMOTE_MANAGEMENT", "true") - .WithEnvironment("DOCKER_STEPCA_INIT_ACME", "true") - // Wait until the HTTPS endpoint of the container is available. - .WithWaitStrategy(Wait.ForUnixContainer().UntilMessageIsLogged($"Serving HTTPS on :{_caPort} ...")) - // Build the container configuration. - .Build(); + if (_isWindows && runningWindowsDockerEngine) + { + if (!Directory.Exists(_winRunnerTempDir)) { + Directory.CreateDirectory(_winRunnerTempDir); + } + + // Create new step-ca container + _caContainer = new ContainerBuilder() + .WithName("step-ca") + // Set the image for the container to "jrnelson90/step-ca-win:latest". + .WithImage("jrnelson90/step-ca-win:latest") + .WithBindMount(_winRunnerTempDir, "C:\\Users\\ContainerUser\\.step") + // Bind port 9000 of the container to port 9000 on the host. + .WithPortBinding(_caPort) + .WithEnvironment("DOCKER_STEPCA_INIT_NAME", "Smallstep") + .WithEnvironment("DOCKER_STEPCA_INIT_DNS_NAMES", _caDomain) + .WithEnvironment("DOCKER_STEPCA_INIT_REMOTE_MANAGEMENT", "true") + .WithEnvironment("DOCKER_STEPCA_INIT_ACME", "true") + // Wait until the HTTPS endpoint of the container is available. + .WithWaitStrategy(Wait.ForUnixContainer().UntilMessageIsLogged($"Serving HTTPS on :{_caPort} ...")) + // Build the container configuration. + .Build(); + } + else + { + // Create new volume for step-ca container + _stepVolume = new VolumeBuilder().WithName("step").Build(); + await _stepVolume.CreateAsync(); + + // Create new step-ca container + _caContainer = new ContainerBuilder() + .WithName("step-ca") + // Set the image for the container to "smallstep/step-ca:latest". + .WithImage("smallstep/step-ca:latest") + .WithVolumeMount(_stepVolume, "/home/step") + // Bind port 9000 of the container to port 9000 on the host. + .WithPortBinding(_caPort) + .WithEnvironment("DOCKER_STEPCA_INIT_NAME", "Smallstep") + .WithEnvironment("DOCKER_STEPCA_INIT_DNS_NAMES", _caDomain) + .WithEnvironment("DOCKER_STEPCA_INIT_REMOTE_MANAGEMENT", "true") + .WithEnvironment("DOCKER_STEPCA_INIT_ACME", "true") + // Wait until the HTTPS endpoint of the container is available. + .WithWaitStrategy(Wait.ForUnixContainer().UntilMessageIsLogged($"Serving HTTPS on :{_caPort} ...")) + // Build the container configuration. + .Build(); + } // Start step-ca container await _caContainer.StartAsync(); @@ -154,7 +246,7 @@ private class StepCaConfig public string root; } - private void RunCommand(string program, string args, string description = null, int timeoutMS = 1000 * 5) + private static CommandOutput RunCommand(string program, string args, string description = null, int timeoutMS = Timeout.Infinite) { if (description == null) { description = string.Concat(program, " ", args); } @@ -165,7 +257,6 @@ private void RunCommand(string program, string args, string description = null, { FileName = program, Arguments = args, - RedirectStandardInput = true, RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, @@ -199,7 +290,7 @@ private void RunCommand(string program, string args, string description = null, process.BeginOutputReadLine(); process.BeginErrorReadLine(); - process.WaitForExit(timeoutMS); + process.WaitForExit(timeoutMS); } catch (Exception exp) { @@ -207,10 +298,19 @@ private void RunCommand(string program, string args, string description = null, throw; } - _log.Information($"{description} Successful"); + _log.Information($"{description} is Finished"); + + return new CommandOutput { errorOutput = errorOutput, output = output, exitCode = process.ExitCode }; + } + + private struct CommandOutput + { + public string errorOutput { get; set; } + public string output { get; set; } + public int exitCode { get; set; } } - private async Task CheckCustomCaIsRunning() + private static async Task CheckCustomCaIsRunning() { var httpHandler = new HttpClientHandler(); @@ -244,7 +344,8 @@ private async Task AddCustomCa() CertAuthoritySupportedRequests.DOMAIN_MULTIPLE_SAN.ToString(), CertAuthoritySupportedRequests.DOMAIN_WILDCARD.ToString() }, - SupportedKeyTypes = new List{ + SupportedKeyTypes = new List + { StandardKeyTypes.ECDSA256, } }; @@ -252,7 +353,7 @@ private async Task AddCustomCa() Assert.IsTrue(updateCaRes.IsSuccess, $"Expected Custom CA creation for CA with ID {_customCa.Id} to be successful"); } - private async Task AddNewAccount() + private async Task AddNewCustomCaAccount() { if (_customCa?.Id != null) { @@ -273,40 +374,24 @@ private async Task AddNewAccount() } } - public void Dispose() => Cleanup().Wait(); - - private async Task Cleanup() + private async Task CheckForExistingLeAccount() { - if (_customCaAccount != null) - { - await _certifyManager.RemoveAccount(_customCaAccount.StorageKey, true); - } - - if (_customCa != null) - { - await _certifyManager.RemoveCertificateAuthority(_customCa.Id); - } - - if (!_isContainer) - { - await _caContainer.DisposeAsync(); - await _stepVolume.DeleteAsync(); - await _stepVolume.DisposeAsync(); - } - - var stepConfigPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".step", "config"); - if (Directory.Exists(stepConfigPath)) + if ((await _certifyManager.GetAccountRegistrations()).Find(a => a.CertificateAuthorityId == "letsencrypt.org") == null) { - Directory.Delete(stepConfigPath, true); - } + var contactRegistration = new ContactRegistration + { + AgreedToTermsAndConditions = true, + CertificateAuthorityId = "letsencrypt.org", + EmailAddress = "admin." + Guid.NewGuid().ToString().Substring(0, 6) + "@test.com", + ImportedAccountKey = "", + ImportedAccountURI = "", + IsStaging = true + }; - var stepCertsPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".step", "certs"); - if (Directory.Exists(stepCertsPath)) - { - Directory.Delete(stepCertsPath, true); + // Add account + var addAccountRes = await _certifyManager.AddAccount(contactRegistration); + Assert.IsTrue(addAccountRes.IsSuccess, $"Expected account creation to be successful for {contactRegistration.EmailAddress}"); } - - _certifyManager?.Dispose(); } [TestMethod, Description("Happy path test for using CertifyManager.GetAccountDetails()")] diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/CertifyServiceTests.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/CertifyServiceTests.cs new file mode 100644 index 000000000..92a342980 --- /dev/null +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/CertifyServiceTests.cs @@ -0,0 +1,430 @@ +using Certify.Models; +using Certify.Models.Config; +using Certify.Shared; +using Medallion.Shell; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Http; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace Certify.Core.Tests.Unit +{ +#if NET462 + [TestClass] + public class CertifyServiceTests + { + private HttpClient _httpClient; + private string serviceUri; + + public CertifyServiceTests() { + var serviceConfig = SharedUtils.ServiceConfigManager.GetAppServiceConfig(); + serviceUri = $"{(serviceConfig.UseHTTPS ? "https" : "http")}://{serviceConfig.Host}:{serviceConfig.Port}"; + var httpHandler = new HttpClientHandler { UseDefaultCredentials = true }; + _httpClient = new HttpClient(httpHandler); + _httpClient.DefaultRequestHeaders.Add("User-Agent", "Certify/App"); + _httpClient.BaseAddress = new Uri(serviceUri+"/api/"); + } + + private async Task StartCertifyService(string args = "") + { + Command certifyService; + if (args == "") + { + certifyService = Command.Run(".\\Certify.Service.exe"); + await Task.Delay(2000); + } + else + { + certifyService = Command.Run(".\\Certify.Service.exe", args); + } + + return certifyService; + } + + private async Task StopCertifyService(Command certifyService) + { + await certifyService.TrySignalAsync(CommandSignal.ControlC); + + var cmdResult = await certifyService.Task; + + Assert.AreEqual(cmdResult.ExitCode, 0, "Unexpected exit code"); + + return cmdResult; + } + + [TestMethod, Description("Validate that Certify.Service.exe does not start with args from CLI")] + public async Task TestProgramMainFailsWithArgsCli() + { + var certifyService = await StartCertifyService("args"); + + var cmdResult = await certifyService.Task; + + Assert.IsTrue(cmdResult.StandardOutput.Contains("Topshelf.HostFactory Error: 0 : An exception occurred creating the host, Topshelf.HostConfigurationException: The service was not properly configured:")); + Assert.IsTrue(cmdResult.StandardOutput.Contains("Topshelf.HostFactory Error: 0 : The service terminated abnormally, Topshelf.HostConfigurationException: The service was not properly configured:")); + + Assert.AreEqual(cmdResult.ExitCode, 1067, "Unexpected exit code"); + } + + [TestMethod, Description("Validate that Certify.Service.exe starts from CLI with no args")] + public async Task TestProgramMainStartsCli() + { + var certifyService = await StartCertifyService(); + + var cmdResult = await StopCertifyService(certifyService); + + Assert.IsTrue(cmdResult.StandardOutput.Contains("[Success] Name Certify.Service")); + Assert.IsTrue(cmdResult.StandardOutput.Contains("[Success] DisplayName Certify Certificate Manager Service (Instance: Debug)")); + Assert.IsTrue(cmdResult.StandardOutput.Contains("[Success] Description Certify Certificate Manager Service")); + Assert.IsTrue(cmdResult.StandardOutput.Contains("[Success] InstanceName Debug")); + Assert.IsTrue(cmdResult.StandardOutput.Contains("[Success] ServiceName Certify.Service$Debug")); + Assert.IsTrue(cmdResult.StandardOutput.Contains("The Certify.Service$Debug service is now running, press Control+C to exit.")); + Assert.IsTrue(cmdResult.StandardOutput.Contains("Control+C detected, attempting to stop service.")); + Assert.IsTrue(cmdResult.StandardOutput.Contains("The Certify.Service$Debug service has stopped.")); + } + + [TestMethod, Description("Validate that Certify.Service.exe returns a valid response on route GET /api/system/appversion")] + public async Task TestCertifyServiceAppVersionRoute() + { + var certifyService = await StartCertifyService(); + + try + { + var versionRawRes = await _httpClient.GetAsync("system/appversion"); + var versionResStr = await versionRawRes.Content.ReadAsStringAsync(); + var versionRes = JsonConvert.DeserializeObject(versionResStr); + + Assert.AreEqual(HttpStatusCode.OK, versionRawRes.StatusCode, $"Unexpected status code from GET {versionRawRes.RequestMessage.RequestUri.AbsoluteUri}"); + StringAssert.Matches(versionRes, new Regex(@"^(\d+\.)?(\d+\.)?(\d+\.)?(\*|\d+)$"), $"Unexpected response from GET {versionRawRes.RequestMessage.RequestUri.AbsoluteUri} : {versionResStr}"); + } + finally + { + await StopCertifyService(certifyService); + } + } + + [TestMethod, Description("Validate that Certify.Service.exe returns a valid respose on route GET /api/system/updatecheck")] + public async Task TestCertifyServiceUpdateCheckRoute() + { + var certifyService = await StartCertifyService(); + + try + { + var updatesRawRes = await _httpClient.GetAsync("system/updatecheck"); + var updateRawResStr = await updatesRawRes.Content.ReadAsStringAsync(); + var updateRes = JsonConvert.DeserializeObject(updateRawResStr); + + Assert.AreEqual(HttpStatusCode.OK, updatesRawRes.StatusCode, $"Unexpected status code from GET {updatesRawRes.RequestMessage.RequestUri.AbsoluteUri}"); + Assert.IsFalse(updateRes.MustUpdate); + Assert.IsFalse(updateRes.IsNewerVersion); + Assert.AreEqual(updateRes.InstalledVersion.ToString(), updateRes.Version.ToString()); + Assert.AreEqual("", updateRes.UpdateFilePath); + } + finally + { + await StopCertifyService(certifyService); + } + } + + [TestMethod, Description("Validate that Certify.Service.exe returns a valid response on route GET /api/system/diagnostics")] + public async Task TestCertifyServiceDiagnosticsRoute() + { + var certifyService = await StartCertifyService(); + + try + { + var diagnosticsRawRes = await _httpClient.GetAsync("system/diagnostics"); + var diagnosticsRawResStr = await diagnosticsRawRes.Content.ReadAsStringAsync(); + var diagnosticsRes = JsonConvert.DeserializeObject>(diagnosticsRawResStr); + + Assert.AreEqual(HttpStatusCode.OK, diagnosticsRawRes.StatusCode, $"Unexpected status code from GET {diagnosticsRawRes.RequestMessage.RequestUri.AbsoluteUri}"); + Assert.AreEqual(4, diagnosticsRes.Count); + + Assert.AreEqual("Created test temp file OK.", diagnosticsRes[0].Message); + Assert.IsTrue(diagnosticsRes[0].IsSuccess); + Assert.IsFalse(diagnosticsRes[0].IsWarning); + Assert.AreEqual(null, diagnosticsRes[0].Result); + + Assert.AreEqual($"Drive {Environment.GetEnvironmentVariable("SystemDrive")} has more than 512MB of disk space free.", diagnosticsRes[1].Message); + Assert.IsTrue(diagnosticsRes[1].IsSuccess); + Assert.IsFalse(diagnosticsRes[1].IsWarning); + Assert.AreEqual(null, diagnosticsRes[1].Result); + + Assert.AreEqual("System time is correct.", diagnosticsRes[2].Message); + Assert.IsTrue(diagnosticsRes[2].IsSuccess); + Assert.IsFalse(diagnosticsRes[2].IsWarning); + Assert.AreEqual(null, diagnosticsRes[2].Result); + + Assert.AreEqual("PowerShell 5.0 or higher is available.", diagnosticsRes[3].Message); + Assert.IsTrue(diagnosticsRes[3].IsSuccess); + Assert.IsFalse(diagnosticsRes[3].IsWarning); + Assert.AreEqual(null, diagnosticsRes[3].Result); + } + finally + { + await StopCertifyService(certifyService); + } + } + + [TestMethod, Description("Validate that Certify.Service.exe returns a valid response on route GET /api/system/datastores/providers")] + public async Task TestCertifyServiceDatastoreProvidersRoute() + { + var certifyService = await StartCertifyService(); + + try + { + var datastoreProvidersRawRes = await _httpClient.GetAsync("system/datastores/providers"); + var datastoreProvidersRawResStr = await datastoreProvidersRawRes.Content.ReadAsStringAsync(); + var datastoreProvidersRes = JsonConvert.DeserializeObject>(datastoreProvidersRawResStr); + + Assert.AreEqual(HttpStatusCode.OK, datastoreProvidersRawRes.StatusCode, $"Unexpected status code from GET {datastoreProvidersRawRes.RequestMessage.RequestUri.AbsoluteUri}"); + } + finally + { + await StopCertifyService(certifyService); + } + } + + [TestMethod, Description("Validate that Certify.Service.exe returns a valid response on route GET /api/system/datastores/")] + public async Task TestCertifyServiceDatastoresRoute() + { + var certifyService = await StartCertifyService(); + + try + { + var datastoreRawRes = await _httpClient.GetAsync("system/datastores/"); + var datastoreRawResStr = await datastoreRawRes.Content.ReadAsStringAsync(); + var datastoreRes = JsonConvert.DeserializeObject>(datastoreRawResStr); + + Assert.AreEqual(HttpStatusCode.OK, datastoreRawRes.StatusCode, $"Unexpected status code from GET {datastoreRawRes.RequestMessage.RequestUri.AbsoluteUri}"); + Assert.IsTrue(datastoreRes.Count >= 1); + } + finally + { + await StopCertifyService(certifyService); + } + } + + [TestMethod, Description("Validate that Certify.Service.exe returns a valid response on route POST /api/system/datastores/test")] + public async Task TestCertifyServiceDatastoresTestRoute() + { + var certifyService = await StartCertifyService(); + + try + { + var datastoreRawRes = await _httpClient.GetAsync("system/datastores/"); + var datastoreRawResStr = await datastoreRawRes.Content.ReadAsStringAsync(); + var datastoreRes = JsonConvert.DeserializeObject>(datastoreRawResStr); + + Assert.AreEqual(HttpStatusCode.OK, datastoreRawRes.StatusCode, $"Unexpected status code from GET {datastoreRawRes.RequestMessage.RequestUri.AbsoluteUri}"); + Assert.IsTrue(datastoreRes.Count >= 1); + + var datastoreTestRawRes = await _httpClient.PostAsJsonAsync("system/datastores/test", datastoreRes[0]); + var datastoreTestRawResStr = await datastoreTestRawRes.Content.ReadAsStringAsync(); + var datastoreTestRes = JsonConvert.DeserializeObject>(datastoreTestRawResStr); + + Assert.AreEqual(HttpStatusCode.OK, datastoreTestRawRes.StatusCode, $"Unexpected status code from POST {datastoreTestRawRes.RequestMessage.RequestUri.AbsoluteUri}"); + Assert.IsTrue(datastoreTestRes.Count >= 1); + } + finally + { + await StopCertifyService(certifyService); + } + } + + [TestMethod, Description("Validate that Certify.Service.exe returns a valid response on route POST /api/system/datastores/update")] + public async Task TestCertifyServiceDatastoresUpdateRoute() + { + var certifyService = await StartCertifyService(); + + try + { + var datastoreRawRes = await _httpClient.GetAsync("system/datastores/"); + var datastoreRawResStr = await datastoreRawRes.Content.ReadAsStringAsync(); + var datastoreRes = JsonConvert.DeserializeObject>(datastoreRawResStr); + + Assert.AreEqual(HttpStatusCode.OK, datastoreRawRes.StatusCode, $"Unexpected status code from GET {datastoreRawRes.RequestMessage.RequestUri.AbsoluteUri}"); + Assert.IsTrue(datastoreRes.Count >= 1); + + var datastoreUpdateRawRes = await _httpClient.PostAsJsonAsync("system/datastores/update", datastoreRes[0]); + var datastoreUpdateRawResStr = await datastoreUpdateRawRes.Content.ReadAsStringAsync(); + var datastoreUpdateRes = JsonConvert.DeserializeObject>(datastoreUpdateRawResStr); + + Assert.AreEqual(HttpStatusCode.OK, datastoreUpdateRawRes.StatusCode, $"Unexpected status code from POST {datastoreUpdateRawRes.RequestMessage.RequestUri.AbsoluteUri}"); + Assert.IsTrue(datastoreUpdateRes.Count >= 1); + } + finally + { + await StopCertifyService(certifyService); + } + } + + [TestMethod, Description("Validate that Certify.Service.exe returns a valid response on route POST /api/system/datastores/setdefault/{dataStoreId}")] + public async Task TestCertifyServiceDatastoresSetDefaultRoute() + { + var certifyService = await StartCertifyService(); + + try + { + var datastoreRawRes = await _httpClient.GetAsync("system/datastores/"); + var datastoreRawResStr = await datastoreRawRes.Content.ReadAsStringAsync(); + var datastoreRes = JsonConvert.DeserializeObject>(datastoreRawResStr); + + Assert.AreEqual(HttpStatusCode.OK, datastoreRawRes.StatusCode, $"Unexpected status code from GET {datastoreRawRes.RequestMessage.RequestUri.AbsoluteUri}"); + Assert.IsTrue(datastoreRes.Count >= 1); + + var datastoreSetDefaultRawRes = await _httpClient.PostAsync($"system/datastores/setdefault/{datastoreRes[0].Id}", new StringContent("")); + var datastoreSetDefaultRawResStr = await datastoreSetDefaultRawRes.Content.ReadAsStringAsync(); + var datastoreSetDefaultRes = JsonConvert.DeserializeObject>(datastoreSetDefaultRawResStr); + + Assert.AreEqual(HttpStatusCode.OK, datastoreSetDefaultRawRes.StatusCode, $"Unexpected status code from POST {datastoreSetDefaultRawRes.RequestMessage.RequestUri.AbsoluteUri}"); + Assert.IsTrue(datastoreSetDefaultRes.Count >= 1); + } + finally + { + await StopCertifyService(certifyService); + } + } + + [TestMethod, Description("Validate that Certify.Service.exe returns a valid response on route POST /api/system/datastores/delete")] + [Ignore] + public async Task TestCertifyServiceDatastoresDeleteRoute() + { + var certifyService = await StartCertifyService(); + + try + { + var datastoreRawRes = await _httpClient.GetAsync("system/datastores/"); + var datastoreRawResStr = await datastoreRawRes.Content.ReadAsStringAsync(); + var datastoreRes = JsonConvert.DeserializeObject>(datastoreRawResStr); + + Assert.AreEqual(HttpStatusCode.OK, datastoreRawRes.StatusCode, $"Unexpected status code from GET {datastoreRawRes.RequestMessage.RequestUri.AbsoluteUri}"); + Assert.IsTrue(datastoreRes.Count >= 1); + + var datastoreDeleteRawRes = await _httpClient.PostAsync("system/datastores/delete", new StringContent(datastoreRes[0].Id)); + var datastoreDeleteRawResStr = await datastoreDeleteRawRes.Content.ReadAsStringAsync(); + var datastoreDeleteRes = JsonConvert.DeserializeObject>(datastoreDeleteRawResStr); + + Assert.AreEqual(HttpStatusCode.OK, datastoreDeleteRawRes.StatusCode, $"Unexpected status code from POST {datastoreDeleteRawRes.RequestMessage.RequestUri.AbsoluteUri}"); + Assert.IsTrue(datastoreDeleteRes.Count >= 1); + + var datastoreUpdateRawRes = await _httpClient.PostAsJsonAsync("system/datastores/update", datastoreRes[0]); + var datastoreUpdateRawResStr = await datastoreUpdateRawRes.Content.ReadAsStringAsync(); + var datastoreUpdateRes = JsonConvert.DeserializeObject>(datastoreUpdateRawResStr); + + Assert.AreEqual(HttpStatusCode.OK, datastoreUpdateRawRes.StatusCode, $"Unexpected status code from POST {datastoreUpdateRawRes.RequestMessage.RequestUri.AbsoluteUri}"); + Assert.IsTrue(datastoreUpdateRes.Count >= 1); + } + finally + { + await StopCertifyService(certifyService); + } + } + + [TestMethod, Description("Validate that Certify.Service.exe returns a valid response on route POST /api/system/datastores/copy/{sourceId}/{destId}")] + [Ignore] + public async Task TestCertifyServiceDatastoresCopyRoute() + { + var certifyService = await StartCertifyService(); + + try + { + var datastoreRawRes = await _httpClient.GetAsync("system/datastores/"); + var datastoreRawResStr = await datastoreRawRes.Content.ReadAsStringAsync(); + var datastoreRes = JsonConvert.DeserializeObject>(datastoreRawResStr); + + Assert.AreEqual(HttpStatusCode.OK, datastoreRawRes.StatusCode, $"Unexpected status code from GET {datastoreRawRes.RequestMessage.RequestUri.AbsoluteUri}"); + Assert.IsTrue(datastoreRes.Count >= 1); + + var newDataStoreId = "default-copy"; + var datastoreCopyRawRes = await _httpClient.PostAsync($"system/datastores/copy/{datastoreRes[0].Id}/{newDataStoreId}", new StringContent("")); + var datastoreCopyRawResStr = await datastoreCopyRawRes.Content.ReadAsStringAsync(); + var datastoreCopyRes = JsonConvert.DeserializeObject>(datastoreCopyRawResStr); + + Assert.AreEqual(HttpStatusCode.OK, datastoreCopyRawRes.StatusCode, $"Unexpected status code from POST {datastoreCopyRawRes.RequestMessage.RequestUri.AbsoluteUri}"); + Assert.IsTrue(datastoreCopyRes.Count >= 1); + + datastoreRawRes = await _httpClient.GetAsync("system/datastores/"); + datastoreRawResStr = await datastoreRawRes.Content.ReadAsStringAsync(); + datastoreRes = JsonConvert.DeserializeObject>(datastoreRawResStr); + + Assert.AreEqual(HttpStatusCode.OK, datastoreRawRes.StatusCode, $"Unexpected status code from GET {datastoreRawRes.RequestMessage.RequestUri.AbsoluteUri}"); + Assert.IsTrue(datastoreRes.Count >= 2); + + var datastoreDeleteRawRes = await _httpClient.PostAsJsonAsync("system/datastores/delete", newDataStoreId); + var datastoreDeleteRawResStr = await datastoreDeleteRawRes.Content.ReadAsStringAsync(); + var datastoreDeleteRes = JsonConvert.DeserializeObject>(datastoreDeleteRawResStr); + + Assert.AreEqual(HttpStatusCode.OK, datastoreDeleteRawRes.StatusCode, $"Unexpected status code from POST {datastoreDeleteRawRes.RequestMessage.RequestUri.AbsoluteUri}"); + Assert.IsTrue(datastoreDeleteRes.Count >= 1); + } + finally + { + await StopCertifyService(certifyService); + } + } + + [TestMethod, Description("Validate that Certify.Service.exe returns a valid response on route GET /api/server/isavailable/{serverType}")] + public async Task TestCertifyServiceServerIsavailableRoute() + { + var certifyService = await StartCertifyService(); + + try + { + var isAvailableRawRes = await _httpClient.GetAsync($"server/isavailable/{StandardServerTypes.IIS}"); + var isAvailableRawResStr = await isAvailableRawRes.Content.ReadAsStringAsync(); + var isAvailableRes = JsonConvert.DeserializeObject(isAvailableRawResStr); + + Assert.AreEqual(HttpStatusCode.OK, isAvailableRawRes.StatusCode, $"Unexpected status code from GET {isAvailableRawRes.RequestMessage.RequestUri.AbsoluteUri}"); + Assert.IsTrue(isAvailableRes); + } + finally + { + await StopCertifyService(certifyService); + } + } + + [TestMethod, Description("Validate that Certify.Service.exe returns a valid response on route GET /api/server/sitelist/{serverType}")] + public async Task TestCertifyServiceServerSitelistRoute() + { + var certifyService = await StartCertifyService(); + + try + { + var sitelistRawRes = await _httpClient.GetAsync($"server/sitelist/{StandardServerTypes.IIS}"); + var sitelistRawResStr = await sitelistRawRes.Content.ReadAsStringAsync(); + var sitelistRes = JsonConvert.DeserializeObject>(sitelistRawResStr); + + Assert.AreEqual(HttpStatusCode.OK, sitelistRawRes.StatusCode, $"Unexpected status code from GET {sitelistRawRes.RequestMessage.RequestUri.AbsoluteUri}"); + } + finally + { + await StopCertifyService(certifyService); + } + } + + [TestMethod, Description("Validate that Certify.Service.exe returns a valid response on route GET /api/server/version/{serverType}")] + public async Task TestCertifyServiceServerVersionRoute() + { + var certifyService = await StartCertifyService(); + + try + { + var versionRawRes = await _httpClient.GetAsync($"server/version/{StandardServerTypes.IIS}"); + var versionRawResStr = await versionRawRes.Content.ReadAsStringAsync(); + var versionRes = JsonConvert.DeserializeObject(versionRawResStr); + + Assert.AreEqual(HttpStatusCode.OK, versionRawRes.StatusCode, $"Unexpected status code from GET {versionRawRes.RequestMessage.RequestUri.AbsoluteUri}"); + StringAssert.Matches(versionRes, new Regex(@"^(\d+\.)?(\*|\d+)$"), $"Unexpected response from GET {versionRawRes.RequestMessage.RequestUri.AbsoluteUri} : {versionRawResStr}"); + } + finally + { + await StopCertifyService(certifyService); + } + } + } +#endif +} diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/ChallengeConfigMatchTests.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/ChallengeConfigMatchTests.cs index 2053786f1..7ef2d122a 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/ChallengeConfigMatchTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/ChallengeConfigMatchTests.cs @@ -144,8 +144,6 @@ public void ChallengeDelegationRuleTests() [TestMethod, Description("Ensure correct challenge config selected when rule is blank")] public void ChallengeDelegationRuleBlankRule() { - // wildcard rule tests [any subdomain source, any subdomain target] - var testRule = "*.test.com:*.auth.test.co.uk"; var result = Management.Challenges.DnsChallengeHelper.ApplyChallengeDelegationRule("test.com", "_acme-challenge.test.com", null); Assert.AreEqual("_acme-challenge.test.com", result); } diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/MiscTests.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/MiscTests.cs index c382954f3..abdee2668 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/MiscTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/MiscTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Threading.Tasks; using Certify.Models.API; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -8,12 +8,6 @@ namespace Certify.Core.Tests.Unit [TestClass] public class MiscTests { - - public MiscTests() - { - - } - [TestMethod, Description("Test null/blank coalesce of string")] public void TestNullOrBlankCoalesce() { diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/RdapTests.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/RdapTests.cs index 6e4d50931..cb6c25d63 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/RdapTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/RdapTests.cs @@ -7,12 +7,6 @@ namespace Certify.Core.Tests.Unit [TestClass] public class RdapTests { - - public RdapTests() - { - - } - [TestMethod, Description("Test domain TLD check")] [DataTestMethod] [DataRow("example.com", "com")] diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/step-ca-win-init.bat b/src/Certify.Tests/Certify.Core.Tests.Unit/step-ca-win-init.bat index c2a262060..56fde2918 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/step-ca-win-init.bat +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/step-ca-win-init.bat @@ -1,23 +1,63 @@ -FOR /F "tokens=* USEBACKQ" %%F IN (`step path`) DO ( +FOR /F "tokens=* USEBACKQ" %%F IN (`step path`) DO ( SET STEPPATH=%%F ) +IF EXIST %STEPPATH%\config\ca.json ( + step-ca --password-file %STEPPATH%\secrets\password %STEPPATH%\config\ca.json + EXIT 0 +) + +IF "%DOCKER_STEPCA_INIT_NAME%"=="" ( + echo "there is no ca.json config file; please run step ca init, or provide config parameters via DOCKER_STEPCA_INIT_ vars" + EXIT 1 +) + +IF "%DOCKER_STEPCA_INIT_DNS_NAMES%"=="" ( + echo "there is no ca.json config file; please run step ca init, or provide config parameters via DOCKER_STEPCA_INIT_ vars" + EXIT 1 +) + +IF "%DOCKER_STEPCA_INIT_PROVISIONER_NAME%"=="" SET DOCKER_STEPCA_INIT_PROVISIONER_NAME=admin +IF "%DOCKER_STEPCA_INIT_ADMIN_SUBJECT%"=="" SET DOCKER_STEPCA_INIT_ADMIN_SUBJECT=step +IF "%DOCKER_STEPCA_INIT_ADDRESS%"=="" SET DOCKER_STEPCA_INIT_ADDRESS=:9000 + +IF NOT "%DOCKER_STEPCA_INIT_PASSWORD%"=="" ( +pwsh -Command "Out-File -FilePath "$Env:STEPPATH\password" -InputObject "$Env:DOCKER_STEPCA_INIT_PASSWORD";"^ + "Out-File -FilePath "$Env:STEPPATH\provisioner_password" -InputObject "$Env:DOCKER_STEPCA_INIT_PASSWORD";" +) ELSE IF NOT "%DOCKER_STEPCA_INIT_PASSWORD_FILE%"=="" ( +pwsh -Command "Out-File -FilePath "$Env:STEPPATH\password" -InputObject "$Env:DOCKER_STEPCA_INIT_PASSWORD_FILE";"^ + "Out-File -FilePath "$Env:STEPPATH\provisioner_password" -InputObject "$Env:DOCKER_STEPCA_INIT_PASSWORD_FILE";" +) ELSE ( pwsh -Command "$psw = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'.tochararray() | Get-Random -Count 40 | Join-String;"^ - "echo $psw;"^ "Out-File -FilePath "$Env:STEPPATH\password" -InputObject $psw;"^ "Out-File -FilePath "$Env:STEPPATH\provisioner_password" -InputObject $psw;"^ "Remove-Variable psw" +) + +setlocal + +SET INIT_ARGS=--deployment-type standalone --name %DOCKER_STEPCA_INIT_NAME% --dns %DOCKER_STEPCA_INIT_DNS_NAMES% --provisioner %DOCKER_STEPCA_INIT_PROVISIONER_NAME% --password-file %STEPPATH%\password --provisioner-password-file %STEPPATH%\provisioner_password --address %DOCKER_STEPCA_INIT_ADDRESS% + +IF "%DOCKER_STEPCA_INIT_SSH%"=="true" SET INIT_ARGS=%INIT_ARGS% -ssh +IF "%DOCKER_STEPCA_INIT_REMOTE_MANAGEMENT%"=="true" SET INIT_ARGS=%INIT_ARGS% --remote-management --admin-subject %DOCKER_STEPCA_INIT_ADMIN_SUBJECT% + +step ca init %INIT_ARGS% +SET /p psw=<%STEPPATH%\provisioner_password +echo "👉 Your CA administrative password is: %psw%" +echo "🤫 This will only be displayed once." + +endlocal -step ca init --deployment-type standalone --name Smallstep --dns localhost --provisioner admin ^ ---password-file %STEPPATH%\password --provisioner-password-file %STEPPATH%\provisioner_password ^ ---address :9000 --remote-management --admin-subject step +SET HEALTH_URL=https://%DOCKER_STEPCA_INIT_DNS_NAMES%%DOCKER_STEPCA_INIT_ADDRESS%/health sdelete64 -accepteula -nobanner -q %STEPPATH%\provisioner_password move "%STEPPATH%\password" "%STEPPATH%\secrets\password" +:: Current error with running this program in Windows Docker Container causes issue reading DB first time, so they must be deleted to be recreated rmdir /s /q %STEPPATH%\db -step ca provisioner add acme --type ACME +:: Current error with running this program in Windows Docker Container causes ACME not to be set with --acme +IF "%DOCKER_STEPCA_INIT_ACME%"=="true" step ca provisioner add acme --type ACME step-ca --password-file %STEPPATH%\secrets\password %STEPPATH%\config\ca.json diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/step-ca-win.dockerfile b/src/Certify.Tests/Certify.Core.Tests.Unit/step-ca-win.dockerfile index 37cba87d9..e653a1724 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/step-ca-win.dockerfile +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/step-ca-win.dockerfile @@ -16,6 +16,6 @@ FROM base AS final COPY ./step-ca-win-init.bat . COPY --from=netapi /Windows/System32/netapi32.dll /Windows/System32/netapi32.dll -HEALTHCHECK CMD curl -Method GET -f http://localhost:9000/health || exit 1 +HEALTHCHECK CMD curl -Method GET -f %HEALTH_URL% || exit 1 CMD step-ca-win-init.bat && cmd diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/unit-test-462.runsettings b/src/Certify.Tests/Certify.Core.Tests.Unit/unit-test-462.runsettings new file mode 100644 index 000000000..9e1f06a10 --- /dev/null +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/unit-test-462.runsettings @@ -0,0 +1,48 @@ + + + + + + %SystemDrive%\%HOMEPATH%\.nuget\packages\microsoft.codecoverage\17.8.0\build\netstandard2.0 + net462 + .\TestResults-4_6_2-windows + true + + + + + + + Cobertura + + + + + .*Certify.*$ + .*Plugin.Datastore.SQLite.dll$ + + + .*Certify.Core.Tests.Unit.dll$ + .*Moq.dll$ + + + + True + + + + + + + + + + + normal + + + + + + diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/unit-test-8-0-linux.runsettings b/src/Certify.Tests/Certify.Core.Tests.Unit/unit-test-8-0-linux.runsettings new file mode 100644 index 000000000..1ffd818a7 --- /dev/null +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/unit-test-8-0-linux.runsettings @@ -0,0 +1,36 @@ + + + + + + $HOME/.nuget/packages/coverlet.collector/6.0.0/build/netstandard1.0;$HOME/.nuget/packages/coverlet.msbuild/6.0.0/build + net8.0 + ./TestResults-8_0-Linux + true + + + + + + + cobertura + + [Certify.*]*,[Plugin.Datastore.SQLite]* + + false + + + + + + + + + + normal + + + + + + diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/unit-test-8-0.runsettings b/src/Certify.Tests/Certify.Core.Tests.Unit/unit-test-8-0.runsettings new file mode 100644 index 000000000..1f1211437 --- /dev/null +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/unit-test-8-0.runsettings @@ -0,0 +1,36 @@ + + + + + + %SystemDrive%\%HOMEPATH%\.nuget\packages\coverlet.collector\6.0.0\build\netstandard1.0;%SystemDrive%\%HOMEPATH%\.nuget\packages\coverlet.msbuild\6.0.0\build + net8.0 + .\TestResults-8_0-windows + true + + + + + + + cobertura + + [Certify.*]*,[Plugin.Datastore.SQLite]* + + false + + + + + + + + + + normal + + + + + + diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/windows_compose.yaml b/src/Certify.Tests/Certify.Core.Tests.Unit/windows_compose.yaml index 80e4f500c..8dd660ff5 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/windows_compose.yaml +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/windows_compose.yaml @@ -50,6 +50,11 @@ services: profiles: ["4_6_2", "8_0"] ports: - 9000:9000 + environment: + DOCKER_STEPCA_INIT_NAME: Smallstep + DOCKER_STEPCA_INIT_DNS_NAMES: localhost + DOCKER_STEPCA_INIT_REMOTE_MANAGEMENT: true + DOCKER_STEPCA_INIT_ACME: true volumes: - step:C:\Users\ContainerUser\.step From 4a592083343c05ac0d301520225ae899b5abc8d7 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Fri, 15 Dec 2023 17:06:27 +0800 Subject: [PATCH 096/328] API: simplify access to health endpoint for debugging etc --- .../Certify.Server.Api.Public/Controllers/v1/SystemController.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/SystemController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/SystemController.cs index efdef9c32..09d69d291 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/SystemController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/SystemController.cs @@ -46,6 +46,7 @@ public async Task GetSystemVersion() /// [HttpGet] [Route("health")] + [Route("/health")] public async Task GetHealth() { var serviceAvailable = false; From 17ad2e434d21e2a912eb7594ae2a6b18f9e1c850 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Fri, 15 Dec 2023 17:06:52 +0800 Subject: [PATCH 097/328] API: update service host/port from ENV depending on context --- src/Certify.Server/Certify.Server.Api.Public/Startup.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Certify.Server/Certify.Server.Api.Public/Startup.cs b/src/Certify.Server/Certify.Server.Api.Public/Startup.cs index 0ef5e6eba..4df5446ae 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Startup.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Startup.cs @@ -119,8 +119,9 @@ public void ConfigureServices(IServiceCollection services) var configManager = new ServiceConfigManager(); var serviceConfig = configManager.GetServiceConfig(); - var serviceHostEnv = Environment.GetEnvironmentVariable("CERTIFY_SERVER_HOST"); - var servicePortEnv = Environment.GetEnvironmentVariable("CERTIFY_SERVER_PORT"); + // Optionally load service host/port from environment variables. ENV_CERTIFY_SERVICE_ is kubernetes and CERTIFY_SERVICE_HOST is docker-compose + var serviceHostEnv = Environment.GetEnvironmentVariable("ENV_CERTIFY_SERVICE_HOST") ?? Environment.GetEnvironmentVariable("CERTIFY_SERVICE_HOST"); + var servicePortEnv = Environment.GetEnvironmentVariable("ENV_CERTIFY_SERVICE_PORT") ?? Environment.GetEnvironmentVariable("CERTIFY_SERVICE_PORT"); if (!string.IsNullOrEmpty(serviceHostEnv)) { From 3baef24f7155c50d889fd14b3c29cd4b49b735fc Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Mon, 18 Dec 2023 11:45:09 +0800 Subject: [PATCH 098/328] Fix and test validation for mixed wildcards with subdomain like names --- .../Validation/CertificateEditorService.cs | 4 +- .../Tests/CertificateEditorServiceTests.cs | 264 ++++++++++++++++++ 2 files changed, 266 insertions(+), 2 deletions(-) create mode 100644 src/Certify.Tests/Certify.Core.Tests.Unit/Tests/CertificateEditorServiceTests.cs diff --git a/src/Certify.Models/Shared/Validation/CertificateEditorService.cs b/src/Certify.Models/Shared/Validation/CertificateEditorService.cs index 6fc89b84a..56e33f67d 100644 --- a/src/Certify.Models/Shared/Validation/CertificateEditorService.cs +++ b/src/Certify.Models/Shared/Validation/CertificateEditorService.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Certify.Locales; @@ -358,7 +358,7 @@ public static ValidationResult Validate(ManagedCertificate item, SiteInfo? selec if (identifiers.Any(d => d.Value.StartsWith("*.", StringComparison.OrdinalIgnoreCase))) { - foreach (var wildcard in identifiers.Where(d => d.IdentifierType == CertIdentifierType.Dns && d.Value.StartsWith("*.", StringComparison.OrdinalIgnoreCase))) + foreach (var wildcard in identifiers.Where(d => d.IdentifierType == CertIdentifierType.Dns && d.Value.StartsWith("*.",StringComparison.OrdinalIgnoreCase))) { var rootDomain = wildcard.Value.Replace("*.", ""); // add list of identifiers where label count exceeds root domain label count diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/CertificateEditorServiceTests.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/CertificateEditorServiceTests.cs new file mode 100644 index 000000000..4ea03b1d1 --- /dev/null +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/CertificateEditorServiceTests.cs @@ -0,0 +1,264 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Certify.Models; +using Certify.Models.Providers; +using Certify.Models.Shared.Validation; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; + +namespace Certify.Core.Tests.Unit +{ + [TestClass] + public class CertificateEditorServiceTests + { + + [TestMethod, Description("Test primary domain required")] + public void TestPrimaryDomainRequired() + { + + var item = new ManagedCertificate + { + DomainOptions = new System.Collections.ObjectModel.ObservableCollection + { + new DomainOption { Domain = "test.com", IsPrimaryDomain=false, IsSelected=true }, + new DomainOption { Domain = "www.test.com", IsPrimaryDomain=false, IsSelected=true } + }, + RequestConfig = new CertRequestConfig + { + Challenges = new System.Collections.ObjectModel.ObservableCollection + { + new CertRequestChallengeConfig + { + ChallengeType = SupportedChallengeTypes.CHALLENGE_TYPE_HTTP + } + } + } + }; + + // skip auto config to that primary domain is not auto selected + var validationResult = CertificateEditorService.Validate(item, null, null, false); + + Assert.IsNotNull(validationResult); + Assert.IsFalse(validationResult.IsValid); + Assert.AreEqual(ValidationErrorCodes.PRIMARY_IDENTIFIER_REQUIRED.ToString(), validationResult.ErrorCode); + } + + [TestMethod, Description("Test primary domain too many")] + public void TestPrimaryDomainTooMany() + { + + var item = new ManagedCertificate + { + DomainOptions = new System.Collections.ObjectModel.ObservableCollection + { + new DomainOption { Domain = "test.com", IsPrimaryDomain=true, IsSelected=true }, + new DomainOption { Domain = "www.test.com", IsPrimaryDomain=true, IsSelected=true } + }, + RequestConfig = new CertRequestConfig + { + Challenges = new System.Collections.ObjectModel.ObservableCollection + { + new CertRequestChallengeConfig + { + ChallengeType = SupportedChallengeTypes.CHALLENGE_TYPE_HTTP + } + } + } + }; + + var validationResult = CertificateEditorService.Validate(item, null, null, true); + + Assert.IsNotNull(validationResult); + Assert.IsFalse(validationResult.IsValid); + Assert.AreEqual(ValidationErrorCodes.PRIMARY_IDENTIFIER_TOOMANY.ToString(), validationResult.ErrorCode); + } + + [TestMethod, Description("Test mixed wildcard label validation")] + public void TestMixedWildcardLabels() + { + + var item = new ManagedCertificate + { + DomainOptions = new System.Collections.ObjectModel.ObservableCollection + { + new DomainOption { Domain = "test.com", IsPrimaryDomain=true, IsSelected=true }, + new DomainOption { Domain = "www.test.com", IsPrimaryDomain=false,IsSelected=true }, + new DomainOption { Domain = "*.test.com", IsPrimaryDomain=false,IsSelected=true } + }, + RequestConfig = new CertRequestConfig + { + Challenges = new System.Collections.ObjectModel.ObservableCollection + { + new CertRequestChallengeConfig + { + ChallengeType = SupportedChallengeTypes.CHALLENGE_TYPE_DNS + } + } + } + }; + + var validationResult = CertificateEditorService.Validate(item, null, null, true); + + Assert.IsNotNull(validationResult); + Assert.IsFalse(validationResult.IsValid); + Assert.AreEqual(ValidationErrorCodes.MIXED_WILDCARD_WITH_LABELS.ToString(), validationResult.ErrorCode); + } + + [TestMethod, Description("Test mixed wildcard subdomain-like name allowed")] + public void TestMixedWildcardSubdomainLabels() + { + // in this example *.test.com and *.vs-test.com should be allowed as they are distinct + var item = new ManagedCertificate + { + DomainOptions = new System.Collections.ObjectModel.ObservableCollection + { + new DomainOption { Domain = "test.com", IsPrimaryDomain=true, IsSelected=true }, + new DomainOption { Domain = "vs-test.com", IsPrimaryDomain=false, IsSelected=true }, + new DomainOption { Domain = "*.test.com", IsPrimaryDomain=false,IsSelected=true }, + new DomainOption { Domain = "*.vs-test.com", IsPrimaryDomain=false,IsSelected=true } + }, + RequestConfig = new CertRequestConfig + { + Challenges = new System.Collections.ObjectModel.ObservableCollection + { + new CertRequestChallengeConfig + { + ChallengeType = SupportedChallengeTypes.CHALLENGE_TYPE_DNS, + ChallengeProvider = "DNS01.API.Route53" + } + } + } + }; + + var validationResult = CertificateEditorService.Validate(item, null, null, true); + + Assert.IsNotNull(validationResult); + Assert.IsTrue(validationResult.IsValid); + + } + + [TestMethod, Description("Test mixed wildcard subdomain-like with invalid subdomain label")] + public void TestMixedWildcardSubdomainWithInvalidLabels() + { + // in this example *.test.com and *.vs-test.com should be allowed as they are distinct + var item = new ManagedCertificate + { + DomainOptions = new System.Collections.ObjectModel.ObservableCollection + { + new DomainOption { Domain = "test.com", IsPrimaryDomain=true, IsSelected=true }, + new DomainOption { Domain = "vs-test.com", IsPrimaryDomain=false, IsSelected=true }, + new DomainOption { Domain = "*.test.com", IsPrimaryDomain=false,IsSelected=true }, + new DomainOption { Domain = "*.vs-test.com", IsPrimaryDomain=false,IsSelected=true }, + new DomainOption { Domain = "www.vs-test.com", IsPrimaryDomain=false,IsSelected=true } + }, + RequestConfig = new CertRequestConfig + { + Challenges = new System.Collections.ObjectModel.ObservableCollection + { + new CertRequestChallengeConfig + { + ChallengeType = SupportedChallengeTypes.CHALLENGE_TYPE_DNS, + ChallengeProvider = "DNS01.API.Route53" + } + } + } + }; + + var validationResult = CertificateEditorService.Validate(item, null, null, true); + + Assert.IsNotNull(validationResult); + Assert.IsFalse(validationResult.IsValid); + Assert.AreEqual(ValidationErrorCodes.MIXED_WILDCARD_WITH_LABELS.ToString(), validationResult.ErrorCode); + + } + + [TestMethod, Description("Test mixed wildcard invalid challenge type")] + public void TestMixedWildcardInvalidChallenge() + { + + var item = new ManagedCertificate + { + DomainOptions = new System.Collections.ObjectModel.ObservableCollection + { + new DomainOption { Domain = "test.com", IsPrimaryDomain=true, IsSelected=true }, + new DomainOption { Domain = "www.test.com", IsPrimaryDomain=false,IsSelected=true }, + new DomainOption { Domain = "*.test.com", IsPrimaryDomain=false,IsSelected=true } + }, + RequestConfig = new CertRequestConfig + { + Challenges = new System.Collections.ObjectModel.ObservableCollection + { + new CertRequestChallengeConfig + { + ChallengeType = SupportedChallengeTypes.CHALLENGE_TYPE_HTTP + } + } + } + }; + + var validationResult = CertificateEditorService.Validate(item, null, null, true); + + Assert.IsNotNull(validationResult); + Assert.IsFalse(validationResult.IsValid); + Assert.AreEqual(ValidationErrorCodes.CHALLENGE_TYPE_INVALID.ToString(), validationResult.ErrorCode); + } + + [TestMethod, Description("Test max CN length")] + public void TestMaxCNLength() + { + var item = new ManagedCertificate + { + DomainOptions = new System.Collections.ObjectModel.ObservableCollection + { + new DomainOption { Domain = "TherearemanyvariationsofpassagesofLoremIpsumavailablebutthemajorityhavesufferedalterationinsomeformbyinjectedhumourorrandomisedwordswhichdontlookevenslightlybelievable.com", IsPrimaryDomain=true, IsSelected=true }, + new DomainOption { Domain = "www.test.com", IsPrimaryDomain=false,IsSelected=true } + }, + RequestConfig = new CertRequestConfig + { + Challenges = new System.Collections.ObjectModel.ObservableCollection + { + new CertRequestChallengeConfig + { + ChallengeType = SupportedChallengeTypes.CHALLENGE_TYPE_HTTP + } + } + } + }; + + var validationResult = CertificateEditorService.Validate(item, null, null, true); + + Assert.IsNotNull(validationResult); + Assert.IsFalse(validationResult.IsValid); + Assert.AreEqual(ValidationErrorCodes.CN_LIMIT.ToString(), validationResult.ErrorCode); + } + + [TestMethod, Description("Test with invalid local hostname")] + public void TestInvalidHostname() + { + var item = new ManagedCertificate + { + DomainOptions = new System.Collections.ObjectModel.ObservableCollection + { + new DomainOption { Domain = "intranet.local", IsPrimaryDomain=true, IsSelected=true }, + new DomainOption { Domain = "exchange01", IsPrimaryDomain=false,IsSelected=true } + }, + RequestConfig = new CertRequestConfig + { + Challenges = new System.Collections.ObjectModel.ObservableCollection + { + new CertRequestChallengeConfig + { + ChallengeType = SupportedChallengeTypes.CHALLENGE_TYPE_HTTP + } + } + } + }; + + var validationResult = CertificateEditorService.Validate(item, null, null, true); + + Assert.IsNotNull(validationResult); + Assert.IsFalse(validationResult.IsValid); + Assert.AreEqual(ValidationErrorCodes.INVALID_HOSTNAME.ToString(), validationResult.ErrorCode); + } + } +} From 960d92a8981c561510396038fb4571b7c26e554e Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Mon, 18 Dec 2023 11:45:43 +0800 Subject: [PATCH 099/328] Move unit test source into subfolder, fix running tests from VS --- .../Certify.Core.Tests.Unit.csproj | 19 +++++-------------- .../{ => Tests}/AccessControlTests.cs | 0 .../{ => Tests}/AccountKeyTests.cs | 0 .../{ => Tests}/BindingMatchTests.cs | 0 .../{ => Tests}/CAFailoverTests.cs | 0 .../{ => Tests}/CertifyManagerAccountTests.cs | 0 .../{ => Tests}/CertifyServiceTests.cs | 0 .../{ => Tests}/ChallengeConfigMatchTests.cs | 0 .../{ => Tests}/ConnectionCheckTests.cs | 0 .../{ => Tests}/DnsQueryTests.cs | 0 .../{ => Tests}/DomainZoneMatchTests.cs | 0 .../{ => Tests}/GetDnsProviderTests.cs | 0 .../{ => Tests}/LoggyTests.cs | 0 .../{ => Tests}/MiscTests.cs | 0 .../{ => Tests}/RdapTests.cs | 0 .../{ => Tests}/RenewalRequiredTests.cs | 0 .../{ => Tests}/UpdateCheckTest.cs | 0 17 files changed, 5 insertions(+), 14 deletions(-) rename src/Certify.Tests/Certify.Core.Tests.Unit/{ => Tests}/AccessControlTests.cs (100%) rename src/Certify.Tests/Certify.Core.Tests.Unit/{ => Tests}/AccountKeyTests.cs (100%) rename src/Certify.Tests/Certify.Core.Tests.Unit/{ => Tests}/BindingMatchTests.cs (100%) rename src/Certify.Tests/Certify.Core.Tests.Unit/{ => Tests}/CAFailoverTests.cs (100%) rename src/Certify.Tests/Certify.Core.Tests.Unit/{ => Tests}/CertifyManagerAccountTests.cs (100%) rename src/Certify.Tests/Certify.Core.Tests.Unit/{ => Tests}/CertifyServiceTests.cs (100%) rename src/Certify.Tests/Certify.Core.Tests.Unit/{ => Tests}/ChallengeConfigMatchTests.cs (100%) rename src/Certify.Tests/Certify.Core.Tests.Unit/{ => Tests}/ConnectionCheckTests.cs (100%) rename src/Certify.Tests/Certify.Core.Tests.Unit/{ => Tests}/DnsQueryTests.cs (100%) rename src/Certify.Tests/Certify.Core.Tests.Unit/{ => Tests}/DomainZoneMatchTests.cs (100%) rename src/Certify.Tests/Certify.Core.Tests.Unit/{ => Tests}/GetDnsProviderTests.cs (100%) rename src/Certify.Tests/Certify.Core.Tests.Unit/{ => Tests}/LoggyTests.cs (100%) rename src/Certify.Tests/Certify.Core.Tests.Unit/{ => Tests}/MiscTests.cs (100%) rename src/Certify.Tests/Certify.Core.Tests.Unit/{ => Tests}/RdapTests.cs (100%) rename src/Certify.Tests/Certify.Core.Tests.Unit/{ => Tests}/RenewalRequiredTests.cs (100%) rename src/Certify.Tests/Certify.Core.Tests.Unit/{ => Tests}/UpdateCheckTest.cs (100%) diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj index 4800cb76e..bf556a61c 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj @@ -102,26 +102,16 @@ 1701;1702;NU1701 - .\unit-test-462.runsettings + $(MSBuildProjectDirectory)/unit-test-462.runsettings - .\unit-test-8-0.runsettings + $(MSBuildProjectDirectory)/unit-test-8-0.runsettings - .\unit-test-8-0-linux.runsettings + $(MSBuildProjectDirectory)/unit-test-8-0-linux.runsettings + - - - - - - - PreserveNewest - - - PreserveNewest - PreserveNewest @@ -170,4 +160,5 @@ + \ No newline at end of file diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/AccessControlTests.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/AccessControlTests.cs similarity index 100% rename from src/Certify.Tests/Certify.Core.Tests.Unit/AccessControlTests.cs rename to src/Certify.Tests/Certify.Core.Tests.Unit/Tests/AccessControlTests.cs diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/AccountKeyTests.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/AccountKeyTests.cs similarity index 100% rename from src/Certify.Tests/Certify.Core.Tests.Unit/AccountKeyTests.cs rename to src/Certify.Tests/Certify.Core.Tests.Unit/Tests/AccountKeyTests.cs diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/BindingMatchTests.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/BindingMatchTests.cs similarity index 100% rename from src/Certify.Tests/Certify.Core.Tests.Unit/BindingMatchTests.cs rename to src/Certify.Tests/Certify.Core.Tests.Unit/Tests/BindingMatchTests.cs diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/CAFailoverTests.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/CAFailoverTests.cs similarity index 100% rename from src/Certify.Tests/Certify.Core.Tests.Unit/CAFailoverTests.cs rename to src/Certify.Tests/Certify.Core.Tests.Unit/Tests/CAFailoverTests.cs diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/CertifyManagerAccountTests.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/CertifyManagerAccountTests.cs similarity index 100% rename from src/Certify.Tests/Certify.Core.Tests.Unit/CertifyManagerAccountTests.cs rename to src/Certify.Tests/Certify.Core.Tests.Unit/Tests/CertifyManagerAccountTests.cs diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/CertifyServiceTests.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/CertifyServiceTests.cs similarity index 100% rename from src/Certify.Tests/Certify.Core.Tests.Unit/CertifyServiceTests.cs rename to src/Certify.Tests/Certify.Core.Tests.Unit/Tests/CertifyServiceTests.cs diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/ChallengeConfigMatchTests.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/ChallengeConfigMatchTests.cs similarity index 100% rename from src/Certify.Tests/Certify.Core.Tests.Unit/ChallengeConfigMatchTests.cs rename to src/Certify.Tests/Certify.Core.Tests.Unit/Tests/ChallengeConfigMatchTests.cs diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/ConnectionCheckTests.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/ConnectionCheckTests.cs similarity index 100% rename from src/Certify.Tests/Certify.Core.Tests.Unit/ConnectionCheckTests.cs rename to src/Certify.Tests/Certify.Core.Tests.Unit/Tests/ConnectionCheckTests.cs diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/DnsQueryTests.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/DnsQueryTests.cs similarity index 100% rename from src/Certify.Tests/Certify.Core.Tests.Unit/DnsQueryTests.cs rename to src/Certify.Tests/Certify.Core.Tests.Unit/Tests/DnsQueryTests.cs diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/DomainZoneMatchTests.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/DomainZoneMatchTests.cs similarity index 100% rename from src/Certify.Tests/Certify.Core.Tests.Unit/DomainZoneMatchTests.cs rename to src/Certify.Tests/Certify.Core.Tests.Unit/Tests/DomainZoneMatchTests.cs diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/GetDnsProviderTests.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/GetDnsProviderTests.cs similarity index 100% rename from src/Certify.Tests/Certify.Core.Tests.Unit/GetDnsProviderTests.cs rename to src/Certify.Tests/Certify.Core.Tests.Unit/Tests/GetDnsProviderTests.cs diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/LoggyTests.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/LoggyTests.cs similarity index 100% rename from src/Certify.Tests/Certify.Core.Tests.Unit/LoggyTests.cs rename to src/Certify.Tests/Certify.Core.Tests.Unit/Tests/LoggyTests.cs diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/MiscTests.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/MiscTests.cs similarity index 100% rename from src/Certify.Tests/Certify.Core.Tests.Unit/MiscTests.cs rename to src/Certify.Tests/Certify.Core.Tests.Unit/Tests/MiscTests.cs diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/RdapTests.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/RdapTests.cs similarity index 100% rename from src/Certify.Tests/Certify.Core.Tests.Unit/RdapTests.cs rename to src/Certify.Tests/Certify.Core.Tests.Unit/Tests/RdapTests.cs diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/RenewalRequiredTests.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/RenewalRequiredTests.cs similarity index 100% rename from src/Certify.Tests/Certify.Core.Tests.Unit/RenewalRequiredTests.cs rename to src/Certify.Tests/Certify.Core.Tests.Unit/Tests/RenewalRequiredTests.cs diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/UpdateCheckTest.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/UpdateCheckTest.cs similarity index 100% rename from src/Certify.Tests/Certify.Core.Tests.Unit/UpdateCheckTest.cs rename to src/Certify.Tests/Certify.Core.Tests.Unit/Tests/UpdateCheckTest.cs From 4fe5fb73e437183220c8098202abcb41fba059b8 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Mon, 18 Dec 2023 16:29:33 +0800 Subject: [PATCH 100/328] API: consolidate ports for various launch profiles --- .../Properties/launchSettings.json | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/Certify.Server/Certify.Server.Api.Public/Properties/launchSettings.json b/src/Certify.Server/Certify.Server.Api.Public/Properties/launchSettings.json index 4529b8901..3e4e64087 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Properties/launchSettings.json +++ b/src/Certify.Server/Certify.Server.Api.Public/Properties/launchSettings.json @@ -2,41 +2,42 @@ "profiles": { "IIS Express": { "commandName": "IISExpress", - "launchUrl": "docs", + "launchUrl": "https://localhost:44361/docs", "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" + "ASPNETCORE_URLS": "https://localhost:44361;http://localhost:44360", + "CERTIFY_SERVICE_HOST": "localhost", + "CERTIFY_SERVICE_PORT": "9695" } }, "Certify.Server.Api.Public": { "commandName": "Project", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development", - "CERTIFY_SERVER_HOST": "127.0.0.2", - "CERTIFY_SERVER_PORT": "9695" + "CERTIFY_SERVICE_HOST": "127.0.0.2", + "CERTIFY_SERVICE_PORT": "9695" }, "applicationUrl": "https://localhost:44361;http://localhost:44360" }, "WSL": { "commandName": "WSL2", - "launchUrl": "https://localhost:5001", + "launchUrl": "https://localhost:44361/docs", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development", "ASPNETCORE_URLS": "https://localhost:44361;http://localhost:44360", - "CERTIFY_SERVER_HOST": "localhost", - "CERTIFY_SERVER_PORT": "9695" - }, - "distributionName": "" + "CERTIFY_SERVICE_HOST": "localhost", + "CERTIFY_SERVICE_PORT": "9695" + } }, "Docker": { "commandName": "Docker", "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/docs", "environmentVariables": { - "ASPNETCORE_URLS": "https://localhost:44361;http://localhost:44360", - "CERTIFY_SERVER_HOST": "localhost", - "CERTIFY_SERVER_PORT": "9695" + "ASPNETCORE_URLS": "https://0.0.0.0:44361;http://0.0.0.0:44360", + "CERTIFY_SERVICE_HOST": "localhost", + "CERTIFY_SERVICE_PORT": "9695" }, - "publishAllPorts": true, - "useSSL": true + "httpPort": 44360, + "sslPort": 44361 } }, "iisSettings": { From 13c7e0f79a1112a148930f5f4688d386b1967fe8 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Mon, 18 Dec 2023 16:51:18 +0800 Subject: [PATCH 101/328] Service Worker: consolidate ports for launch profiles --- .../Properties/launchSettings.json | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Properties/launchSettings.json b/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Properties/launchSettings.json index 6ab245a4f..487cd482b 100644 --- a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Properties/launchSettings.json +++ b/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Properties/launchSettings.json @@ -4,14 +4,18 @@ "windowsAuthentication": false, "anonymousAuthentication": true, "iisExpress": { - "applicationUrl": "http://localhost:54688", - "sslPort": 44346 + "applicationUrl": "http://localhost:9695" } }, + "profiles": { "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, + "launchUrl": "http://localhost:9695/docs", + "iisExpress": { + "applicationUrl": "http://localhost:9695" + }, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } @@ -21,26 +25,25 @@ "launchBrowser": true, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "https://localhost:5001;http://localhost:5000" + } }, "Docker": { "commandName": "Docker", - "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/api/system/appversion", + "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/docs", "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" + "ASPNETCORE_ENVIRONMENT": "Development", + "ASPNETCORE_URLS": "http://0.0.0.0:9695" }, - "publishAllPorts": true + "publishAllPorts": true, + "httpPort": 9695 }, "WSL": { "commandName": "WSL2", "launchBrowser": true, - "launchUrl": "https://localhost:5001/api/system/appversion", + "launchUrl": "http://localhost:9695/docs", "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development", - "ASPNETCORE_URLS": "https://localhost:5001;http://localhost:5000" - }, - "distributionName": "" + "ASPNETCORE_ENVIRONMENT": "Development" + } } } } From 44bbabe14991568bbcef0758f6f8666efb839d27 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Tue, 19 Dec 2023 15:37:00 +0800 Subject: [PATCH 102/328] Cleanup --- src/Certify.Client/CertifyApiClient.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Certify.Client/CertifyApiClient.cs b/src/Certify.Client/CertifyApiClient.cs index 6d904e026..bb06dfbd9 100644 --- a/src/Certify.Client/CertifyApiClient.cs +++ b/src/Certify.Client/CertifyApiClient.cs @@ -10,7 +10,6 @@ using Certify.Models.API; using Certify.Models.Config; using Certify.Models.Config.AccessControl; -using Certify.Models.Config.Migration; using Certify.Models.Reporting; using Certify.Models.Utils; using Certify.Shared; From 20ef81dc878399fa64f358ef0a1ef411ad68c4bc Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Tue, 19 Dec 2023 15:44:35 +0800 Subject: [PATCH 103/328] Package updates --- .../DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj index 76b0fc8d9..dc44d9be6 100644 --- a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj +++ b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj @@ -7,7 +7,7 @@ - + From 9307d6d86b0907f17f71c6052c8dee5f1e2c79cd Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Wed, 20 Dec 2023 17:05:28 +0800 Subject: [PATCH 104/328] Cleanup --- src/Certify.Core/Management/CertifyManager/CertifyManager.cs | 3 +-- .../Certify.Server.Api.Public.csproj | 3 +++ .../Certify.Server.Api.Public/appsettings.Development.json | 4 +++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.cs index 8917da2e2..7b5dbd4de 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.cs @@ -345,8 +345,7 @@ private void InitLogging(Shared.ServiceConfig serverConfig) _serviceLog = new Loggy( new LoggerConfiguration() .MinimumLevel.ControlledBy(_loggingLevelSwitch) - .WriteTo.Debug() - .WriteTo.File(Path.Combine(EnvironmentUtil.CreateAppDataPath("logs"), "session.log"), shared: true, flushToDiskInterval: new TimeSpan(0, 0, 10), rollOnFileSizeLimit: true, fileSizeLimitBytes: 5 * 1024 * 1024) + .WriteTo.File(Path.Combine(EnvironmentUtil.GetAppDataFolder("logs"), "session.log"), shared: true, flushToDiskInterval: new TimeSpan(0, 0, 10), rollOnFileSizeLimit: true, fileSizeLimitBytes: 5 * 1024 * 1024) .CreateLogger() ); diff --git a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj index c394d382e..df15f2b92 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj +++ b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj @@ -9,6 +9,9 @@ 8793068b-aa98-48a5-807b-962b5b3e1aea True + + False + diff --git a/src/Certify.Server/Certify.Server.Api.Public/appsettings.Development.json b/src/Certify.Server/Certify.Server.Api.Public/appsettings.Development.json index 90dc7b136..589bf6c0d 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/appsettings.Development.json +++ b/src/Certify.Server/Certify.Server.Api.Public/appsettings.Development.json @@ -3,7 +3,9 @@ "LogLevel": { "Default": "Debug", "Microsoft": "Debug", - "Microsoft.Hosting.Lifetime": "Debug" + "Microsoft.Hosting.Lifetime": "Debug", + "Microsoft.AspNetCore.SignalR": "Debug", + "Microsoft.AspNetCore.Http.Connections": "Debug" } } } From 8c409d3975ab175d673fedb8ecae667f4151ce4b Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 21 Dec 2023 17:11:32 +0800 Subject: [PATCH 105/328] API: update connection handling to backend service --- .../Certify.Server.Api.Public/Startup.cs | 37 +++++++++++++++---- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/src/Certify.Server/Certify.Server.Api.Public/Startup.cs b/src/Certify.Server/Certify.Server.Api.Public/Startup.cs index 4df5446ae..f3dae0e94 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Startup.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Startup.cs @@ -133,17 +133,38 @@ public void ConfigureServices(IServiceCollection services) serviceConfig.Port = tryServicePort; } - var defaultConnectionConfig = new Shared.ServerConnection(serviceConfig); + var backendServiceConnectionConfig = new Shared.ServerConnection(serviceConfig); + + backendServiceConnectionConfig.Authentication = "jwt"; + backendServiceConnectionConfig.ServerMode = "v2"; + System.Diagnostics.Debug.WriteLine($"Public API: connecting to background service {serviceConfig:Host}:{serviceConfig.Port}"); - var connections = ServerConnectionManager.GetServerConnections(null, defaultConnectionConfig); - var serverConnection = connections.FirstOrDefault(c => c.IsDefault == true); -#if DEBUG - serverConnection = defaultConnectionConfig; -#endif - var internalServiceClient = new Client.CertifyServiceClient(configManager, serverConnection); + var internalServiceClient = new Client.CertifyServiceClient(configManager, backendServiceConnectionConfig); + + var attempts = 3; + while (attempts > 0) + { + try + { + internalServiceClient.ConnectStatusStreamAsync().Wait(); + break; + } + catch + { + attempts--; + + if (attempts == 0) + { + throw; + } + else + { + Task.Delay(2000).Wait(); // wait for service to start + } + } + } - internalServiceClient.ConnectStatusStreamAsync(); internalServiceClient.OnMessageFromService += InternalServiceClient_OnMessageFromService; internalServiceClient.OnRequestProgressStateUpdated += InternalServiceClient_OnRequestProgressStateUpdated; internalServiceClient.OnManagedCertificateUpdated += InternalServiceClient_OnManagedCertificateUpdated; From fe7a4f2e1bf01e83874f92b8ab967afb853e83eb Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 21 Dec 2023 17:12:12 +0800 Subject: [PATCH 106/328] Service worker: fix package version exception --- .../Certify.Server.Core/Certify.Server.Core.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj index 24c804572..2d8319d72 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj @@ -7,6 +7,7 @@ + From 16d68b191db11267819163e8daa72c1801ba07fa Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 21 Dec 2023 17:22:42 +0800 Subject: [PATCH 107/328] API: all controllers should be partial classes for extension with source gen --- .../Controllers/internal/ChallengeProviderController.cs | 2 +- .../Controllers/internal/DeploymentTaskController.cs | 2 +- .../Controllers/internal/PreviewController.cs | 2 +- .../Controllers/internal/TargetController.cs | 2 +- .../Controllers/v1/AuthController.cs | 2 +- .../Controllers/v1/CertificateController.cs | 2 +- .../Controllers/v1/ValidationController.cs | 4 ++-- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/ChallengeProviderController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/ChallengeProviderController.cs index 0b17a5579..f0623000b 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/ChallengeProviderController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/ChallengeProviderController.cs @@ -11,7 +11,7 @@ namespace Certify.Server.Api.Public.Controllers /// [ApiController] [Route("internal/v1/[controller]")] - public class ChallengeProviderController : ControllerBase + public partial class ChallengeProviderController : ControllerBase { private readonly ILogger _logger; diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/DeploymentTaskController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/DeploymentTaskController.cs index c9798e017..f6689980a 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/DeploymentTaskController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/DeploymentTaskController.cs @@ -10,7 +10,7 @@ namespace Certify.Server.Api.Public.Controllers ///
[ApiController] [Route("internal/v1/[controller]")] - public class DeploymentTaskController : ControllerBase + public partial class DeploymentTaskController : ControllerBase { private readonly ILogger _logger; diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/PreviewController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/PreviewController.cs index f035d576e..5ba7728c9 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/PreviewController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/PreviewController.cs @@ -11,7 +11,7 @@ namespace Certify.Server.Api.Public.Controllers ///
[ApiController] [Route("internal/v1/[controller]")] - public class PreviewController : ControllerBase + public partial class PreviewController : ControllerBase { private readonly ILogger _logger; diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/TargetController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/TargetController.cs index 0d469e433..895fbcb26 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/TargetController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/TargetController.cs @@ -11,7 +11,7 @@ namespace Certify.Server.Api.Public.Controllers ///
[ApiController] [Route("internal/v1/[controller]")] - public class TargetController : ControllerBase + public partial class TargetController : ControllerBase { private readonly ILogger _logger; diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/AuthController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/AuthController.cs index c667da260..e51c31c73 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/AuthController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/AuthController.cs @@ -12,7 +12,7 @@ namespace Certify.Server.Api.Public.Controllers ///
[ApiController] [Route("api/v1/[controller]")] - public class AuthController : ControllerBase + public partial class AuthController : ControllerBase { private readonly ILogger _logger; private readonly ICertifyInternalApiClient _client; diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs index 182f407c0..759c1e3de 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs @@ -12,7 +12,7 @@ namespace Certify.Server.Api.Public.Controllers ///
[ApiController] [Route("api/v1/[controller]")] - public class CertificateController : ControllerBase + public partial class CertificateController : ControllerBase { private readonly ILogger _logger; diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/ValidationController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/ValidationController.cs index 9f7212972..71b4127bc 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/ValidationController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/ValidationController.cs @@ -10,7 +10,7 @@ namespace Certify.Server.Api.Public.Controllers ///
[ApiController] [Route("api/v1/[controller]")] - public class ValidationController : ControllerBase + public partial class ValidationController : ControllerBase { private readonly ILogger _logger; @@ -29,7 +29,7 @@ public ValidationController(ILogger logger, ICertifyIntern } /// - /// get current challenge info fo a given type/key + /// get current challenge info for a given type/key /// /// /// From 15dde9d1e725a010e00c7fc831d4e9e6e3338e0d Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 21 Dec 2023 17:48:18 +0800 Subject: [PATCH 108/328] Package updates Package updates --- .../DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj | 2 +- src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj | 2 +- src/Certify.UI.Shared/Certify.UI.Shared.csproj | 4 ++-- src/Certify.UI/Certify.UI.csproj | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj index dc44d9be6..172a6a417 100644 --- a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj +++ b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj b/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj index 26de95501..aabbfb6c2 100644 --- a/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj +++ b/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj @@ -9,7 +9,7 @@ - + diff --git a/src/Certify.UI.Shared/Certify.UI.Shared.csproj b/src/Certify.UI.Shared/Certify.UI.Shared.csproj index 1dfec6cde..35e9e1d33 100644 --- a/src/Certify.UI.Shared/Certify.UI.Shared.csproj +++ b/src/Certify.UI.Shared/Certify.UI.Shared.csproj @@ -32,12 +32,12 @@ - + NU1701 - + diff --git a/src/Certify.UI/Certify.UI.csproj b/src/Certify.UI/Certify.UI.csproj index 6f6198df1..25bab82ec 100644 --- a/src/Certify.UI/Certify.UI.csproj +++ b/src/Certify.UI/Certify.UI.csproj @@ -158,7 +158,7 @@ 4.7.0.9 - 3.0.0-alpha0457 + 3.0.0-alpha0476 0.5.0.1 From 04d74cdea40e6dbc3d05195f8a34da3847143d12 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Mon, 8 Jan 2024 16:21:18 +0800 Subject: [PATCH 109/328] Cleanup --- src/Certify.Client/ICertifyClient.cs | 4 +-- .../Validation/CertificateEditorService.cs | 2 +- .../Controllers/AccessController.cs | 6 +---- .../Controllers/AccountsController.cs | 4 +-- .../Controllers/ControllerBase.cs | 3 +-- .../Controllers/CredentialsController.cs | 3 --- .../ManagedCertificateController.cs | 4 --- .../Controllers/ServerController.cs | 4 +-- .../Controllers/SystemController.cs | 4 +-- .../Certify.Server.Core/StatusHub.cs | 4 +-- .../CertifyManagerTests.cs | 3 ++- .../DeploymentTaskTests.cs | 2 +- .../Tests/CertificateEditorServiceTests.cs | 6 +---- .../Tests/CertifyManagerAccountTests.cs | 20 ++++++++------- .../Tests/CertifyServiceTests.cs | 25 ++++++++++--------- 15 files changed, 37 insertions(+), 57 deletions(-) diff --git a/src/Certify.Client/ICertifyClient.cs b/src/Certify.Client/ICertifyClient.cs index e4f913811..f8bd95f1b 100644 --- a/src/Certify.Client/ICertifyClient.cs +++ b/src/Certify.Client/ICertifyClient.cs @@ -25,8 +25,8 @@ public partial interface ICertifyInternalApiClient Task> PerformServiceDiagnostics(); Task> PerformManagedCertMaintenance(string id = null); - // Task PerformExport(ExportRequest exportRequest); - // Task> PerformImport(ImportRequest importRequest); + // Task PerformExport(ExportRequest exportRequest); + // Task> PerformImport(ImportRequest importRequest); Task> SetDefaultDataStore(string dataStoreId); Task> GetDataStoreProviders(); diff --git a/src/Certify.Models/Shared/Validation/CertificateEditorService.cs b/src/Certify.Models/Shared/Validation/CertificateEditorService.cs index 56e33f67d..7c5c1df70 100644 --- a/src/Certify.Models/Shared/Validation/CertificateEditorService.cs +++ b/src/Certify.Models/Shared/Validation/CertificateEditorService.cs @@ -358,7 +358,7 @@ public static ValidationResult Validate(ManagedCertificate item, SiteInfo? selec if (identifiers.Any(d => d.Value.StartsWith("*.", StringComparison.OrdinalIgnoreCase))) { - foreach (var wildcard in identifiers.Where(d => d.IdentifierType == CertIdentifierType.Dns && d.Value.StartsWith("*.",StringComparison.OrdinalIgnoreCase))) + foreach (var wildcard in identifiers.Where(d => d.IdentifierType == CertIdentifierType.Dns && d.Value.StartsWith("*.", StringComparison.OrdinalIgnoreCase))) { var rootDomain = wildcard.Value.Replace("*.", ""); // add list of identifiers where label count exceeds root domain label count diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccessController.cs b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccessController.cs index 758956746..a6882a580 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccessController.cs +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccessController.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Certify.Core.Management.Access; +using Certify.Core.Management.Access; using Certify.Management; using Certify.Models.Config.AccessControl; using Microsoft.AspNetCore.Mvc; diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccountsController.cs b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccountsController.cs index f3e339d73..8f0251706 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccountsController.cs +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccountsController.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using Certify.Management; +using Certify.Management; using Certify.Models; using Microsoft.AspNetCore.Mvc; diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/ControllerBase.cs b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/ControllerBase.cs index eeee5fd89..333eff4a6 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/ControllerBase.cs +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/ControllerBase.cs @@ -1,5 +1,4 @@ -using System; -using System.Diagnostics; +using System.Diagnostics; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/CredentialsController.cs b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/CredentialsController.cs index 5f490c263..90e53068c 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/CredentialsController.cs +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/CredentialsController.cs @@ -1,6 +1,3 @@ -using System.Collections.Generic; -using System.Threading.Tasks; - using Certify.Management; using Certify.Models.Config; using Microsoft.AspNetCore.Mvc; diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/ManagedCertificateController.cs b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/ManagedCertificateController.cs index d59f09c6b..657ed53ed 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/ManagedCertificateController.cs +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/ManagedCertificateController.cs @@ -1,7 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; using Certify.Config; using Certify.Management; using Certify.Models; diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/ServerController.cs b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/ServerController.cs index 5e7fce563..493f06868 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/ServerController.cs +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/ServerController.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using Certify.Management; +using Certify.Management; using Certify.Models; using Microsoft.AspNetCore.Mvc; diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/SystemController.cs b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/SystemController.cs index 91917727a..b0e165664 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/SystemController.cs +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/SystemController.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using Certify.Management; +using Certify.Management; using Certify.Models; using Certify.Models.Config; using Certify.Models.Config.Migration; diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/StatusHub.cs b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/StatusHub.cs index 1e94ce57d..27a780fc8 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/StatusHub.cs +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/StatusHub.cs @@ -1,6 +1,4 @@ -using System; -using System.Diagnostics; -using System.Threading.Tasks; +using System.Diagnostics; using Certify.Models; using Certify.Providers; using Microsoft.AspNetCore.SignalR; diff --git a/src/Certify.Tests/Certify.Core.Tests.Integration/CertifyManagerTests.cs b/src/Certify.Tests/Certify.Core.Tests.Integration/CertifyManagerTests.cs index d5052a534..14f661f6c 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Integration/CertifyManagerTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Integration/CertifyManagerTests.cs @@ -19,7 +19,8 @@ public CertifyManagerTests() _certifyManager.Init().Wait(); } - [TestCleanup] public void Cleanup() + [TestCleanup] + public void Cleanup() { _certifyManager.Dispose(); } diff --git a/src/Certify.Tests/Certify.Core.Tests.Integration/DeploymentTaskTests.cs b/src/Certify.Tests/Certify.Core.Tests.Integration/DeploymentTaskTests.cs index 5be15834e..3640d938a 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Integration/DeploymentTaskTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Integration/DeploymentTaskTests.cs @@ -33,7 +33,7 @@ public DeploymentTaskTests() certifyManager.Init().Wait(); } - [TestCleanup] + [TestCleanup] public void Cleanup() { certifyManager?.Dispose(); diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/CertificateEditorServiceTests.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/CertificateEditorServiceTests.cs index 4ea03b1d1..795d566c1 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/CertificateEditorServiceTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/CertificateEditorServiceTests.cs @@ -1,10 +1,6 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using Certify.Models; -using Certify.Models.Providers; +using Certify.Models; using Certify.Models.Shared.Validation; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; namespace Certify.Core.Tests.Unit { diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/CertifyManagerAccountTests.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/CertifyManagerAccountTests.cs index 44ca2ef33..f9fd0a4f2 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/CertifyManagerAccountTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/CertifyManagerAccountTests.cs @@ -136,7 +136,8 @@ private static async Task BootstrapStepCa() // Read step-ca fingerprint from config file var stepCaConfigJson = JsonReader.ReadFile($"{_winRunnerTempDir}\\config\\defaults.json"); stepCaFingerprint = stepCaConfigJson.fingerprint; - } else + } + else { var stepCaConfigBytes = await _caContainer.ReadFileAsync("/home/step/config/defaults.json"); var stepCaConfigJson = JsonReader.ReadBytes(stepCaConfigBytes); @@ -155,7 +156,8 @@ private static async Task StartStepCaContainer(bool runningWindowsDockerEngine) { if (_isWindows && runningWindowsDockerEngine) { - if (!Directory.Exists(_winRunnerTempDir)) { + if (!Directory.Exists(_winRunnerTempDir)) + { Directory.CreateDirectory(_winRunnerTempDir); } @@ -175,7 +177,7 @@ private static async Task StartStepCaContainer(bool runningWindowsDockerEngine) .WithWaitStrategy(Wait.ForUnixContainer().UntilMessageIsLogged($"Serving HTTPS on :{_caPort} ...")) // Build the container configuration. .Build(); - } + } else { // Create new volume for step-ca container @@ -239,17 +241,17 @@ public static T ReadBytes(byte[] bytes) private class StepCaConfig { [JsonProperty(PropertyName = "ca-url")] - public string ca_url; + public string ca_url = string.Empty; [JsonProperty(PropertyName = "ca-config")] - public string ca_config; - public string fingerprint; - public string root; + public string ca_config = string.Empty; + public string fingerprint = string.Empty; + public string root = string.Empty; } private static CommandOutput RunCommand(string program, string args, string description = null, int timeoutMS = Timeout.Infinite) { if (description == null) { description = string.Concat(program, " ", args); } - + var output = ""; var errorOutput = ""; @@ -290,7 +292,7 @@ private static CommandOutput RunCommand(string program, string args, string desc process.BeginOutputReadLine(); process.BeginErrorReadLine(); - process.WaitForExit(timeoutMS); + process.WaitForExit(timeoutMS); } catch (Exception exp) { diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/CertifyServiceTests.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/CertifyServiceTests.cs index 92a342980..e87eb8db7 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/CertifyServiceTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/CertifyServiceTests.cs @@ -1,15 +1,15 @@ -using Certify.Models; -using Certify.Models.Config; -using Certify.Shared; -using Medallion.Shell; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Newtonsoft.Json; -using System; +using System; using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Text.RegularExpressions; using System.Threading.Tasks; +using Certify.Models; +using Certify.Models.Config; +using Certify.Shared; +using Medallion.Shell; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Newtonsoft.Json; namespace Certify.Core.Tests.Unit { @@ -20,16 +20,17 @@ public class CertifyServiceTests private HttpClient _httpClient; private string serviceUri; - public CertifyServiceTests() { + public CertifyServiceTests() + { var serviceConfig = SharedUtils.ServiceConfigManager.GetAppServiceConfig(); serviceUri = $"{(serviceConfig.UseHTTPS ? "https" : "http")}://{serviceConfig.Host}:{serviceConfig.Port}"; var httpHandler = new HttpClientHandler { UseDefaultCredentials = true }; _httpClient = new HttpClient(httpHandler); _httpClient.DefaultRequestHeaders.Add("User-Agent", "Certify/App"); - _httpClient.BaseAddress = new Uri(serviceUri+"/api/"); + _httpClient.BaseAddress = new Uri(serviceUri + "/api/"); } - private async Task StartCertifyService(string args = "") + private async Task StartCertifyService(string args = "") { Command certifyService; if (args == "") @@ -98,7 +99,7 @@ public async Task TestCertifyServiceAppVersionRoute() var versionRes = JsonConvert.DeserializeObject(versionResStr); Assert.AreEqual(HttpStatusCode.OK, versionRawRes.StatusCode, $"Unexpected status code from GET {versionRawRes.RequestMessage.RequestUri.AbsoluteUri}"); - StringAssert.Matches(versionRes, new Regex(@"^(\d+\.)?(\d+\.)?(\d+\.)?(\*|\d+)$"), $"Unexpected response from GET {versionRawRes.RequestMessage.RequestUri.AbsoluteUri} : {versionResStr}"); + StringAssert.Matches(versionRes, new Regex(@"^(\d+\.)?(\d+\.)?(\d+\.)?(\*|\d+)$"), $"Unexpected response from GET {versionRawRes.RequestMessage.RequestUri.AbsoluteUri} : {versionResStr}"); } finally { @@ -116,7 +117,7 @@ public async Task TestCertifyServiceUpdateCheckRoute() var updatesRawRes = await _httpClient.GetAsync("system/updatecheck"); var updateRawResStr = await updatesRawRes.Content.ReadAsStringAsync(); var updateRes = JsonConvert.DeserializeObject(updateRawResStr); - + Assert.AreEqual(HttpStatusCode.OK, updatesRawRes.StatusCode, $"Unexpected status code from GET {updatesRawRes.RequestMessage.RequestUri.AbsoluteUri}"); Assert.IsFalse(updateRes.MustUpdate); Assert.IsFalse(updateRes.IsNewerVersion); From 9043adf8a831523ea167f08e9c8abc174c5ffbe1 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Mon, 8 Jan 2024 17:05:43 +0800 Subject: [PATCH 110/328] Cleanup --- src/Certify.Core/Management/CertifyManager/CertifyManager.cs | 2 +- .../Controllers/v1/SystemController.cs | 3 +-- .../Certify.Server.Core}/appsettings.Development.json | 0 .../Certify.Server.Core}/appsettings.json | 0 4 files changed, 2 insertions(+), 3 deletions(-) rename src/Certify.Server/{Certify.Service.Worker/Certify.Service.Worker => Certify.Server.Core/Certify.Server.Core}/appsettings.Development.json (100%) rename src/Certify.Server/{Certify.Service.Worker/Certify.Service.Worker => Certify.Server.Core/Certify.Server.Core}/appsettings.json (100%) diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.cs index 7b5dbd4de..505aecd3a 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.cs @@ -537,7 +537,7 @@ public async Task PerformRenewalTasks() private void Cleanup() { ManagedCertificateLog.DisposeLoggers(); - if(_tc != null) + if (_tc != null) { _tc.Dispose(); } diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/SystemController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/SystemController.cs index 09d69d291..8f5ea4adf 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/SystemController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/SystemController.cs @@ -1,4 +1,4 @@ -using Certify.Client; +using Certify.Client; using Microsoft.AspNetCore.Mvc; namespace Certify.Server.Api.Public.Controllers @@ -46,7 +46,6 @@ public async Task GetSystemVersion() /// [HttpGet] [Route("health")] - [Route("/health")] public async Task GetHealth() { var serviceAvailable = false; diff --git a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/appsettings.Development.json b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/appsettings.Development.json similarity index 100% rename from src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/appsettings.Development.json rename to src/Certify.Server/Certify.Server.Core/Certify.Server.Core/appsettings.Development.json diff --git a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/appsettings.json b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/appsettings.json similarity index 100% rename from src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/appsettings.json rename to src/Certify.Server/Certify.Server.Core/Certify.Server.Core/appsettings.json From fc7254ab85e99371d5537e4cdbad81367884ad1a Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Mon, 8 Jan 2024 17:07:21 +0800 Subject: [PATCH 111/328] Package updates --- src/Certify.Client/Certify.Client.csproj | 2 +- src/Certify.Core/Certify.Core.csproj | 2 +- .../DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj | 2 +- .../Certify.Server.Api.Public.csproj | 4 ++-- .../Certify.Server.Core/Certify.Server.Core.csproj | 5 ++++- .../Certify.Core.Tests.Integration.csproj | 2 +- .../Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj | 2 +- .../Certify.Service.Tests.Integration.csproj | 2 +- 8 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/Certify.Client/Certify.Client.csproj b/src/Certify.Client/Certify.Client.csproj index d05161e55..ab9d2fd2c 100644 --- a/src/Certify.Client/Certify.Client.csproj +++ b/src/Certify.Client/Certify.Client.csproj @@ -20,7 +20,7 @@ - + diff --git a/src/Certify.Core/Certify.Core.csproj b/src/Certify.Core/Certify.Core.csproj index 815d2c69b..c3badb08b 100644 --- a/src/Certify.Core/Certify.Core.csproj +++ b/src/Certify.Core/Certify.Core.csproj @@ -83,7 +83,7 @@ 1701;1702;CA1068 - + diff --git a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj index 172a6a417..b1a86b1b9 100644 --- a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj +++ b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj index df15f2b92..3ab93a8fa 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj +++ b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj @@ -1,4 +1,4 @@ - + net8.0 enable @@ -15,7 +15,7 @@ - + diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj index 2d8319d72..7c6eb1f8e 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj @@ -4,12 +4,15 @@ 3648c5f3-f642-441e-979e-d4624cd39e49 Linux AnyCPU + enable - + + + diff --git a/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj b/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj index 7f8024375..0dd1e195e 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj @@ -96,7 +96,7 @@ - + diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj index bf556a61c..0fc71d252 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj @@ -146,7 +146,7 @@ - + diff --git a/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj b/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj index 14c598d36..bde2a31f4 100644 --- a/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj @@ -80,7 +80,7 @@ - + From 384efcfbe2a2bdc35e11e971a7e2f32fe122ac9b Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Mon, 8 Jan 2024 17:11:10 +0800 Subject: [PATCH 112/328] Implement Aspire in dev and remove Certify.Service.Worker --- .../Certify.Aspire.AppHost.csproj | 20 +++ .../Certify.Aspire.AppHost/Program.cs | 7 + .../Properties/launchSettings.json | 16 ++ .../appsettings.Development.json | 8 + .../Certify.Aspire.AppHost/appsettings.json | 9 + .../Certify.Aspire.ServiceDefaults.csproj | 24 +++ .../Extensions.cs | 119 +++++++++++++ src/Certify.Client/CertifyServiceClient.cs | 5 + .../APITestBase.cs | 32 +++- .../Certify.Server.Api.Public.Tests.csproj | 2 +- .../Certify.Server.Api.Public.csproj | 7 +- .../Certify.Server.Api.Public/Program.cs | 24 ++- .../Certify.Server.Api.Public/Startup.cs | 84 +++++---- .../Certify.Server.Core.csproj | 50 +++--- .../Certify.Server.Core/Program.cs | 30 ++-- .../Properties/launchSettings.json | 47 ++++- .../Certify.Server.Core/Startup.cs | 33 ++-- .../Certify.Service.Worker/.dockerignore | 25 --- .../Certify.Service.Worker.sln | 25 --- .../Certify.Service.Worker.csproj | 23 --- .../Certify.Service.Worker/Dockerfile | 29 ---- .../Certify.Service.Worker/Program.cs | 162 ------------------ .../Properties/launchSettings.json | 49 ------ .../Certify.Service.Worker/Startup.cs | 12 -- .../Certify.Service.Worker/Worker.cs | 50 ------ .../certifytheweb.service | 14 -- .../Certify.Service.Worker/readme.md | 81 --------- 27 files changed, 423 insertions(+), 564 deletions(-) create mode 100644 src/Certify.Aspire/Certify.Aspire.AppHost/Certify.Aspire.AppHost.csproj create mode 100644 src/Certify.Aspire/Certify.Aspire.AppHost/Program.cs create mode 100644 src/Certify.Aspire/Certify.Aspire.AppHost/Properties/launchSettings.json create mode 100644 src/Certify.Aspire/Certify.Aspire.AppHost/appsettings.Development.json create mode 100644 src/Certify.Aspire/Certify.Aspire.AppHost/appsettings.json create mode 100644 src/Certify.Aspire/Certify.Aspire.ServiceDefaults/Certify.Aspire.ServiceDefaults.csproj create mode 100644 src/Certify.Aspire/Certify.Aspire.ServiceDefaults/Extensions.cs delete mode 100644 src/Certify.Server/Certify.Service.Worker/.dockerignore delete mode 100644 src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker.sln delete mode 100644 src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Certify.Service.Worker.csproj delete mode 100644 src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Dockerfile delete mode 100644 src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Program.cs delete mode 100644 src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Properties/launchSettings.json delete mode 100644 src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Startup.cs delete mode 100644 src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Worker.cs delete mode 100644 src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/certifytheweb.service delete mode 100644 src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/readme.md diff --git a/src/Certify.Aspire/Certify.Aspire.AppHost/Certify.Aspire.AppHost.csproj b/src/Certify.Aspire/Certify.Aspire.AppHost/Certify.Aspire.AppHost.csproj new file mode 100644 index 000000000..11f3a87c3 --- /dev/null +++ b/src/Certify.Aspire/Certify.Aspire.AppHost/Certify.Aspire.AppHost.csproj @@ -0,0 +1,20 @@ + + + + Exe + net8.0 + enable + enable + true + + + + + + + + + + + + diff --git a/src/Certify.Aspire/Certify.Aspire.AppHost/Program.cs b/src/Certify.Aspire/Certify.Aspire.AppHost/Program.cs new file mode 100644 index 000000000..e88c0aac4 --- /dev/null +++ b/src/Certify.Aspire/Certify.Aspire.AppHost/Program.cs @@ -0,0 +1,7 @@ +var builder = DistributedApplication.CreateBuilder(args); + +builder.AddProject("certify.server.core"); + +builder.AddProject("certify.server.api.public"); + +builder.Build().Run(); diff --git a/src/Certify.Aspire/Certify.Aspire.AppHost/Properties/launchSettings.json b/src/Certify.Aspire/Certify.Aspire.AppHost/Properties/launchSettings.json new file mode 100644 index 000000000..31b555470 --- /dev/null +++ b/src/Certify.Aspire/Certify.Aspire.AppHost/Properties/launchSettings.json @@ -0,0 +1,16 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:15155", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:16075" + } + } + } +} diff --git a/src/Certify.Aspire/Certify.Aspire.AppHost/appsettings.Development.json b/src/Certify.Aspire/Certify.Aspire.AppHost/appsettings.Development.json new file mode 100644 index 000000000..0c208ae91 --- /dev/null +++ b/src/Certify.Aspire/Certify.Aspire.AppHost/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/src/Certify.Aspire/Certify.Aspire.AppHost/appsettings.json b/src/Certify.Aspire/Certify.Aspire.AppHost/appsettings.json new file mode 100644 index 000000000..31c092aa4 --- /dev/null +++ b/src/Certify.Aspire/Certify.Aspire.AppHost/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "Aspire.Hosting.Dcp": "Warning" + } + } +} diff --git a/src/Certify.Aspire/Certify.Aspire.ServiceDefaults/Certify.Aspire.ServiceDefaults.csproj b/src/Certify.Aspire/Certify.Aspire.ServiceDefaults/Certify.Aspire.ServiceDefaults.csproj new file mode 100644 index 000000000..dcf9ca505 --- /dev/null +++ b/src/Certify.Aspire/Certify.Aspire.ServiceDefaults/Certify.Aspire.ServiceDefaults.csproj @@ -0,0 +1,24 @@ + + + + Library + net8.0 + enable + enable + true + + + + + + + + + + + + + + + + diff --git a/src/Certify.Aspire/Certify.Aspire.ServiceDefaults/Extensions.cs b/src/Certify.Aspire/Certify.Aspire.ServiceDefaults/Extensions.cs new file mode 100644 index 000000000..d2f1d578c --- /dev/null +++ b/src/Certify.Aspire/Certify.Aspire.ServiceDefaults/Extensions.cs @@ -0,0 +1,119 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.Extensions.Logging; +using OpenTelemetry.Logs; +using OpenTelemetry.Metrics; +using OpenTelemetry.Trace; + +namespace Microsoft.Extensions.Hosting; + +public static class Extensions +{ + public static IHostApplicationBuilder AddServiceDefaults(this IHostApplicationBuilder builder) + { + builder.ConfigureOpenTelemetry(); + + builder.AddDefaultHealthChecks(); + + builder.Services.AddServiceDiscovery(); + + builder.Services.ConfigureHttpClientDefaults(http => + { + // Turn on resilience by default + http.AddStandardResilienceHandler(); + + // Turn on service discovery by default + http.UseServiceDiscovery(); + }); + + return builder; + } + + public static IHostApplicationBuilder ConfigureOpenTelemetry(this IHostApplicationBuilder builder) + { + builder.Logging.AddOpenTelemetry(logging => + { + logging.IncludeFormattedMessage = true; + logging.IncludeScopes = true; + }); + + builder.Services.AddOpenTelemetry() + .WithMetrics(metrics => + { + metrics.AddRuntimeInstrumentation() + .AddBuiltInMeters(); + }) + .WithTracing(tracing => + { + if (builder.Environment.IsDevelopment()) + { + // We want to view all traces in development + tracing.SetSampler(new AlwaysOnSampler()); + } + + tracing.AddAspNetCoreInstrumentation() + .AddGrpcClientInstrumentation() + .AddHttpClientInstrumentation(); + }); + + builder.AddOpenTelemetryExporters(); + + return builder; + } + + private static IHostApplicationBuilder AddOpenTelemetryExporters(this IHostApplicationBuilder builder) + { + var useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]); + + if (useOtlpExporter) + { + builder.Services.Configure(logging => logging.AddOtlpExporter()); + builder.Services.ConfigureOpenTelemetryMeterProvider(metrics => metrics.AddOtlpExporter()); + builder.Services.ConfigureOpenTelemetryTracerProvider(tracing => tracing.AddOtlpExporter()); + } + + // Uncomment the following lines to enable the Prometheus exporter (requires the OpenTelemetry.Exporter.Prometheus.AspNetCore package) + // builder.Services.AddOpenTelemetry() + // .WithMetrics(metrics => metrics.AddPrometheusExporter()); + + // Uncomment the following lines to enable the Azure Monitor exporter (requires the Azure.Monitor.OpenTelemetry.Exporter package) + // builder.Services.AddOpenTelemetry() + // .UseAzureMonitor(); + + return builder; + } + + public static IHostApplicationBuilder AddDefaultHealthChecks(this IHostApplicationBuilder builder) + { + builder.Services.AddHealthChecks() + // Add a default liveness check to ensure app is responsive + .AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]); + + return builder; + } + + public static WebApplication MapDefaultEndpoints(this WebApplication app) + { + // Uncomment the following line to enable the Prometheus endpoint (requires the OpenTelemetry.Exporter.Prometheus.AspNetCore package) + // app.MapPrometheusScrapingEndpoint(); + + // All health checks must pass for app to be considered ready to accept traffic after starting + app.MapHealthChecks("/health"); + + // Only health checks tagged with the "live" tag must pass for app to be considered alive + app.MapHealthChecks("/alive", new HealthCheckOptions + { + Predicate = r => r.Tags.Contains("live") + }); + + return app; + } + + private static MeterProviderBuilder AddBuiltInMeters(this MeterProviderBuilder meterProviderBuilder) => + meterProviderBuilder.AddMeter( + "Microsoft.AspNetCore.Hosting", + "Microsoft.AspNetCore.Server.Kestrel", + "System.Net.Http"); +} diff --git a/src/Certify.Client/CertifyServiceClient.cs b/src/Certify.Client/CertifyServiceClient.cs index 0760be343..d09128a3e 100644 --- a/src/Certify.Client/CertifyServiceClient.cs +++ b/src/Certify.Client/CertifyServiceClient.cs @@ -156,5 +156,10 @@ public ServerConnection GetConnectionInfo() { return _connectionConfig; } + + public string GetStatusHubUri() + { + return _statusHubUri; + } } } diff --git a/src/Certify.Server/Certify.Server.Api.Public.Tests/APITestBase.cs b/src/Certify.Server/Certify.Server.Api.Public.Tests/APITestBase.cs index 586e0c34b..8fb166e7c 100644 --- a/src/Certify.Server/Certify.Server.Api.Public.Tests/APITestBase.cs +++ b/src/Certify.Server/Certify.Server.Api.Public.Tests/APITestBase.cs @@ -1,9 +1,11 @@ using System; +using System.IO; using System.Linq; using System.Net.Http; using System.Threading.Tasks; using Certify.Models.API; using Certify.Server.Api.Public.Controllers; +using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.Configuration; @@ -31,7 +33,7 @@ public class APITestBase public static void Init(TestContext context) { - // setup public API service and backend service worker API + // setup public API service and backend service var config = new ConfigurationBuilder() .SetBasePath(AppContext.BaseDirectory) @@ -60,7 +62,33 @@ public static void Init(TestContext context) _clientWithAnonymousAccess = _server.CreateClient(); _clientWithAuthorizedAccess = _server.CreateClient(); - Worker.Program.CreateHostBuilder(new string[] { }).Build().StartAsync(); + // CreateCoreServer(); + } + + private static void CreateCoreServer() + { + // configure and start a custom instance of the core server for the public API to talk to + var builder = WebApplication.CreateBuilder(); + + var coreServerStartup = new Certify.Server.Core.Startup(builder.Configuration); + + if (File.Exists(Path.Join(AppContext.BaseDirectory, "appsettings.worker.test.json"))) + { + builder.Configuration.AddJsonFile("appsettings.worker.test.json"); + builder.Configuration.AddUserSecrets(typeof(APITestBase).Assembly); // for worker pfx details + } + + builder.Logging.ClearProviders(); + builder.Logging.AddConsole(); + + coreServerStartup.ConfigureServices(builder.Services); + + var coreServerApp = builder.Build(); + + coreServerStartup.Configure(coreServerApp, builder.Environment); + + coreServerApp.StartAsync(); + } public async Task PerformAuth() diff --git a/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj b/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj index 6854064ce..ac9c3fbc3 100644 --- a/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj +++ b/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj @@ -37,7 +37,7 @@ - + diff --git a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj index 3ab93a8fa..f9ead6e91 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj +++ b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj @@ -1,4 +1,4 @@ - + net8.0 enable @@ -11,6 +11,10 @@ False + $(DefineConstants);ASPIRE + + + $(DefineConstants);ASPIRE @@ -22,6 +26,7 @@ + \ No newline at end of file diff --git a/src/Certify.Server/Certify.Server.Api.Public/Program.cs b/src/Certify.Server/Certify.Server.Api.Public/Program.cs index 7ff00a0c1..0780c2333 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Program.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Program.cs @@ -1,22 +1,36 @@ - -using Certify.Server.API; +using Certify.Server.API; var builder = WebApplication.CreateBuilder(args); #if ASPIRE - builder.AddServiceDefaults(); +builder.AddServiceDefaults(); #endif var startup = new Startup(builder.Configuration); -startup.ConfigureServices(builder.Services); +var results = startup.ConfigureServicesWithResults(builder.Services); var app = builder.Build(); +// log any relevant startup messages encountered during configure services +foreach (var result in results) +{ + if (result.IsSuccess) + { + app.Logger.LogInformation($"Startup: {result.Message}"); + } + else + { + app.Logger.LogError($"Startup: {result.Message}"); + } +} + #if ASPIRE - app.MapDefaultEndpoints(); +app.MapDefaultEndpoints(); #endif startup.Configure(app, builder.Environment); +app.Lifetime.ApplicationStarted.Register(async () => await startup.SetupStatusHubConnection(app)); + app.Run(); diff --git a/src/Certify.Server/Certify.Server.Api.Public/Startup.cs b/src/Certify.Server/Certify.Server.Api.Public/Startup.cs index f3dae0e94..675df3c9a 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Startup.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Startup.cs @@ -1,10 +1,12 @@ -using System.Reflection; +using System.Reflection; +using Certify.Client; +using Certify.Models.Config; using Certify.Server.Api.Public.Middleware; -using Certify.Shared.Core.Management; using Certify.SharedUtils; using Microsoft.AspNetCore.ResponseCompression; using Microsoft.AspNetCore.SignalR; using Microsoft.OpenApi.Models; +using Polly; namespace Certify.Server.API { @@ -29,12 +31,18 @@ public Startup(IConfiguration configuration) ///
public IConfiguration Configuration { get; } + public void ConfigureServices(IServiceCollection services) + { + _ = ConfigureServicesWithResults(services); + } + /// /// Configure services for use by the API /// /// - public void ConfigureServices(IServiceCollection services) + public List ConfigureServicesWithResults(IServiceCollection services) { + var results = new List(); services .AddTokenAuthentication(Configuration) @@ -142,45 +150,23 @@ public void ConfigureServices(IServiceCollection services) var internalServiceClient = new Client.CertifyServiceClient(configManager, backendServiceConnectionConfig); - var attempts = 3; - while (attempts > 0) - { - try - { - internalServiceClient.ConnectStatusStreamAsync().Wait(); - break; - } - catch - { - attempts--; - - if (attempts == 0) - { - throw; - } - else - { - Task.Delay(2000).Wait(); // wait for service to start - } - } - } - internalServiceClient.OnMessageFromService += InternalServiceClient_OnMessageFromService; internalServiceClient.OnRequestProgressStateUpdated += InternalServiceClient_OnRequestProgressStateUpdated; internalServiceClient.OnManagedCertificateUpdated += InternalServiceClient_OnManagedCertificateUpdated; services.AddSingleton(typeof(Certify.Client.ICertifyInternalApiClient), internalServiceClient); + + return results; } /// /// Configure the http request pipeline /// - /// - /// public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { - var statusHubContext = app.ApplicationServices.GetService(typeof(IHubContext)) as IHubContext; + var statusHubContext = app.ApplicationServices.GetRequiredService>(); + if (statusHubContext == null) { throw new Exception("Status Hub not registered"); @@ -202,8 +188,6 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) p.AllowAnyOrigin() .AllowAnyMethod() .AllowAnyHeader(); - //.AllowCredentials(); - }); app.UseAuthentication(); @@ -230,6 +214,44 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) #endif } + public async Task SetupStatusHubConnection(WebApplication app) + { + var internalServiceClient = app.Services.GetRequiredService() as CertifyServiceClient; + + if (internalServiceClient == null) + { + app.Logger.LogError($"Unable to resolve internal service client. Cannot connect status stream."); + return; + } + else + { + + var attempts = 3; + var connected = false; + while (attempts > 0 && !connected) + { + try + { + await internalServiceClient.ConnectStatusStreamAsync(); + connected = true; + } + catch + { + attempts--; + + if (attempts == 0) + { + app.Logger.LogError($"Unable to connect to service SignalR stream at {internalServiceClient?.GetStatusHubUri()}."); + } + else + { + Task.Delay(2000).Wait(); // wait for service to start + } + } + } + } + } + private StatusHubReporting _statusReporting; private void InternalServiceClient_OnManagedCertificateUpdated(Models.ManagedCertificate obj) diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj index 7c6eb1f8e..d5def1de3 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj @@ -1,30 +1,32 @@ - - net8.0 - 3648c5f3-f642-441e-979e-d4624cd39e49 - Linux - AnyCPU + + net8.0 + 3648c5f3-f642-441e-979e-d4624cd39e49 + Linux + AnyCPU enable - - - - + + + + - + - - - - - - - - - - - - - - + + + + + +
+ + + + + + + + + + \ No newline at end of file diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Program.cs b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Program.cs index 3342ced90..b8586329d 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Program.cs +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Program.cs @@ -1,20 +1,14 @@ -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Hosting; + +var builder = WebApplication.CreateBuilder(args); -namespace Certify.Server.Core -{ - public class Program - { - public static void Main(string[] args) - { - CreateHostBuilder(args).Build().Run(); - } +builder.AddServiceDefaults(); - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }); - } -} +var startup = new Certify.Server.Core.Startup(builder.Configuration); + +startup.ConfigureServices(builder.Services); + +var app = builder.Build(); + +startup.Configure(app, builder.Environment); + +app.Run(); diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Properties/launchSettings.json b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Properties/launchSettings.json index 2db09550c..397156ba7 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Properties/launchSettings.json +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Properties/launchSettings.json @@ -1,8 +1,51 @@ { + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:9695" + } + }, + "profiles": { + "Certify.Server.Core": { "commandName": "Project", - "applicationUrl": "http://localhost:9695" + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "ASPNETCORE_URLS": "http://localhost:9695" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "http://localhost:9695/docs", + "iisExpress": { + "applicationUrl": "http://localhost:9695" + }, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Docker": { + "commandName": "Docker", + "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/docs", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "ASPNETCORE_URLS": "http://0.0.0.0:9695" + }, + "publishAllPorts": true, + "httpPort": 9695 + }, + "WSL": { + "commandName": "WSL2", + "launchBrowser": true, + "launchUrl": "http://localhost:9695/docs", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } } } -} \ No newline at end of file +} diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Startup.cs b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Startup.cs index 072480bc2..f619b52da 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Startup.cs +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Startup.cs @@ -1,10 +1,6 @@ -using System.Collections.Generic; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; +using Certify.Management; +using Microsoft.AspNetCore.ResponseCompression; using Microsoft.AspNetCore.SignalR; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; using Microsoft.OpenApi.Models; namespace Certify.Server.Core @@ -27,6 +23,11 @@ public void ConfigureServices(IServiceCollection services) .AddSignalR() .AddMessagePackProtocol(); + services.AddResponseCompression(opts => + { + opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(new[] { "application/octet-stream", "application/json" }); + }); + services.AddCors(options => { options.AddDefaultPolicy( @@ -39,6 +40,8 @@ public void ConfigureServices(IServiceCollection services) }); #if DEBUG + services.AddEndpointsApiExplorer(); + // Register the Swagger generator, defining 1 or more Swagger documents // https://docs.microsoft.com/en-us/aspnet/core/tutorials/getting-started-with-swashbuckle?view=aspnetcore-3.1&tabs=visual-studio services.AddSwaggerGen(c => @@ -81,7 +84,6 @@ public void ConfigureServices(IServiceCollection services) #endif // inject instance of certify manager - var certifyManager = new Management.CertifyManager(); certifyManager.Init().Wait(); @@ -100,8 +102,21 @@ public void ConfigureServices(IServiceCollection services) } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IHubContext statusHubContext, Management.ICertifyManager certifyManager) + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { + var statusHubContext = app.ApplicationServices.GetRequiredService>(); + if (statusHubContext == null) + { + throw new Exception("Status Hub not registered"); + } + + var certifyManager = app.ApplicationServices.GetRequiredService(typeof(ICertifyManager)) as CertifyManager; + + if (certifyManager == null) + { + throw new Exception("Certify Manager not registered"); + } + if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); @@ -122,7 +137,6 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IHubCont // set status report context provider certifyManager.SetStatusReporting(new Service.StatusHubReporting(statusHubContext)); - // var useHttps = bool.Parse(Configuration["API:Service:UseHttps"]); if (useHttps) @@ -140,7 +154,6 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IHubCont { endpoints.MapHub("/api/status"); endpoints.MapControllers(); - }); } diff --git a/src/Certify.Server/Certify.Service.Worker/.dockerignore b/src/Certify.Server/Certify.Service.Worker/.dockerignore deleted file mode 100644 index 3729ff0cd..000000000 --- a/src/Certify.Server/Certify.Service.Worker/.dockerignore +++ /dev/null @@ -1,25 +0,0 @@ -**/.classpath -**/.dockerignore -**/.env -**/.git -**/.gitignore -**/.project -**/.settings -**/.toolstarget -**/.vs -**/.vscode -**/*.*proj.user -**/*.dbmdl -**/*.jfm -**/azds.yaml -**/bin -**/charts -**/docker-compose* -**/Dockerfile* -**/node_modules -**/npm-debug.log -**/obj -**/secrets.dev.yaml -**/values.dev.yaml -LICENSE -README.md \ No newline at end of file diff --git a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker.sln b/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker.sln deleted file mode 100644 index 76482fa41..000000000 --- a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker.sln +++ /dev/null @@ -1,25 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.30114.128 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Certify.Service.Worker", "Certify.Service.Worker\Certify.Service.Worker.csproj", "{7EA8644D-580D-49CD-BD5B-CFDA23A20EC2}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {7EA8644D-580D-49CD-BD5B-CFDA23A20EC2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7EA8644D-580D-49CD-BD5B-CFDA23A20EC2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7EA8644D-580D-49CD-BD5B-CFDA23A20EC2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7EA8644D-580D-49CD-BD5B-CFDA23A20EC2}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {CFDF0060-BB3D-49C8-8A8D-9F0813953142} - EndGlobalSection -EndGlobal diff --git a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Certify.Service.Worker.csproj b/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Certify.Service.Worker.csproj deleted file mode 100644 index 98a7f08a9..000000000 --- a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Certify.Service.Worker.csproj +++ /dev/null @@ -1,23 +0,0 @@ - - - net8.0 - dotnet-Certify.Service.Worker-347A036F-C1EA-4D32-A163-DCB38C3CA53E - Linux - -v certifydata:/usr/share/Certify - ..\..\.. - - - portable - true - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Dockerfile b/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Dockerfile deleted file mode 100644 index 78f967792..000000000 --- a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Dockerfile +++ /dev/null @@ -1,29 +0,0 @@ -#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging. - -FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base - -# grant write to store settings path before switching to app user -RUN mkdir /usr/share/certify && chown -R app:app /usr/share/certify - -USER app -WORKDIR /app -EXPOSE 8080 -EXPOSE 8081 - -FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build -ARG BUILD_CONFIGURATION=Release -WORKDIR /src -COPY ["Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Certify.Service.Worker.csproj", "Certify.Server/Certify.Service.Worker/Certify.Service.Worker/"] -RUN dotnet restore "./Certify.Server/Certify.Service.Worker/Certify.Service.Worker/./Certify.Service.Worker.csproj" -COPY . . -WORKDIR "/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker" -RUN dotnet build "./Certify.Service.Worker.csproj" -c $BUILD_CONFIGURATION -o /app/build - -FROM build AS publish -ARG BUILD_CONFIGURATION=Release -RUN dotnet publish "./Certify.Service.Worker.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false - -FROM base AS final -WORKDIR /app -COPY --from=publish /app/publish . -ENTRYPOINT ["dotnet", "Certify.Service.Worker.dll"] diff --git a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Program.cs b/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Program.cs deleted file mode 100644 index b1169602b..000000000 --- a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Program.cs +++ /dev/null @@ -1,162 +0,0 @@ -using System; -using System.IO; -using System.Net; -using System.Reflection; -using System.Runtime.InteropServices; -using System.Security.Cryptography.X509Certificates; -using Certify.Server.Core; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Server.Kestrel.Https; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; - -namespace Certify.Service.Worker -{ - public class Program - { - public static void Main(string[] args) - { - CreateHostBuilder(args).Build().Run(); - } - - public static IHostBuilder CreateHostBuilder(string[] args) - { - - var builder = Host.CreateDefaultBuilder(args) - .ConfigureAppConfiguration((context, builder) => - { - // when running within an integration test optionally load test config - if (File.Exists(Path.Join(AppContext.BaseDirectory, "appsettings.worker.test.json"))) - { - builder.AddJsonFile("appsettings.worker.test.json"); - builder.AddUserSecrets(typeof(Program).GetTypeInfo().Assembly); // for worker pfx details) - } - }) - .ConfigureLogging(logging => - { - logging.ClearProviders(); - logging.AddConsole(); - - }); - - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - builder.UseSystemd(); - } - - builder.ConfigureServices((hostContext, services) => - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - services.AddWindowsService(options => - options.ServiceName = "Certify Certificate Manager Background Service" - - ); - } - - services.AddHostedService(); - }) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.ConfigureKestrel(serverOptions => - { - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - serverOptions.UseSystemd(); - } - - // configure https listener, cert path and pwd can come either from an environment variable, usersecrets or appsettings. - // configuration precedence is secrets first, https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-5.0#default - var configuration = (IConfiguration)serverOptions.ApplicationServices.GetService(typeof(IConfiguration)); - - var useHttps = bool.Parse(configuration["API:Service:UseHttps"]); - - // default IP to localhost then specify from configuration - var ipSelection = configuration["API:Service:BindingIP"]; - var ipBinding = IPAddress.Loopback; - - if (ipSelection != null) - { - if (ipSelection.ToLower() == "loopback") - { - ipBinding = IPAddress.Loopback; - } - else if (ipSelection.ToLower() == "any") - { - ipBinding = IPAddress.Any; - } - else - { - ipBinding = IPAddress.Parse(ipSelection); - } - } - - if (useHttps) - { - - var certPassword = Environment.GetEnvironmentVariable("ASPNETCORE_Kestrel__Certificates__Development__Password"); - var certPath = Environment.GetEnvironmentVariable("ASPNETCORE_Kestrel__Certificates__Development__Path"); - - // if not yet defined load config from usersecrets (development env only) or appsettings - if (certPassword == null) - { - certPassword = configuration["Kestrel:Certificates:Default:Password"]; - } - - if (certPath == null) - { - certPath = configuration["Kestrel:Certificates:Default:Path"]; - } - - try - { - var certificate = new X509Certificate2(certPath, certPassword); - - // if password is wrong at this stage the attempts to use the cert will results in SSL Protocol Error - - var httpsConnectionAdapterOptions = new HttpsConnectionAdapterOptions() - { - ClientCertificateMode = ClientCertificateMode.NoCertificate, - SslProtocols = System.Security.Authentication.SslProtocols.Tls12 | System.Security.Authentication.SslProtocols.Tls13, - ServerCertificate = certificate, - }; - - var httpsPort = Convert.ToInt32(configuration["API:Service:HttpsPort"]); - - serverOptions.Listen(new System.Net.IPEndPoint(ipBinding, httpsPort), listenOptions => - { - listenOptions.UseHttps(httpsConnectionAdapterOptions); - }); - - } - catch (Exception exp) - { - // TODO: there is no logger yet, need to report this failure to main log once the log exists - System.Diagnostics.Debug.WriteLine("Failed to load PFX certificate for application. Check service certificate config." + exp.ToString()); - } - } - else - { - var httpPort = Convert.ToInt32(configuration["API:Service:HttpPort"]); - - serverOptions.Listen(new System.Net.IPEndPoint(ipBinding, httpPort), listenOptions => - { - }); - } - }); - - webBuilder.ConfigureLogging(logging => - { - logging.AddFilter("Microsoft.AspNetCore.SignalR", LogLevel.Debug); - logging.AddFilter("Microsoft.AspNetCore.Http.Connections", LogLevel.Debug); - }); - - webBuilder.UseStartup(); - }); - - return builder; - } - } -} diff --git a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Properties/launchSettings.json b/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Properties/launchSettings.json deleted file mode 100644 index 487cd482b..000000000 --- a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Properties/launchSettings.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "$schema": "http://json.schemastore.org/launchsettings.json", - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:9695" - } - }, - - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "launchUrl": "http://localhost:9695/docs", - "iisExpress": { - "applicationUrl": "http://localhost:9695" - }, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "Certify.Service.Worker": { - "commandName": "Project", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "Docker": { - "commandName": "Docker", - "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/docs", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development", - "ASPNETCORE_URLS": "http://0.0.0.0:9695" - }, - "publishAllPorts": true, - "httpPort": 9695 - }, - "WSL": { - "commandName": "WSL2", - "launchBrowser": true, - "launchUrl": "http://localhost:9695/docs", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - } - } -} diff --git a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Startup.cs b/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Startup.cs deleted file mode 100644 index ef965344c..000000000 --- a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Startup.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Microsoft.Extensions.Configuration; - -namespace Certify.Server.Worker -{ - public class Startup : Certify.Server.Core.Startup - { - public Startup(IConfiguration configuration) : base(configuration) - { - // base startup performs most of the configuration in this instance - } - } -} diff --git a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Worker.cs b/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Worker.cs deleted file mode 100644 index 4f05e30ab..000000000 --- a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/Worker.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; - -namespace Certify.Service.Worker -{ - public class Worker : BackgroundService - { - private readonly ILogger _logger; - - public Worker(ILogger logger) - { - _logger = logger; - } - - protected override async Task ExecuteAsync(CancellationToken stoppingToken) - { - // https://learn.microsoft.com/en-us/dotnet/core/extensions/windows-service?pivots=dotnet-7-0 - try - { - while (!stoppingToken.IsCancellationRequested) - { - _logger.LogInformation("Certify Service Worker heartbeat: {time}", DateTimeOffset.Now); - await Task.Delay(1000 * 60 * 60, stoppingToken); - } - } - catch (TaskCanceledException) - { - // When the stopping token is canceled, for example, a call made from services.msc, - // we shouldn't exit with a non-zero exit code. In other words, this is expected... - } - catch (Exception ex) - { - _logger.LogError(ex, "{Message}", ex.Message); - - // Terminates this process and returns an exit code to the operating system. - // This is required to avoid the 'BackgroundServiceExceptionBehavior', which - // performs one of two scenarios: - // 1. When set to "Ignore": will do nothing at all, errors cause zombie services. - // 2. When set to "StopHost": will cleanly stop the host, and log errors. - // - // In order for the Windows Service Management system to leverage configured - // recovery options, we need to terminate the process with a non-zero exit code. - Environment.Exit(1); - } - } - } -} diff --git a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/certifytheweb.service b/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/certifytheweb.service deleted file mode 100644 index 600d8d9f9..000000000 --- a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/certifytheweb.service +++ /dev/null @@ -1,14 +0,0 @@ -[Unit] -Description=Certify The Web API Worker - -[Service] -Type=notify -ExecStart=/usr/sbin/certifytheweb -WorkingDirectory=/opt/certifytheweb -Restart=always - -# Restart every day -RuntimeMaxSec=604800 - -[Install] -WantedBy=multi-user.target \ No newline at end of file diff --git a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/readme.md b/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/readme.md deleted file mode 100644 index aa95fc5c0..000000000 --- a/src/Certify.Server/Certify.Service.Worker/Certify.Service.Worker/readme.md +++ /dev/null @@ -1,81 +0,0 @@ -Certify Service Worker (Cross Platform) ------------------ - -# Architecture -- Certify.Server.Core, running as a hosted internal API app within a Worker (Certify.Service.Worker). This is a re-implementation of the original service usedby the desktop UI. -- Certify.Server.Api.Public, running as a API for public consumption, wrapping calls to the core internal API - -The advantage of this structure is that the public API can be exposed to the network for Web UI access, without client machines talking directly to the core service. The public API can run as the least prvilelged service user, while the core service may require elevated privileges depending on how it is used. - - -Development workflow - - - -Running Manually: - -dotnet ./Certify.Service.Worker.dll - -API will listen on http://localhost:32768 and https://localhost:44360 -HTTPS certificate setup is configured in Program.cs -Initial setup should use invalid pfx for https, with valid PFX to be acquired from own API. API status should flag https cert status for UI to report. - -WSL debug ---------- -- set DebugType to portable in build.props to enable debugging -- in debug service will run from host pc with a mnt -- database and log are in /usr/share/certify from Environment.SpecialFolder.CommonApplicationData - - -Linux Install ------------- - -`apt-get certifytheweb` - -`sudo mkdir /opt/certifytheweb` - -Systemd ------------ -``` -[Unit] -Description=Certify The Web - -[Service] -ExecStart=dotnet /opt/certifytheweb/certify.service -WorkingDirectory=/opt/certifytheweb/ -User=certifytheweb -Restart=on-failure -SyslogIdentifier=certifytheweb -PrivateTmp=true - -[Install] -WantedBy=multi-user.target -``` - -Windows Service ------------------ - -` sc create "Certify Certificate Manager [dotnet]" binpath="C:\Work\GIT\certify_dev\certify\src\Certify.Server\Certify.Service.Worker\Certify.Service.Worker\bin\Debug\net8.0\Certify.Service.Worker.exe"` - - -Publishing: - -- - -Windows: - -dotnet publish -c Release -r win-x64 --self-contained true - -Linux: - -dotnet publish -c Release -r linux-x64 --self-contained true - -Single File: - -dotnet publish -c Release -r win-x64 --self-contained true -p:PublishSingleFile=true - - -Docker ---------- -Requires a dockerfile to define how to build the images. certify-manager/docker for more dockerfile examples -Requries Microsoft.VisualStudio.Azure.Containers.Tools.Targets NuGet package installed in the project to hook up docker integration. From e5832c6e387df71708caffae4a580d2060b5bf54 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Mon, 8 Jan 2024 17:27:07 +0800 Subject: [PATCH 113/328] API: move example client from web UI --- .../Certify.API.Public.cs | 3655 +++++++++++++++++ .../Certify.Server.Api.Public.Client.csproj | 21 + .../nswag.json | 117 + .../readme.md | 5 + 4 files changed, 3798 insertions(+) create mode 100644 src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs create mode 100644 src/Certify.Server/Certify.Server.Api.Public.Client/Certify.Server.Api.Public.Client.csproj create mode 100644 src/Certify.Server/Certify.Server.Api.Public.Client/nswag.json create mode 100644 src/Certify.Server/Certify.Server.Api.Public.Client/readme.md diff --git a/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs b/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs new file mode 100644 index 000000000..47ec8f528 --- /dev/null +++ b/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs @@ -0,0 +1,3655 @@ +//---------------------- +// +// Generated using the NSwag toolchain v14.0.0.0 (NJsonSchema v11.0.0.0 (Newtonsoft.Json v13.0.0.0)) (http://NSwag.org) +// +//---------------------- + +using Certify.Models; +using Certify.Models.Reporting; +using Certify.Models.Config; +using Certify.Models.API; +using Certify.Models.Providers; +using Certify.Models.Config.AccessControl; +using Certify.Models.Config.Migration; + +#pragma warning disable 108 // Disable "CS0108 '{derivedDto}.ToJson()' hides inherited member '{dtoBase}.ToJson()'. Use the new keyword if hiding was intended." +#pragma warning disable 114 // Disable "CS0114 '{derivedDto}.RaisePropertyChanged(String)' hides inherited member 'dtoBase.RaisePropertyChanged(String)'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword." +#pragma warning disable 472 // Disable "CS0472 The result of the expression is always 'false' since a value of type 'Int32' is never equal to 'null' of type 'Int32?' +#pragma warning disable 612 // Disable "CS0612 '...' is obsolete" +#pragma warning disable 1573 // Disable "CS1573 Parameter '...' has no matching param tag in the XML comment for ... +#pragma warning disable 1591 // Disable "CS1591 Missing XML comment for publicly visible type or member ..." +#pragma warning disable 8073 // Disable "CS8073 The result of the expression is always 'false' since a value of type 'T' is never equal to 'null' of type 'T?'" +#pragma warning disable 3016 // Disable "CS3016 Arrays as attribute arguments is not CLS-compliant" +#pragma warning disable 8603 // Disable "CS8603 Possible null reference return" +#pragma warning disable 8604 // Disable "CS8604 Possible null reference argument for parameter" + +namespace Certify.API.Public +{ + using System = global::System; + + [System.CodeDom.Compiler.GeneratedCode("NSwag", "14.0.0.0 (NJsonSchema v11.0.0.0 (Newtonsoft.Json v13.0.0.0))")] + public partial class Client + { + #pragma warning disable 8618 // Set by constructor via BaseUrl property + private string _baseUrl; + #pragma restore disable 8618 // Set by constructor via BaseUrl property + private System.Net.Http.HttpClient _httpClient; + private static System.Lazy _settings = new System.Lazy(CreateSerializerSettings, true); + + public Client(string baseUrl, System.Net.Http.HttpClient httpClient) + { + BaseUrl = baseUrl; + _httpClient = httpClient; + } + + private static Newtonsoft.Json.JsonSerializerSettings CreateSerializerSettings() + { + var settings = new Newtonsoft.Json.JsonSerializerSettings(); + UpdateJsonSerializerSettings(settings); + return settings; + } + + public string BaseUrl + { + get { return _baseUrl; } + set + { + _baseUrl = value; + if (!string.IsNullOrEmpty(_baseUrl) && !_baseUrl.EndsWith("/")) + _baseUrl += '/'; + } + } + + protected Newtonsoft.Json.JsonSerializerSettings JsonSerializerSettings { get { return _settings.Value; } } + + static partial void UpdateJsonSerializerSettings(Newtonsoft.Json.JsonSerializerSettings settings); + + partial void PrepareRequest(System.Net.Http.HttpClient client, System.Net.Http.HttpRequestMessage request, string url); + partial void PrepareRequest(System.Net.Http.HttpClient client, System.Net.Http.HttpRequestMessage request, System.Text.StringBuilder urlBuilder); + partial void ProcessResponse(System.Net.Http.HttpClient client, System.Net.Http.HttpResponseMessage response); + + /// + /// Get list of Assigned Roles for a given security principle [Generated by Certify.SourceGenerators] + /// + /// Success + /// A server side error occurred. + public virtual System.Threading.Tasks.Task> GetSecurityPrincipleAssignedRolesAsync(string id) + { + return GetSecurityPrincipleAssignedRolesAsync(id, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Get list of Assigned Roles for a given security principle [Generated by Certify.SourceGenerators] + /// + /// Success + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task> GetSecurityPrincipleAssignedRolesAsync(string id, System.Threading.CancellationToken cancellationToken) + { + if (id == null) + throw new System.ArgumentNullException("id"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + // Operation Path: "internal/v1/access/securityprinciple/{id}/assignedroles" + urlBuilder_.Append("internal/v1/access/securityprinciple/"); + urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(id, System.Globalization.CultureInfo.InvariantCulture))); + urlBuilder_.Append("/assignedroles"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync>(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// + /// Get list of available security Roles [Generated by Certify.SourceGenerators] + /// + /// Success + /// A server side error occurred. + public virtual System.Threading.Tasks.Task> GetAccessRolesAsync() + { + return GetAccessRolesAsync(System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Get list of available security Roles [Generated by Certify.SourceGenerators] + /// + /// Success + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task> GetAccessRolesAsync(System.Threading.CancellationToken cancellationToken) + { + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + // Operation Path: "internal/v1/access/roles" + urlBuilder_.Append("internal/v1/access/roles"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync>(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// + /// Get list of available security principles [Generated by Certify.SourceGenerators] + /// + /// Success + /// A server side error occurred. + public virtual System.Threading.Tasks.Task> GetSecurityPrinciplesAsync() + { + return GetSecurityPrinciplesAsync(System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Get list of available security principles [Generated by Certify.SourceGenerators] + /// + /// Success + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task> GetSecurityPrinciplesAsync(System.Threading.CancellationToken cancellationToken) + { + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + // Operation Path: "internal/v1/access/securityprinciples" + urlBuilder_.Append("internal/v1/access/securityprinciples"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync>(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// + /// Operations to check current auth status for the given presented authentication tokens + /// + /// Success + /// A server side error occurred. + public virtual System.Threading.Tasks.Task CheckAuthStatusAsync() + { + return CheckAuthStatusAsync(System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Operations to check current auth status for the given presented authentication tokens + /// + /// Success + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task CheckAuthStatusAsync(System.Threading.CancellationToken cancellationToken) + { + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + // Operation Path: "api/v1/auth/status" + urlBuilder_.Append("api/v1/auth/status"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + return; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// + /// Perform login using username and password + /// + /// Login credentials + /// Success + /// A server side error occurred. + public virtual System.Threading.Tasks.Task LoginAsync(AuthRequest body) + { + return LoginAsync(body, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Perform login using username and password + /// + /// Login credentials + /// Success + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task LoginAsync(AuthRequest body, System.Threading.CancellationToken cancellationToken) + { + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value); + var content_ = new System.Net.Http.StringContent(json_); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + // Operation Path: "api/v1/auth/login" + urlBuilder_.Append("api/v1/auth/login"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// + /// Refresh users current auth token + /// + /// Success + /// A server side error occurred. + public virtual System.Threading.Tasks.Task RefreshAsync(string refreshToken) + { + return RefreshAsync(refreshToken, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Refresh users current auth token + /// + /// Success + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task RefreshAsync(string refreshToken, System.Threading.CancellationToken cancellationToken) + { + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Content = new System.Net.Http.StringContent(string.Empty, System.Text.Encoding.UTF8, "text/plain"); + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + // Operation Path: "api/v1/auth/refresh" + urlBuilder_.Append("api/v1/auth/refresh"); + urlBuilder_.Append('?'); + if (refreshToken != null) + { + urlBuilder_.Append(System.Uri.EscapeDataString("refreshToken")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(refreshToken, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); + } + urlBuilder_.Length--; + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// + /// Download the latest certificate for the given managed certificate + /// + /// Success + /// A server side error occurred. + public virtual System.Threading.Tasks.Task DownloadAsync(string managedCertId, string format, string mode) + { + return DownloadAsync(managedCertId, format, mode, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Download the latest certificate for the given managed certificate + /// + /// Success + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task DownloadAsync(string managedCertId, string format, string mode, System.Threading.CancellationToken cancellationToken) + { + if (managedCertId == null) + throw new System.ArgumentNullException("managedCertId"); + + if (format == null) + throw new System.ArgumentNullException("format"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + // Operation Path: "api/v1/certificate/{managedCertId}/download/{format}" + urlBuilder_.Append("api/v1/certificate/"); + urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(managedCertId, System.Globalization.CultureInfo.InvariantCulture))); + urlBuilder_.Append("/download/"); + urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(format, System.Globalization.CultureInfo.InvariantCulture))); + urlBuilder_.Append('?'); + if (mode != null) + { + urlBuilder_.Append(System.Uri.EscapeDataString("mode")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(mode, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); + } + urlBuilder_.Length--; + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + return; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// + /// Download text log for the given managed certificate + /// + /// Success + /// A server side error occurred. + public virtual System.Threading.Tasks.Task DownloadLogAsync(string managedCertId, int? maxLines) + { + return DownloadLogAsync(managedCertId, maxLines, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Download text log for the given managed certificate + /// + /// Success + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task DownloadLogAsync(string managedCertId, int? maxLines, System.Threading.CancellationToken cancellationToken) + { + if (managedCertId == null) + throw new System.ArgumentNullException("managedCertId"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + // Operation Path: "api/v1/certificate/{managedCertId}/log" + urlBuilder_.Append("api/v1/certificate/"); + urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(managedCertId, System.Globalization.CultureInfo.InvariantCulture))); + urlBuilder_.Append("/log"); + urlBuilder_.Append('?'); + if (maxLines != null) + { + urlBuilder_.Append(System.Uri.EscapeDataString("maxLines")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(maxLines, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); + } + urlBuilder_.Length--; + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// + /// Get all managed certificates matching criteria + /// + /// Success + /// A server side error occurred. + public virtual System.Threading.Tasks.Task GetManagedCertificatesAsync(string keyword, int? page, int? pageSize) + { + return GetManagedCertificatesAsync(keyword, page, pageSize, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Get all managed certificates matching criteria + /// + /// Success + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task GetManagedCertificatesAsync(string keyword, int? page, int? pageSize, System.Threading.CancellationToken cancellationToken) + { + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + // Operation Path: "api/v1/certificate" + urlBuilder_.Append("api/v1/certificate"); + urlBuilder_.Append('?'); + if (keyword != null) + { + urlBuilder_.Append(System.Uri.EscapeDataString("keyword")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(keyword, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); + } + if (page != null) + { + urlBuilder_.Append(System.Uri.EscapeDataString("page")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(page, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); + } + if (pageSize != null) + { + urlBuilder_.Append(System.Uri.EscapeDataString("pageSize")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(pageSize, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); + } + urlBuilder_.Length--; + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// + /// Get summary counts of all managed certs + /// + /// Success + /// A server side error occurred. + public virtual System.Threading.Tasks.Task GetManagedCertificateSummaryAsync(string keyword) + { + return GetManagedCertificateSummaryAsync(keyword, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Get summary counts of all managed certs + /// + /// Success + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task GetManagedCertificateSummaryAsync(string keyword, System.Threading.CancellationToken cancellationToken) + { + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Content = new System.Net.Http.StringContent(string.Empty, System.Text.Encoding.UTF8, "text/plain"); + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + // Operation Path: "api/v1/certificate/summary" + urlBuilder_.Append("api/v1/certificate/summary"); + urlBuilder_.Append('?'); + if (keyword != null) + { + urlBuilder_.Append(System.Uri.EscapeDataString("keyword")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(keyword, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); + } + urlBuilder_.Length--; + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// + /// Gets the full settings for a specific managed certificate + /// + /// Success + /// A server side error occurred. + public virtual System.Threading.Tasks.Task GetManagedCertificateDetailsAsync(string managedCertId) + { + return GetManagedCertificateDetailsAsync(managedCertId, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Gets the full settings for a specific managed certificate + /// + /// Success + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task GetManagedCertificateDetailsAsync(string managedCertId, System.Threading.CancellationToken cancellationToken) + { + if (managedCertId == null) + throw new System.ArgumentNullException("managedCertId"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + // Operation Path: "api/v1/certificate/settings/{managedCertId}" + urlBuilder_.Append("api/v1/certificate/settings/"); + urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(managedCertId, System.Globalization.CultureInfo.InvariantCulture))); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// + /// Add/update the full settings for a specific managed certificate + /// + /// Success + /// A server side error occurred. + public virtual System.Threading.Tasks.Task UpdateManagedCertificateDetailsAsync(ManagedCertificate body) + { + return UpdateManagedCertificateDetailsAsync(body, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Add/update the full settings for a specific managed certificate + /// + /// Success + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task UpdateManagedCertificateDetailsAsync(ManagedCertificate body, System.Threading.CancellationToken cancellationToken) + { + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value); + var content_ = new System.Net.Http.StringContent(json_); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + // Operation Path: "api/v1/certificate/settings/update" + urlBuilder_.Append("api/v1/certificate/settings/update"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// + /// Begin the managed certificate request/renewal process for the given managed certificate id (on demand) + /// + /// Success + /// A server side error occurred. + public virtual System.Threading.Tasks.Task BeginOrderAsync(string id) + { + return BeginOrderAsync(id, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Begin the managed certificate request/renewal process for the given managed certificate id (on demand) + /// + /// Success + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task BeginOrderAsync(string id, System.Threading.CancellationToken cancellationToken) + { + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Content = new System.Net.Http.StringContent(string.Empty, System.Text.Encoding.UTF8, "text/plain"); + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + // Operation Path: "api/v1/certificate/order" + urlBuilder_.Append("api/v1/certificate/order"); + urlBuilder_.Append('?'); + if (id != null) + { + urlBuilder_.Append(System.Uri.EscapeDataString("id")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(id, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); + } + urlBuilder_.Length--; + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// + /// Begin the managed certificate request/renewal process a set of managed certificates + /// + /// Success + /// A server side error occurred. + public virtual System.Threading.Tasks.Task PerformRenewalAsync(RenewalSettings body) + { + return PerformRenewalAsync(body, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Begin the managed certificate request/renewal process a set of managed certificates + /// + /// Success + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task PerformRenewalAsync(RenewalSettings body, System.Threading.CancellationToken cancellationToken) + { + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value); + var content_ = new System.Net.Http.StringContent(json_); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + // Operation Path: "api/v1/certificate/renew" + urlBuilder_.Append("api/v1/certificate/renew"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// + /// Perform default tests for the given configuration + /// + /// Success + /// A server side error occurred. + public virtual System.Threading.Tasks.Task> PerformConfigurationTestAsync(ManagedCertificate body) + { + return PerformConfigurationTestAsync(body, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Perform default tests for the given configuration + /// + /// Success + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task> PerformConfigurationTestAsync(ManagedCertificate body, System.Threading.CancellationToken cancellationToken) + { + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value); + var content_ = new System.Net.Http.StringContent(json_); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + // Operation Path: "api/v1/certificate/test" + urlBuilder_.Append("api/v1/certificate/test"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync>(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// + /// Remove Managed Certificate [Generated by Certify.SourceGenerators] + /// + /// Success + /// A server side error occurred. + public virtual System.Threading.Tasks.Task RemoveManagedCertificateAsync(string id) + { + return RemoveManagedCertificateAsync(id, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Remove Managed Certificate [Generated by Certify.SourceGenerators] + /// + /// Success + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task RemoveManagedCertificateAsync(string id, System.Threading.CancellationToken cancellationToken) + { + if (id == null) + throw new System.ArgumentNullException("id"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("DELETE"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + // Operation Path: "api/v1/certificate/certificate/{id}" + urlBuilder_.Append("api/v1/certificate/certificate/"); + urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(id, System.Globalization.CultureInfo.InvariantCulture))); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// + /// Get list of known certificate authorities + /// + /// Success + /// A server side error occurred. + public virtual System.Threading.Tasks.Task> GetCertificateAuthoritiesAsync() + { + return GetCertificateAuthoritiesAsync(System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Get list of known certificate authorities + /// + /// Success + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task> GetCertificateAuthoritiesAsync(System.Threading.CancellationToken cancellationToken) + { + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + // Operation Path: "internal/v1/certificateauthority" + urlBuilder_.Append("internal/v1/certificateauthority"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync>(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// + /// Get All Acme Accounts [Generated by Certify.SourceGenerators] + /// + /// Success + /// A server side error occurred. + public virtual System.Threading.Tasks.Task> GetAcmeAccountsAsync() + { + return GetAcmeAccountsAsync(System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Get All Acme Accounts [Generated by Certify.SourceGenerators] + /// + /// Success + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task> GetAcmeAccountsAsync(System.Threading.CancellationToken cancellationToken) + { + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + // Operation Path: "internal/v1/certificateauthority/accounts" + urlBuilder_.Append("internal/v1/certificateauthority/accounts"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync>(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// + /// Add New Acme Account [Generated by Certify.SourceGenerators] + /// + /// Success + /// A server side error occurred. + public virtual System.Threading.Tasks.Task AddAcmeAccountAsync(ContactRegistration body) + { + return AddAcmeAccountAsync(body, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Add New Acme Account [Generated by Certify.SourceGenerators] + /// + /// Success + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task AddAcmeAccountAsync(ContactRegistration body, System.Threading.CancellationToken cancellationToken) + { + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value); + var content_ = new System.Net.Http.StringContent(json_); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + // Operation Path: "internal/v1/certificateauthority/account" + urlBuilder_.Append("internal/v1/certificateauthority/account"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// + /// Add New Certificate Authority [Generated by Certify.SourceGenerators] + /// + /// Success + /// A server side error occurred. + public virtual System.Threading.Tasks.Task AddCertificateAuthorityAsync(CertificateAuthority body) + { + return AddCertificateAuthorityAsync(body, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Add New Certificate Authority [Generated by Certify.SourceGenerators] + /// + /// Success + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task AddCertificateAuthorityAsync(CertificateAuthority body, System.Threading.CancellationToken cancellationToken) + { + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value); + var content_ = new System.Net.Http.StringContent(json_); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + // Operation Path: "internal/v1/certificateauthority/authority" + urlBuilder_.Append("internal/v1/certificateauthority/authority"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// + /// Remove Certificate Authority [Generated by Certify.SourceGenerators] + /// + /// Success + /// A server side error occurred. + public virtual System.Threading.Tasks.Task RemoveCertificateAuthorityAsync(string id) + { + return RemoveCertificateAuthorityAsync(id, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Remove Certificate Authority [Generated by Certify.SourceGenerators] + /// + /// Success + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task RemoveCertificateAuthorityAsync(string id, System.Threading.CancellationToken cancellationToken) + { + if (id == null) + throw new System.ArgumentNullException("id"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("DELETE"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + // Operation Path: "internal/v1/certificateauthority/authority/{id}" + urlBuilder_.Append("internal/v1/certificateauthority/authority/"); + urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(id, System.Globalization.CultureInfo.InvariantCulture))); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// + /// Remove ACME Account [Generated by Certify.SourceGenerators] + /// + /// Success + /// A server side error occurred. + public virtual System.Threading.Tasks.Task RemoveAcmeAccountAsync(string storageKey, bool deactivate) + { + return RemoveAcmeAccountAsync(storageKey, deactivate, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Remove ACME Account [Generated by Certify.SourceGenerators] + /// + /// Success + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task RemoveAcmeAccountAsync(string storageKey, bool deactivate, System.Threading.CancellationToken cancellationToken) + { + if (storageKey == null) + throw new System.ArgumentNullException("storageKey"); + + if (deactivate == null) + throw new System.ArgumentNullException("deactivate"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("DELETE"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + // Operation Path: "internal/v1/certificateauthority/accounts/{storageKey}/{deactivate}" + urlBuilder_.Append("internal/v1/certificateauthority/accounts/"); + urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(storageKey, System.Globalization.CultureInfo.InvariantCulture))); + urlBuilder_.Append('/'); + urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(deactivate, System.Globalization.CultureInfo.InvariantCulture))); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// + /// Get list of supported challenge providers + /// + /// Success + /// A server side error occurred. + public virtual System.Threading.Tasks.Task> GetChallengeProvidersAsync() + { + return GetChallengeProvidersAsync(System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Get list of supported challenge providers + /// + /// Success + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task> GetChallengeProvidersAsync(System.Threading.CancellationToken cancellationToken) + { + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + // Operation Path: "internal/v1/challengeprovider" + urlBuilder_.Append("internal/v1/challengeprovider"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync>(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// + /// Fetch list of DNS zones for a given DNS provider and credential + /// + /// Success + /// A server side error occurred. + public virtual System.Threading.Tasks.Task> GetDnsZonesAsync(string providerTypeId, string credentialsId) + { + return GetDnsZonesAsync(providerTypeId, credentialsId, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Fetch list of DNS zones for a given DNS provider and credential + /// + /// Success + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task> GetDnsZonesAsync(string providerTypeId, string credentialsId, System.Threading.CancellationToken cancellationToken) + { + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + // Operation Path: "internal/v1/challengeprovider/dnszones" + urlBuilder_.Append("internal/v1/challengeprovider/dnszones"); + urlBuilder_.Append('?'); + if (providerTypeId != null) + { + urlBuilder_.Append(System.Uri.EscapeDataString("providerTypeId")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(providerTypeId, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); + } + if (credentialsId != null) + { + urlBuilder_.Append(System.Uri.EscapeDataString("credentialsId")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(credentialsId, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); + } + urlBuilder_.Length--; + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync>(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// + /// Get List of supported deployment tasks + /// + /// Success + /// A server side error occurred. + public virtual System.Threading.Tasks.Task> GetDeploymentProvidersAsync() + { + return GetDeploymentProvidersAsync(System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Get List of supported deployment tasks + /// + /// Success + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task> GetDeploymentProvidersAsync(System.Threading.CancellationToken cancellationToken) + { + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + // Operation Path: "internal/v1/deploymenttask/providers" + urlBuilder_.Append("internal/v1/deploymenttask/providers"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync>(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// + /// Get preview of steps for certificate order and deployment + /// + /// Success + /// A server side error occurred. + public virtual System.Threading.Tasks.Task> GetPreviewAsync(ManagedCertificate body) + { + return GetPreviewAsync(body, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Get preview of steps for certificate order and deployment + /// + /// Success + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task> GetPreviewAsync(ManagedCertificate body, System.Threading.CancellationToken cancellationToken) + { + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value); + var content_ = new System.Net.Http.StringContent(json_); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + // Operation Path: "internal/v1/preview" + urlBuilder_.Append("internal/v1/preview"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync>(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// + /// Get List of stored credentials + /// + /// Success + /// A server side error occurred. + public virtual System.Threading.Tasks.Task> GetStoredCredentialsAsync() + { + return GetStoredCredentialsAsync(System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Get List of stored credentials + /// + /// Success + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task> GetStoredCredentialsAsync(System.Threading.CancellationToken cancellationToken) + { + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + // Operation Path: "internal/v1/storedcredential" + urlBuilder_.Append("internal/v1/storedcredential"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync>(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// + /// Add/Update a stored credential + /// + /// Success + /// A server side error occurred. + public virtual System.Threading.Tasks.Task UpdateStoredCredentialAsync(StoredCredential body) + { + return UpdateStoredCredentialAsync(body, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Add/Update a stored credential + /// + /// Success + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task UpdateStoredCredentialAsync(StoredCredential body, System.Threading.CancellationToken cancellationToken) + { + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value); + var content_ = new System.Net.Http.StringContent(json_); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + // Operation Path: "internal/v1/storedcredential" + urlBuilder_.Append("internal/v1/storedcredential"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// + /// Remove Stored Credential [Generated by Certify.SourceGenerators] + /// + /// Success + /// A server side error occurred. + public virtual System.Threading.Tasks.Task RemoveStoredCredentialAsync(string storageKey) + { + return RemoveStoredCredentialAsync(storageKey, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Remove Stored Credential [Generated by Certify.SourceGenerators] + /// + /// Success + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task RemoveStoredCredentialAsync(string storageKey, System.Threading.CancellationToken cancellationToken) + { + if (storageKey == null) + throw new System.ArgumentNullException("storageKey"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("DELETE"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + // Operation Path: "internal/v1/storedcredential/storedcredential/{storageKey}" + urlBuilder_.Append("internal/v1/storedcredential/storedcredential/"); + urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(storageKey, System.Globalization.CultureInfo.InvariantCulture))); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// + /// Get the server software version + /// + /// Success + /// A server side error occurred. + public virtual System.Threading.Tasks.Task GetSystemVersionAsync() + { + return GetSystemVersionAsync(System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Get the server software version + /// + /// Success + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task GetSystemVersionAsync(System.Threading.CancellationToken cancellationToken) + { + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + // Operation Path: "api/v1/system/version" + urlBuilder_.Append("api/v1/system/version"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + return; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// + /// Check API is responding and can connect to background service + /// + /// Success + /// A server side error occurred. + public virtual System.Threading.Tasks.Task GetHealthAsync() + { + return GetHealthAsync(System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Check API is responding and can connect to background service + /// + /// Success + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task GetHealthAsync(System.Threading.CancellationToken cancellationToken) + { + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + // Operation Path: "api/v1/system/health" + urlBuilder_.Append("api/v1/system/health"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + return; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// + /// Check API is responding and can connect to background service + /// + /// Success + /// A server side error occurred. + public virtual System.Threading.Tasks.Task GetHealth2Async() + { + return GetHealth2Async(System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Check API is responding and can connect to background service + /// + /// Success + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task GetHealth2Async(System.Threading.CancellationToken cancellationToken) + { + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + // Operation Path: "health" + urlBuilder_.Append("health"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + return; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// + /// Perform an export of all settings [Generated by Certify.SourceGenerators] + /// + /// Success + /// A server side error occurred. + public virtual System.Threading.Tasks.Task PerformExportAsync(ExportRequest body) + { + return PerformExportAsync(body, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Perform an export of all settings [Generated by Certify.SourceGenerators] + /// + /// Success + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task PerformExportAsync(ExportRequest body, System.Threading.CancellationToken cancellationToken) + { + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value); + var content_ = new System.Net.Http.StringContent(json_); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + // Operation Path: "api/v1/system/system/migration/export" + urlBuilder_.Append("api/v1/system/system/migration/export"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// + /// Perform an import of all settings [Generated by Certify.SourceGenerators] + /// + /// Success + /// A server side error occurred. + public virtual System.Threading.Tasks.Task> PerformImportAsync(ImportRequest body) + { + return PerformImportAsync(body, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Perform an import of all settings [Generated by Certify.SourceGenerators] + /// + /// Success + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task> PerformImportAsync(ImportRequest body, System.Threading.CancellationToken cancellationToken) + { + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value); + var content_ = new System.Net.Http.StringContent(json_); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + // Operation Path: "api/v1/system/system/migration/import" + urlBuilder_.Append("api/v1/system/system/migration/import"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync>(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// + /// Get list of known service items (e.g. websites etc) we may want to then check for domains etc to add to cert. May return many items (e.g. thousands of sites) + /// + /// Success + /// A server side error occurred. + public virtual System.Threading.Tasks.Task> GetTargetServiceItemsAsync(string serverType) + { + return GetTargetServiceItemsAsync(serverType, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Get list of known service items (e.g. websites etc) we may want to then check for domains etc to add to cert. May return many items (e.g. thousands of sites) + /// + /// Success + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task> GetTargetServiceItemsAsync(string serverType, System.Threading.CancellationToken cancellationToken) + { + if (serverType == null) + throw new System.ArgumentNullException("serverType"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + // Operation Path: "internal/v1/target/{serverType}/items" + urlBuilder_.Append("internal/v1/target/"); + urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(serverType, System.Globalization.CultureInfo.InvariantCulture))); + urlBuilder_.Append("/items"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync>(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// + /// Return details of single target server item (e.g. 1 site) + /// + /// Success + /// A server side error occurred. + public virtual System.Threading.Tasks.Task GetTargetServiceItemAsync(string serverType, string itemId) + { + return GetTargetServiceItemAsync(serverType, itemId, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Return details of single target server item (e.g. 1 site) + /// + /// Success + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task GetTargetServiceItemAsync(string serverType, string itemId, System.Threading.CancellationToken cancellationToken) + { + if (serverType == null) + throw new System.ArgumentNullException("serverType"); + + if (itemId == null) + throw new System.ArgumentNullException("itemId"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + // Operation Path: "internal/v1/target/{serverType}/item/{itemId}" + urlBuilder_.Append("internal/v1/target/"); + urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(serverType, System.Globalization.CultureInfo.InvariantCulture))); + urlBuilder_.Append("/item/"); + urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(itemId, System.Globalization.CultureInfo.InvariantCulture))); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// + /// Get list of known service items (e.g. websites etc) we may want to then check for domains etc to add to cert + /// + /// Success + /// A server side error occurred. + public virtual System.Threading.Tasks.Task> GetTargetServiceItemIdentifiersAsync(string serverType, string itemId) + { + return GetTargetServiceItemIdentifiersAsync(serverType, itemId, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Get list of known service items (e.g. websites etc) we may want to then check for domains etc to add to cert + /// + /// Success + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task> GetTargetServiceItemIdentifiersAsync(string serverType, string itemId, System.Threading.CancellationToken cancellationToken) + { + if (serverType == null) + throw new System.ArgumentNullException("serverType"); + + if (itemId == null) + throw new System.ArgumentNullException("itemId"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + // Operation Path: "internal/v1/target/{serverType}/item/{itemId}/identifiers" + urlBuilder_.Append("internal/v1/target/"); + urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(serverType, System.Globalization.CultureInfo.InvariantCulture))); + urlBuilder_.Append("/item/"); + urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(itemId, System.Globalization.CultureInfo.InvariantCulture))); + urlBuilder_.Append("/identifiers"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync>(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// + /// Get list of target services this server supports (e.g. IIS etc) + /// + /// Success + /// A server side error occurred. + public virtual System.Threading.Tasks.Task> GetTargetServiceTypesAsync() + { + return GetTargetServiceTypesAsync(System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Get list of target services this server supports (e.g. IIS etc) + /// + /// Success + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task> GetTargetServiceTypesAsync(System.Threading.CancellationToken cancellationToken) + { + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + // Operation Path: "internal/v1/target/services" + urlBuilder_.Append("internal/v1/target/services"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync>(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// + /// get current challenge info fo a given type/key + /// + /// Success + /// A server side error occurred. + public virtual System.Threading.Tasks.Task> GetValidationChallengesAsync(string type, string key) + { + return GetValidationChallengesAsync(type, key, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// get current challenge info fo a given type/key + /// + /// Success + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task> GetValidationChallengesAsync(string type, string key, System.Threading.CancellationToken cancellationToken) + { + if (type == null) + throw new System.ArgumentNullException("type"); + + if (key == null) + throw new System.ArgumentNullException("key"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + // Operation Path: "api/v1/validation/{type}/{key}" + urlBuilder_.Append("api/v1/validation/"); + urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(type, System.Globalization.CultureInfo.InvariantCulture))); + urlBuilder_.Append('/'); + urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(key, System.Globalization.CultureInfo.InvariantCulture))); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync>(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + protected struct ObjectResponseResult + { + public ObjectResponseResult(T responseObject, string responseText) + { + this.Object = responseObject; + this.Text = responseText; + } + + public T Object { get; } + + public string Text { get; } + } + + public bool ReadResponseAsString { get; set; } + + protected virtual async System.Threading.Tasks.Task> ReadObjectResponseAsync(System.Net.Http.HttpResponseMessage response, System.Collections.Generic.IReadOnlyDictionary> headers, System.Threading.CancellationToken cancellationToken) + { + if (response == null || response.Content == null) + { + return new ObjectResponseResult(default(T), string.Empty); + } + + if (ReadResponseAsString) + { + var responseText = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + try + { + var typedBody = Newtonsoft.Json.JsonConvert.DeserializeObject(responseText, JsonSerializerSettings); + return new ObjectResponseResult(typedBody, responseText); + } + catch (Newtonsoft.Json.JsonException exception) + { + var message = "Could not deserialize the response body string as " + typeof(T).FullName + "."; + throw new ApiException(message, (int)response.StatusCode, responseText, headers, exception); + } + } + else + { + try + { + using (var responseStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false)) + using (var streamReader = new System.IO.StreamReader(responseStream)) + using (var jsonTextReader = new Newtonsoft.Json.JsonTextReader(streamReader)) + { + var serializer = Newtonsoft.Json.JsonSerializer.Create(JsonSerializerSettings); + var typedBody = serializer.Deserialize(jsonTextReader); + return new ObjectResponseResult(typedBody, string.Empty); + } + } + catch (Newtonsoft.Json.JsonException exception) + { + var message = "Could not deserialize the response body stream as " + typeof(T).FullName + "."; + throw new ApiException(message, (int)response.StatusCode, string.Empty, headers, exception); + } + } + } + + private string ConvertToString(object value, System.Globalization.CultureInfo cultureInfo) + { + if (value == null) + { + return ""; + } + + if (value is System.Enum) + { + var name = System.Enum.GetName(value.GetType(), value); + if (name != null) + { + var field = System.Reflection.IntrospectionExtensions.GetTypeInfo(value.GetType()).GetDeclaredField(name); + if (field != null) + { + var attribute = System.Reflection.CustomAttributeExtensions.GetCustomAttribute(field, typeof(System.Runtime.Serialization.EnumMemberAttribute)) + as System.Runtime.Serialization.EnumMemberAttribute; + if (attribute != null) + { + return attribute.Value != null ? attribute.Value : name; + } + } + + var converted = System.Convert.ToString(System.Convert.ChangeType(value, System.Enum.GetUnderlyingType(value.GetType()), cultureInfo)); + return converted == null ? string.Empty : converted; + } + } + else if (value is bool) + { + return System.Convert.ToString((bool)value, cultureInfo).ToLowerInvariant(); + } + else if (value is byte[]) + { + return System.Convert.ToBase64String((byte[]) value); + } + else if (value is string[]) + { + return string.Join(",", (string[])value); + } + else if (value.GetType().IsArray) + { + var valueArray = (System.Array)value; + var valueTextArray = new string[valueArray.Length]; + for (var i = 0; i < valueArray.Length; i++) + { + valueTextArray[i] = ConvertToString(valueArray.GetValue(i), cultureInfo); + } + return string.Join(",", valueTextArray); + } + + var result = System.Convert.ToString(value, cultureInfo); + return result == null ? "" : result; + } + } + + + + + + [System.CodeDom.Compiler.GeneratedCode("NSwag", "14.0.0.0 (NJsonSchema v11.0.0.0 (Newtonsoft.Json v13.0.0.0))")] + public partial class ApiException : System.Exception + { + public int StatusCode { get; private set; } + + public string Response { get; private set; } + + public System.Collections.Generic.IReadOnlyDictionary> Headers { get; private set; } + + public ApiException(string message, int statusCode, string response, System.Collections.Generic.IReadOnlyDictionary> headers, System.Exception innerException) + : base(message + "\n\nStatus: " + statusCode + "\nResponse: \n" + ((response == null) ? "(null)" : response.Substring(0, response.Length >= 512 ? 512 : response.Length)), innerException) + { + StatusCode = statusCode; + Response = response; + Headers = headers; + } + + public override string ToString() + { + return string.Format("HTTP Response: \n\n{0}\n\n{1}", Response, base.ToString()); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NSwag", "14.0.0.0 (NJsonSchema v11.0.0.0 (Newtonsoft.Json v13.0.0.0))")] + public partial class ApiException : ApiException + { + public TResult Result { get; private set; } + + public ApiException(string message, int statusCode, string response, System.Collections.Generic.IReadOnlyDictionary> headers, TResult result, System.Exception innerException) + : base(message, statusCode, response, headers, innerException) + { + Result = result; + } + } + +} + +#pragma warning restore 108 +#pragma warning restore 114 +#pragma warning restore 472 +#pragma warning restore 612 +#pragma warning restore 1573 +#pragma warning restore 1591 +#pragma warning restore 8073 +#pragma warning restore 3016 +#pragma warning restore 8603 +#pragma warning restore 8604 \ No newline at end of file diff --git a/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.Server.Api.Public.Client.csproj b/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.Server.Api.Public.Client.csproj new file mode 100644 index 000000000..0d959f125 --- /dev/null +++ b/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.Server.Api.Public.Client.csproj @@ -0,0 +1,21 @@ + + + + net8.0 + enable + enable + + + + + PreserveNewest + true + PreserveNewest + + + + + + + + diff --git a/src/Certify.Server/Certify.Server.Api.Public.Client/nswag.json b/src/Certify.Server/Certify.Server.Api.Public.Client/nswag.json new file mode 100644 index 000000000..e3d853588 --- /dev/null +++ b/src/Certify.Server/Certify.Server.Api.Public.Client/nswag.json @@ -0,0 +1,117 @@ +{ + "runtime": "Net70", + "defaultVariables": null, + "documentGenerator": { + "fromDocument": { + "url": "https://localhost:44361/swagger/v1/swagger.json", + "output": null, + "newLineBehavior": "Auto" + } + }, + "codeGenerators": { + "openApiToCSharpClient": { + "clientBaseClass": null, + "configurationClass": null, + "generateClientClasses": true, + "suppressClientClassesOutput": false, + "generateClientInterfaces": false, + "suppressClientInterfacesOutput": false, + "clientBaseInterface": null, + "injectHttpClient": true, + "disposeHttpClient": true, + "protectedMethods": [], + "generateExceptionClasses": true, + "exceptionClass": "ApiException", + "wrapDtoExceptions": true, + "useHttpClientCreationMethod": false, + "httpClientType": "System.Net.Http.HttpClient", + "useHttpRequestMessageCreationMethod": false, + "useBaseUrl": true, + "generateBaseUrlProperty": true, + "generateSyncMethods": false, + "generatePrepareRequestAndProcessResponseAsAsyncMethods": false, + "exposeJsonSerializerSettings": false, + "clientClassAccessModifier": "public", + "typeAccessModifier": "public", + "propertySetterAccessModifier": "", + "generateNativeRecords": false, + "generateContractsOutput": false, + "contractsNamespace": null, + "contractsOutputFilePath": null, + "parameterDateTimeFormat": "s", + "parameterDateFormat": "yyyy-MM-dd", + "generateUpdateJsonSerializerSettingsMethod": true, + "useRequestAndResponseSerializationSettings": false, + "serializeTypeInformation": false, + "queryNullValue": "", + "className": "{controller}Client", + "operationGenerationMode": "SingleClientFromOperationId", + "additionalNamespaceUsages": [ + "Certify.Models", + "Certify.Models.Reporting", + "Certify.Models.Config", + "Certify.Models.API", + "Certify.Models.Providers", + "Certify.Models.Config.AccessControl", + "Certify.Models.Config.Migration" + ], + "additionalContractNamespaceUsages": [], + "generateOptionalParameters": false, + "generateJsonMethods": false, + "enforceFlagEnums": false, + "parameterArrayType": "System.Collections.Generic.IEnumerable", + "parameterDictionaryType": "System.Collections.Generic.IDictionary", + "responseArrayType": "System.Collections.Generic.ICollection", + "responseDictionaryType": "System.Collections.Generic.IDictionary", + "wrapResponses": false, + "wrapResponseMethods": [], + "generateResponseClasses": true, + "responseClass": "SwaggerResponse", + "namespace": "Certify.API.Public", + "requiredPropertiesMustBeDefined": true, + "dateType": "System.DateTimeOffset", + "jsonConverters": null, + "anyType": "object", + "dateTimeType": "System.DateTimeOffset", + "timeType": "System.TimeSpan", + "timeSpanType": "System.TimeSpan", + "arrayType": "System.Collections.Generic.ICollection", + "arrayInstanceType": "System.Collections.ObjectModel.Collection", + "dictionaryType": "System.Collections.Generic.IDictionary", + "dictionaryInstanceType": "System.Collections.Generic.Dictionary", + "arrayBaseType": "System.Collections.ObjectModel.Collection", + "dictionaryBaseType": "System.Collections.Generic.Dictionary", + "classStyle": "Poco", + "jsonLibrary": "NewtonsoftJson", + "generateDefaultValues": true, + "generateDataAnnotations": true, + "excludedTypeNames": [ + "ManagedCertificate", + "BindingInfo", + "DomainOption", + "SiteInfo", + "ActionStep", + "CertRequestChallengeConfig", + "DeploymentProviderDefinition", + "ChallengeProviderDefinition" + ], + "excludedParameterNames": [], + "handleReferences": false, + "generateImmutableArrayProperties": false, + "generateImmutableDictionaryProperties": false, + "jsonSerializerSettingsTransformationMethod": null, + "inlineNamedArrays": false, + "inlineNamedDictionaries": false, + "inlineNamedTuples": true, + "inlineNamedAny": false, + "generateDtoTypes": false, + "generateOptionalPropertiesAsNullable": false, + "generateNullableReferenceTypes": false, + "templateDirectory": null, + "serviceHost": null, + "serviceSchemes": null, + "output": "Certify.API.Public.cs", + "newLineBehavior": "Auto" + } + } +} \ No newline at end of file diff --git a/src/Certify.Server/Certify.Server.Api.Public.Client/readme.md b/src/Certify.Server/Certify.Server.Api.Public.Client/readme.md new file mode 100644 index 000000000..9385e0f1f --- /dev/null +++ b/src/Certify.Server/Certify.Server.Api.Public.Client/readme.md @@ -0,0 +1,5 @@ +# Certify.Server.Api.Public.Client + +This is a an example C# client for the public API of Certify Server. The Client is currently generated using NSwagStudio (start public API in debug then run the tool to generate the updated client). + +This client can optionally be used by API tests and is used by the Blazor WASM UI. From 676d53615e563cc02ed89171d58db6d47da295b6 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Mon, 15 Jan 2024 13:55:05 +0800 Subject: [PATCH 114/328] Package updates --- .../Certify.Aspire.ServiceDefaults.csproj | 2 +- src/Certify.Client/Certify.Client.csproj | 6 +++--- src/Certify.Models/Certify.Models.csproj | 4 ++-- .../DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj | 2 +- src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj | 2 +- .../Certify.Server.Api.Public.Tests.csproj | 3 ++- .../Certify.Server.Api.Public.csproj | 2 +- .../Certify.Server.Core/Certify.Server.Core.csproj | 8 ++++---- src/Certify.Service/App.config | 2 +- .../Certify.Shared.Extensions.csproj | 2 +- .../Certify.Core.Tests.Unit.csproj | 2 +- src/Certify.UI.Shared/Certify.UI.Shared.csproj | 2 +- 12 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/Certify.Aspire/Certify.Aspire.ServiceDefaults/Certify.Aspire.ServiceDefaults.csproj b/src/Certify.Aspire/Certify.Aspire.ServiceDefaults/Certify.Aspire.ServiceDefaults.csproj index dcf9ca505..9f3857e22 100644 --- a/src/Certify.Aspire/Certify.Aspire.ServiceDefaults/Certify.Aspire.ServiceDefaults.csproj +++ b/src/Certify.Aspire/Certify.Aspire.ServiceDefaults/Certify.Aspire.ServiceDefaults.csproj @@ -11,7 +11,7 @@ - + diff --git a/src/Certify.Client/Certify.Client.csproj b/src/Certify.Client/Certify.Client.csproj index ab9d2fd2c..5c6e9143f 100644 --- a/src/Certify.Client/Certify.Client.csproj +++ b/src/Certify.Client/Certify.Client.csproj @@ -16,9 +16,9 @@ - - - + + + diff --git a/src/Certify.Models/Certify.Models.csproj b/src/Certify.Models/Certify.Models.csproj index 9839a6be6..698d5f42e 100644 --- a/src/Certify.Models/Certify.Models.csproj +++ b/src/Certify.Models/Certify.Models.csproj @@ -21,7 +21,7 @@ all runtime; build; native; contentfiles; analyzers - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -30,7 +30,7 @@ all - + diff --git a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj index b1a86b1b9..70d4119e1 100644 --- a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj +++ b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj b/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj index aabbfb6c2..fb43b336d 100644 --- a/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj +++ b/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj b/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj index ac9c3fbc3..864cf5cbe 100644 --- a/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj +++ b/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj @@ -25,7 +25,7 @@ - + @@ -36,6 +36,7 @@ + diff --git a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj index f9ead6e91..0fd764c92 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj +++ b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj @@ -18,7 +18,7 @@ - + diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj index d5def1de3..a0d19b7ec 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj @@ -11,12 +11,12 @@ - + - - - + + + diff --git a/src/Certify.Service/App.config b/src/Certify.Service/App.config index c1a021fc2..559510f55 100644 --- a/src/Certify.Service/App.config +++ b/src/Certify.Service/App.config @@ -14,7 +14,7 @@ - + diff --git a/src/Certify.Shared.Extensions/Certify.Shared.Extensions.csproj b/src/Certify.Shared.Extensions/Certify.Shared.Extensions.csproj index d64920bd5..3ad2e3071 100644 --- a/src/Certify.Shared.Extensions/Certify.Shared.Extensions.csproj +++ b/src/Certify.Shared.Extensions/Certify.Shared.Extensions.csproj @@ -23,7 +23,7 @@ - + diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj index 0fc71d252..1c9f74d96 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj @@ -149,7 +149,7 @@ - + diff --git a/src/Certify.UI.Shared/Certify.UI.Shared.csproj b/src/Certify.UI.Shared/Certify.UI.Shared.csproj index 35e9e1d33..0762fb164 100644 --- a/src/Certify.UI.Shared/Certify.UI.Shared.csproj +++ b/src/Certify.UI.Shared/Certify.UI.Shared.csproj @@ -42,7 +42,7 @@ - + NU1701 From 20f9ae7a4c0fb965b1f288f57b3fc52b305dc35b Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Mon, 15 Jan 2024 13:59:29 +0800 Subject: [PATCH 115/328] UI: update license action text for clarity --- .../Controls/AboutControl.xaml | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Certify.UI.Shared/Controls/AboutControl.xaml b/src/Certify.UI.Shared/Controls/AboutControl.xaml index df6c73b89..72bd281b4 100644 --- a/src/Certify.UI.Shared/Controls/AboutControl.xaml +++ b/src/Certify.UI.Shared/Controls/AboutControl.xaml @@ -81,24 +81,16 @@ Click="UpdateCheck_Click" Content="{x:Static res:SR.AboutControl_CheckForUpdateButton}" DockPanel.Dock="Top" /> - /// Success /// A server side error occurred. - public virtual System.Threading.Tasks.Task DownloadAsync(string managedCertId, string format, string mode) + public virtual System.Threading.Tasks.Task DownloadAsync(string managedCertId, string format, string mode) { return DownloadAsync(managedCertId, format, mode, System.Threading.CancellationToken.None); } @@ -600,7 +601,7 @@ public virtual System.Threading.Tasks.Task DownloadAsync(string managedCertId, s /// /// Success /// A server side error occurred. - public virtual async System.Threading.Tasks.Task DownloadAsync(string managedCertId, string format, string mode, System.Threading.CancellationToken cancellationToken) + public virtual async System.Threading.Tasks.Task DownloadAsync(string managedCertId, string format, string mode, System.Threading.CancellationToken cancellationToken) { if (managedCertId == null) throw new System.ArgumentNullException("managedCertId"); @@ -615,6 +616,7 @@ public virtual async System.Threading.Tasks.Task DownloadAsync(string managedCer using (var request_ = new System.Net.Http.HttpRequestMessage()) { request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); var urlBuilder_ = new System.Text.StringBuilder(); if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); @@ -653,9 +655,12 @@ public virtual async System.Threading.Tasks.Task DownloadAsync(string managedCer ProcessResponse(client_, response_); var status_ = (int)response_.StatusCode; - if (status_ == 200) + if (status_ == 200 || status_ == 206) { - return; + var responseStream_ = response_.Content == null ? System.IO.Stream.Null : await response_.Content.ReadAsStreamAsync().ConfigureAwait(false); + var fileResponse_ = new FileResponse(status_, headers_, responseStream_, null, response_); + disposeClient_ = false; disposeResponse_ = false; // response and client are disposed by FileResponse + return fileResponse_; } else { @@ -2632,7 +2637,7 @@ public virtual async System.Threading.Tasks.Task RemoveStoredCrede /// /// Success /// A server side error occurred. - public virtual System.Threading.Tasks.Task GetSystemVersionAsync() + public virtual System.Threading.Tasks.Task GetSystemVersionAsync() { return GetSystemVersionAsync(System.Threading.CancellationToken.None); } @@ -2643,7 +2648,7 @@ public virtual System.Threading.Tasks.Task GetSystemVersionAsync() ///
/// Success /// A server side error occurred. - public virtual async System.Threading.Tasks.Task GetSystemVersionAsync(System.Threading.CancellationToken cancellationToken) + public virtual async System.Threading.Tasks.Task GetSystemVersionAsync(System.Threading.CancellationToken cancellationToken) { var client_ = _httpClient; var disposeClient_ = false; @@ -2652,6 +2657,7 @@ public virtual async System.Threading.Tasks.Task GetSystemVersionAsync(System.Th using (var request_ = new System.Net.Http.HttpRequestMessage()) { request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); var urlBuilder_ = new System.Text.StringBuilder(); if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); @@ -2683,7 +2689,12 @@ public virtual async System.Threading.Tasks.Task GetSystemVersionAsync(System.Th var status_ = (int)response_.StatusCode; if (status_ == 200) { - return; + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; } else { @@ -2710,7 +2721,7 @@ public virtual async System.Threading.Tasks.Task GetSystemVersionAsync(System.Th ///
/// Success /// A server side error occurred. - public virtual System.Threading.Tasks.Task GetHealthAsync() + public virtual System.Threading.Tasks.Task GetHealthAsync() { return GetHealthAsync(System.Threading.CancellationToken.None); } @@ -2721,7 +2732,7 @@ public virtual System.Threading.Tasks.Task GetHealthAsync() /// /// Success /// A server side error occurred. - public virtual async System.Threading.Tasks.Task GetHealthAsync(System.Threading.CancellationToken cancellationToken) + public virtual async System.Threading.Tasks.Task GetHealthAsync(System.Threading.CancellationToken cancellationToken) { var client_ = _httpClient; var disposeClient_ = false; @@ -2730,6 +2741,7 @@ public virtual async System.Threading.Tasks.Task GetHealthAsync(System.Threading using (var request_ = new System.Net.Http.HttpRequestMessage()) { request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); var urlBuilder_ = new System.Text.StringBuilder(); if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); @@ -2761,85 +2773,12 @@ public virtual async System.Threading.Tasks.Task GetHealthAsync(System.Threading var status_ = (int)response_.StatusCode; if (status_ == 200) { - return; - } - else - { - var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); - throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); - } - } - finally - { - if (disposeResponse_) - response_.Dispose(); - } - } - } - finally - { - if (disposeClient_) - client_.Dispose(); - } - } - - /// - /// Check API is responding and can connect to background service - /// - /// Success - /// A server side error occurred. - public virtual System.Threading.Tasks.Task GetHealth2Async() - { - return GetHealth2Async(System.Threading.CancellationToken.None); - } - - /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. - /// - /// Check API is responding and can connect to background service - /// - /// Success - /// A server side error occurred. - public virtual async System.Threading.Tasks.Task GetHealth2Async(System.Threading.CancellationToken cancellationToken) - { - var client_ = _httpClient; - var disposeClient_ = false; - try - { - using (var request_ = new System.Net.Http.HttpRequestMessage()) - { - request_.Method = new System.Net.Http.HttpMethod("GET"); - - var urlBuilder_ = new System.Text.StringBuilder(); - if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); - // Operation Path: "health" - urlBuilder_.Append("health"); - - PrepareRequest(client_, request_, urlBuilder_); - - var url_ = urlBuilder_.ToString(); - request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - - PrepareRequest(client_, request_, url_); - - var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); - var disposeResponse_ = true; - try - { - var headers_ = new System.Collections.Generic.Dictionary>(); - foreach (var item_ in response_.Headers) - headers_[item_.Key] = item_.Value; - if (response_.Content != null && response_.Content.Headers != null) - { - foreach (var item_ in response_.Content.Headers) - headers_[item_.Key] = item_.Value; - } - - ProcessResponse(client_, response_); - - var status_ = (int)response_.StatusCode; - if (status_ == 200) - { - return; + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; } else { @@ -3398,7 +3337,7 @@ public virtual async System.Threading.Tasks.Task GetTargetServiceItemA } /// - /// get current challenge info fo a given type/key + /// get current challenge info for a given type/key /// /// Success /// A server side error occurred. @@ -3409,7 +3348,7 @@ public virtual async System.Threading.Tasks.Task GetTargetServiceItemA /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// get current challenge info fo a given type/key + /// get current challenge info for a given type/key /// /// Success /// A server side error occurred. @@ -3604,9 +3543,44 @@ private string ConvertToString(object value, System.Globalization.CultureInfo cu + [System.CodeDom.Compiler.GeneratedCode("NSwag", "14.0.1.0 (NJsonSchema v11.0.0.0 (Newtonsoft.Json v13.0.0.0))")] + public partial class FileResponse : System.IDisposable + { + private System.IDisposable _client; + private System.IDisposable _response; + + public int StatusCode { get; private set; } + + public System.Collections.Generic.IReadOnlyDictionary> Headers { get; private set; } + + public System.IO.Stream Stream { get; private set; } + + public bool IsPartial + { + get { return StatusCode == 206; } + } + + public FileResponse(int statusCode, System.Collections.Generic.IReadOnlyDictionary> headers, System.IO.Stream stream, System.IDisposable client, System.IDisposable response) + { + StatusCode = statusCode; + Headers = headers; + Stream = stream; + _client = client; + _response = response; + } + + public void Dispose() + { + Stream.Dispose(); + if (_response != null) + _response.Dispose(); + if (_client != null) + _client.Dispose(); + } + } - [System.CodeDom.Compiler.GeneratedCode("NSwag", "14.0.0.0 (NJsonSchema v11.0.0.0 (Newtonsoft.Json v13.0.0.0))")] + [System.CodeDom.Compiler.GeneratedCode("NSwag", "14.0.1.0 (NJsonSchema v11.0.0.0 (Newtonsoft.Json v13.0.0.0))")] public partial class ApiException : System.Exception { public int StatusCode { get; private set; } @@ -3629,7 +3603,7 @@ public override string ToString() } } - [System.CodeDom.Compiler.GeneratedCode("NSwag", "14.0.0.0 (NJsonSchema v11.0.0.0 (Newtonsoft.Json v13.0.0.0))")] + [System.CodeDom.Compiler.GeneratedCode("NSwag", "14.0.1.0 (NJsonSchema v11.0.0.0 (Newtonsoft.Json v13.0.0.0))")] public partial class ApiException : ApiException { public TResult Result { get; private set; } @@ -3652,4 +3626,5 @@ public ApiException(string message, int statusCode, string response, System.Coll #pragma warning restore 8073 #pragma warning restore 3016 #pragma warning restore 8603 -#pragma warning restore 8604 \ No newline at end of file +#pragma warning restore 8604 +#pragma warning restore 8625 \ No newline at end of file diff --git a/src/Certify.Server/Certify.Server.Api.Public.Tests/APITestBase.cs b/src/Certify.Server/Certify.Server.Api.Public.Tests/APITestBase.cs index 8fb166e7c..fc469204e 100644 --- a/src/Certify.Server/Certify.Server.Api.Public.Tests/APITestBase.cs +++ b/src/Certify.Server/Certify.Server.Api.Public.Tests/APITestBase.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.IO; using System.Linq; using System.Net.Http; @@ -7,10 +8,12 @@ using Certify.Server.Api.Public.Controllers; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Microsoft.VisualStudio.TestPlatform.CoreUtilities.Helpers; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Certify.Service.Api.Tests @@ -18,10 +21,13 @@ namespace Certify.Service.Api.Tests [TestClass] public class APITestBase { - internal static HttpClient _clientWithAnonymousAccess; - internal static HttpClient _clientWithAuthorizedAccess; + internal static Certify.API.Public.Client _clientWithAnonymousAccess; + internal static HttpClient _httpClientWithAnonymousAccess; - internal static TestServer _server; + internal static Certify.API.Public.Client _clientWithAuthorizedAccess; + internal static HttpClient _httpClientWithAuthorizedAccess; + + internal static TestServer _apiServer; internal static System.Text.Json.JsonSerializerOptions _defaultJsonSerializerOptions = new System.Text.Json.JsonSerializerOptions { PropertyNameCaseInsensitive = true }; @@ -30,84 +36,84 @@ public class APITestBase internal string _refreshToken; [AssemblyInitialize] - public static void Init(TestContext context) + public static void AssemblyInit(TestContext context) { - // setup public API service and backend service - var config = new ConfigurationBuilder() - .SetBasePath(AppContext.BaseDirectory) - .AddJsonFile("appsettings.json", optional: true) - .AddEnvironmentVariables() - .Build(); - var services = new ServiceCollection() - .AddLogging() - .BuildServiceProvider(); + // tell backend service to uses specific host/ports if not already set + if (Environment.GetEnvironmentVariable("CERTIFY_SERVICE_HOST") == null) + { + Environment.SetEnvironmentVariable("CERTIFY_SERVICE_HOST", "127.0.0.1"); + } - var factory = services.GetService(); + if (Environment.GetEnvironmentVariable("CERTIFY_SERVICE_PORT") == null) + { + Environment.SetEnvironmentVariable("CERTIFY_SERVICE_PORT", "5000"); + } - var logger = factory.CreateLogger(); + // create a test server for the public API, setup authorized and unauthorized clients - _server = new TestServer( + _apiServer = new TestServer( new WebHostBuilder() .ConfigureAppConfiguration((context, builder) => { - builder - .AddJsonFile("appsettings.api.public.test.json"); + builder.AddJsonFile("appsettings.api.public.test.json"); + }) .UseStartup() ); + + _httpClientWithAnonymousAccess = _apiServer.CreateClient(); + _clientWithAnonymousAccess = new API.Public.Client(_apiServer.BaseAddress.ToString(), _httpClientWithAnonymousAccess); - _clientWithAnonymousAccess = _server.CreateClient(); - _clientWithAuthorizedAccess = _server.CreateClient(); + _httpClientWithAuthorizedAccess = _apiServer.CreateClient(); + _clientWithAuthorizedAccess = new API.Public.Client(_apiServer.BaseAddress.ToString(), _httpClientWithAuthorizedAccess); - // CreateCoreServer(); + CreateCoreServer(); } - private static void CreateCoreServer() + [AssemblyCleanup] + public static void AssemblyCleanup() { - // configure and start a custom instance of the core server for the public API to talk to - var builder = WebApplication.CreateBuilder(); + _serverProcess.CloseMainWindow(); + _serverProcess.Close(); + _serverProcess.Dispose(); + } - var coreServerStartup = new Certify.Server.Core.Startup(builder.Configuration); - - if (File.Exists(Path.Join(AppContext.BaseDirectory, "appsettings.worker.test.json"))) + static Process? _serverProcess = null; + private static void CreateCoreServer() + { + if (_serverProcess == null) { - builder.Configuration.AddJsonFile("appsettings.worker.test.json"); - builder.Configuration.AddUserSecrets(typeof(APITestBase).Assembly); // for worker pfx details + var serverProcessInfo = new ProcessStartInfo() + { + RedirectStandardInput = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true, + FileName = "Certify.Server.Core.exe", + Verb = "RunAs", + }; + + _serverProcess = Process.Start(serverProcessInfo); } - - builder.Logging.ClearProviders(); - builder.Logging.AddConsole(); - - coreServerStartup.ConfigureServices(builder.Services); - - var coreServerApp = builder.Build(); - - coreServerStartup.Configure(coreServerApp, builder.Environment); - - coreServerApp.StartAsync(); - } public async Task PerformAuth() { - if (!_clientWithAuthorizedAccess.DefaultRequestHeaders.Any(h => h.Key == "Authorization")) + if (!_httpClientWithAuthorizedAccess.DefaultRequestHeaders.Any(h => h.Key == "Authorization")) { - var login = new { username = "test", password = "test" }; + var login = new AuthRequest { Username = "test", Password = "test" }; - var payload = new StringContent(System.Text.Json.JsonSerializer.Serialize(login), System.Text.UnicodeEncoding.UTF8, "application/json"); + var result = await _clientWithAuthorizedAccess.LoginAsync(login); - var response = await _clientWithAuthorizedAccess.PostAsync(_apiBaseUri + "/auth/login", payload); - if (response.IsSuccessStatusCode) + if (result.AccessToken != null) { - var responseString = await response.Content.ReadAsStringAsync(); - var authResponse = System.Text.Json.JsonSerializer.Deserialize(responseString, _defaultJsonSerializerOptions); - - _refreshToken = authResponse.RefreshToken; + _refreshToken = result.RefreshToken; - _clientWithAuthorizedAccess.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", authResponse.AccessToken); + _httpClientWithAuthorizedAccess.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", result.AccessToken); } } } diff --git a/src/Certify.Server/Certify.Server.Api.Public.Tests/AuthTests.cs b/src/Certify.Server/Certify.Server.Api.Public.Tests/AuthTests.cs index 3972f20c9..01233a3de 100644 --- a/src/Certify.Server/Certify.Server.Api.Public.Tests/AuthTests.cs +++ b/src/Certify.Server/Certify.Server.Api.Public.Tests/AuthTests.cs @@ -1,5 +1,6 @@ using System.Net.Http; using System.Threading.Tasks; +using Certify.API.Public; using Certify.Models.API; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -11,48 +12,28 @@ public class AuthTests : APITestBase [TestMethod] public async Task GetAuthStatus_UnauthorizedTest() { - // Act - var response = await _clientWithAnonymousAccess.GetAsync(_apiBaseUri + "/auth/status"); - - // Assert - Assert.IsFalse(response.IsSuccessStatusCode, "Auth status should not be success"); - - Assert.AreEqual(System.Net.HttpStatusCode.Unauthorized, response.StatusCode, "Auth status should be Unauthorized"); + await Assert.ThrowsExceptionAsync(async () => await _clientWithAnonymousAccess.CheckAuthStatusAsync()); } [TestMethod] public async Task GetAuthStatus_AuthorizedTest() { - // Act await PerformAuth(); - var response = await _clientWithAuthorizedAccess.GetAsync(_apiBaseUri + "/auth/status"); - - // Assert - Assert.IsTrue(response.IsSuccessStatusCode, "Auth Status check should respond with success"); - - var responseString = await response.Content.ReadAsStringAsync(); - - Assert.AreEqual(System.Net.HttpStatusCode.OK, response.StatusCode, "Auth status code should respond with OK"); + try + { + await _clientWithAuthorizedAccess.CheckAuthStatusAsync(); + } + catch (ApiException exp) + { + Assert.Fail($"Auth status check should not throw exception: {exp.Message}"); + } } [TestMethod] public async Task GetAuthRefreshToken_UnauthorizedTest() { - // Act - - var payload = new StringContent( - System.Text.Json.JsonSerializer.Serialize(new { refreshToken = _refreshToken }), - System.Text.Encoding.UTF8, - "application/json" - ); - - var response = await _clientWithAnonymousAccess.PostAsync(_apiBaseUri + "/auth/refresh", payload); - - // Assert - Assert.IsFalse(response.IsSuccessStatusCode, "Auth refresh should not be success"); - - Assert.AreEqual(System.Net.HttpStatusCode.Unauthorized, response.StatusCode, "Auth refresh should be Unauthorized"); + await Assert.ThrowsExceptionAsync(async () => await _clientWithAnonymousAccess.RefreshAsync("auth123")); } [TestMethod] @@ -61,20 +42,10 @@ public async Task GetAuthRefreshToken_AuthorizedTest() // Act await PerformAuth(); - var payload = new StringContent( - System.Text.Json.JsonSerializer.Serialize(new { refreshToken = _refreshToken }), - System.Text.Encoding.UTF8, - "application/json" - ); - - var response = await _clientWithAuthorizedAccess.PostAsync(_apiBaseUri + "/auth/refresh", payload); + var response = await _clientWithAuthorizedAccess.RefreshAsync(_refreshToken); // Assert - Assert.IsTrue(response.IsSuccessStatusCode, "Auth refresh should respond with success"); - Assert.AreEqual(System.Net.HttpStatusCode.OK, response.StatusCode, "Auth refresh should respond with OK"); - - var responseString = await response.Content.ReadAsStringAsync(); - var authResponse = System.Text.Json.JsonSerializer.Deserialize(responseString, _defaultJsonSerializerOptions); + Assert.IsNotNull(response.AccessToken, "Auth refresh should respond with success"); } } diff --git a/src/Certify.Server/Certify.Server.Api.Public.Tests/CertificateTests.cs b/src/Certify.Server/Certify.Server.Api.Public.Tests/CertificateTests.cs index 498a8abc2..51754b32c 100644 --- a/src/Certify.Server/Certify.Server.Api.Public.Tests/CertificateTests.cs +++ b/src/Certify.Server/Certify.Server.Api.Public.Tests/CertificateTests.cs @@ -1,9 +1,13 @@ using System.Collections.Generic; +using System.IO; using System.Linq; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; +using Certify.API.Public; +using Certify.Models; using Certify.Models.API; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Org.BouncyCastle.Utilities; namespace Certify.Service.Api.Tests { @@ -13,13 +17,8 @@ public class CertificateTests : APITestBase [TestMethod] public async Task GetCertificates_UnauthorizedTest() { - // Act - var response = await _clientWithAnonymousAccess.GetAsync(_apiBaseUri + "/certificate"); - - // Assert - Assert.IsFalse(response.IsSuccessStatusCode); + await Assert.ThrowsExceptionAsync(async () => await _clientWithAnonymousAccess.GetManagedCertificatesAsync("", 0, 10)); - Assert.AreEqual(System.Net.HttpStatusCode.Unauthorized, response.StatusCode); } [TestMethod] @@ -28,19 +27,14 @@ public async Task GetCertificates_AuthorizedTest() // Act await PerformAuth(); - var response = await _clientWithAuthorizedAccess.GetAsync(_apiBaseUri + "/certificate"); - var responseString = await response.Content.ReadAsStringAsync(); + var response = await _clientWithAuthorizedAccess.GetManagedCertificatesAsync("", 0, 10); // Assert - Assert.IsTrue(response.IsSuccessStatusCode); - - Assert.AreEqual(System.Net.HttpStatusCode.OK, response.StatusCode); - - var managedCerts = System.Text.Json.JsonSerializer.Deserialize>(responseString, _defaultJsonSerializerOptions); + Assert.IsTrue(response.TotalResults > 0); - Assert.IsNotNull(managedCerts); + Assert.IsNotNull(response.Results); - Assert.IsNotNull(managedCerts[0].Id); + Assert.IsNotNull(response.Results.First().Id); } [TestMethod] @@ -49,29 +43,34 @@ public async Task GetCertificateDownload_AuthorizedTest() // Act await PerformAuth(); - var response = await _clientWithAuthorizedAccess.GetAsync(_apiBaseUri + "/certificate"); + var response = await _clientWithAuthorizedAccess.GetManagedCertificatesAsync("", 0, 10); - Assert.IsTrue(response.IsSuccessStatusCode, "Certificate query should be successful"); + Assert.IsTrue(response.TotalResults > 0, "Certificate query should be successful"); - var responseString = await response.Content.ReadAsStringAsync(); - var managedCerts = System.Text.Json.JsonSerializer.Deserialize>(responseString, _defaultJsonSerializerOptions); - - var itemWithCert = managedCerts.First(c => c.DateRenewed != null); + var itemWithCert = response.Results.Last(c => c.HasCertificate && c.Identifiers.Any(d=>d.IdentifierType== CertIdentifierType.Dns)); // get cert /certificate/{managedCertId}/download/{format?} - var certDownloadResponse = await _clientWithAuthorizedAccess.GetAsync(_apiBaseUri + "/certificate/" + itemWithCert.Id + "/download/"); + var file = await _clientWithAuthorizedAccess.DownloadAsync(itemWithCert.Id, "pfx","fullchain"); // Assert - Assert.IsTrue(certDownloadResponse.IsSuccessStatusCode); - - var certResponseBytes = await certDownloadResponse.Content.ReadAsByteArrayAsync(); - - var cert = new X509Certificate2(certResponseBytes); - - Assert.IsTrue(cert.HasPrivateKey, "Downloaded PFX has private key"); - - Assert.AreEqual(cert.Subject, "CN=" + itemWithCert.PrimaryIdentifier.Value, "Primary domain of cert should match primary domain of managed item"); + using (var memoryStream = new MemoryStream()) + { + file.Stream.CopyTo(memoryStream); + var certResponseBytes = memoryStream.ToArray(); + + try + { + var cert = new X509Certificate2(certResponseBytes); + + Assert.IsTrue(cert.HasPrivateKey, "Downloaded PFX has private key"); + + Assert.AreEqual(cert.Subject, "CN=" + itemWithCert.PrimaryIdentifier.Value, "Primary domain of cert should match primary domain of managed item"); + } catch (System.Security.Cryptography.CryptographicException) + { + // pfx has a password set + } + } } } } diff --git a/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj b/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj index 864cf5cbe..d4b3e9bc7 100644 --- a/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj +++ b/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj @@ -1,44 +1,30 @@ - - - - net8.0 - - false - - - - - - - - - - PreserveNewest - true - PreserveNewest - - - PreserveNewest - true - PreserveNewest - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - + + + + net8.0 + + false + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + diff --git a/src/Certify.Server/Certify.Server.Api.Public.Tests/SystemTests.cs b/src/Certify.Server/Certify.Server.Api.Public.Tests/SystemTests.cs index 2d9772503..00bc12ede 100644 --- a/src/Certify.Server/Certify.Server.Api.Public.Tests/SystemTests.cs +++ b/src/Certify.Server/Certify.Server.Api.Public.Tests/SystemTests.cs @@ -10,15 +10,13 @@ public class SystemTests : APITestBase [TestMethod] public async Task GetSystemVersionTest() { + await PerformAuth(); - // Act - var response = await _clientWithAnonymousAccess.GetAsync("/api/v1/system/version"); - response.EnsureSuccessStatusCode(); - var responseString = await response.Content.ReadAsStringAsync(); - + var responseVersion = await _clientWithAuthorizedAccess.GetSystemVersionAsync(); + // Assert - var expectedVersion = typeof(Certify.Models.AppVersion).GetTypeInfo().Assembly.GetName().Version.ToString(); - Assert.AreEqual(expectedVersion, responseString); + var expectedVersion = typeof(Certify.Models.AppVersion).GetTypeInfo().Assembly.GetName().Version; + Assert.AreEqual(expectedVersion, responseVersion); } } diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs index 759c1e3de..1974fb214 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs @@ -40,7 +40,9 @@ public CertificateController(ILogger logger, ICertifyInte [HttpGet] [Route("{managedCertId}/download/{format?}")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] - public async Task Download(string managedCertId, string format, string mode) + + [ProducesResponseType(typeof(FileContentResult), 200)] + public async Task Download(string managedCertId, string format, string? mode = null) { if (format == null) { diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/SystemController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/SystemController.cs index 8f5ea4adf..5fced44fd 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/SystemController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/SystemController.cs @@ -1,4 +1,4 @@ -using Certify.Client; +using Certify.Client; using Microsoft.AspNetCore.Mvc; namespace Certify.Server.Api.Public.Controllers @@ -32,12 +32,12 @@ public SystemController(ILogger logger, ICertifyInternalApiCli /// [HttpGet] [Route("version")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Version))] public async Task GetSystemVersion() { - var versionInfo = await _client.GetAppVersion(); - return new OkObjectResult(versionInfo); + return new OkObjectResult(Version.Parse(versionInfo)); } /// @@ -46,6 +46,7 @@ public async Task GetSystemVersion() /// [HttpGet] [Route("health")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(object))] public async Task GetHealth() { var serviceAvailable = false; diff --git a/src/Certify.Server/Certify.Server.Api.Public/Startup.cs b/src/Certify.Server/Certify.Server.Api.Public/Startup.cs index 675df3c9a..56e3f4817 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Startup.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Startup.cs @@ -1,10 +1,12 @@ -using System.Reflection; +using System.Reflection; using Certify.Client; using Certify.Models.Config; using Certify.Server.Api.Public.Middleware; using Certify.SharedUtils; +using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.ResponseCompression; using Microsoft.AspNetCore.SignalR; +using Microsoft.Extensions.Options; using Microsoft.OpenApi.Models; using Polly; @@ -40,9 +42,9 @@ public void ConfigureServices(IServiceCollection services) /// Configure services for use by the API /// /// - public List ConfigureServicesWithResults(IServiceCollection services) + public List ConfigureServicesWithResults(IServiceCollection services) { - var results = new List(); + var results = new List(); services .AddTokenAuthentication(Configuration) @@ -121,6 +123,15 @@ public List ConfigureServicesWithResults(IServiceCollection servic var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile); c.IncludeXmlComments(xmlPath); + c.MapType(() => + { + return new Microsoft.OpenApi.Models.OpenApiSchema + { + Type = "string", + Format = "binary", + }; + }); + }); #endif // connect to certify service @@ -146,7 +157,7 @@ public List ConfigureServicesWithResults(IServiceCollection servic backendServiceConnectionConfig.Authentication = "jwt"; backendServiceConnectionConfig.ServerMode = "v2"; - System.Diagnostics.Debug.WriteLine($"Public API: connecting to background service {serviceConfig:Host}:{serviceConfig.Port}"); + System.Diagnostics.Debug.WriteLine($"Public API: connecting to background service {serviceConfig.Host}:{serviceConfig.Port}"); var internalServiceClient = new Client.CertifyServiceClient(configManager, backendServiceConnectionConfig); diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj index a0d19b7ec..176fa7e38 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj @@ -29,4 +29,7 @@ + + + \ No newline at end of file diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Program.cs b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Program.cs index b8586329d..82ffc5063 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Program.cs +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Program.cs @@ -12,3 +12,8 @@ startup.Configure(app, builder.Environment); app.Run(); + +/// +/// Declare program as partial for reference in tests: https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-8.0 +/// +public partial class Program { } diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Startup.cs b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Startup.cs index f619b52da..692e23be1 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Startup.cs +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Startup.cs @@ -1,4 +1,5 @@ -using Certify.Management; +using System.Text; +using Certify.Management; using Microsoft.AspNetCore.ResponseCompression; using Microsoft.AspNetCore.SignalR; using Microsoft.OpenApi.Models; @@ -154,8 +155,24 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { endpoints.MapHub("/api/status"); endpoints.MapControllers(); - }); +#if DEBUG + endpoints.MapGet("/debug/routes", (IEnumerable endpointSources) => + { + var sb = new StringBuilder(); + var endpoints = endpointSources.SelectMany(es => es.Endpoints); + foreach (var endpoint in endpoints) + { + if (endpoint is RouteEndpoint routeEndpoint) + { + sb.AppendLine($"{routeEndpoint.DisplayName} {routeEndpoint.RoutePattern.RawText}"); + } + } + + return sb.ToString(); + }); +#endif + }); } } } From c370c20d4171c449558fb780e3a69e3de0715525 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 1 Feb 2024 17:29:58 +0800 Subject: [PATCH 117/328] Package updates --- src/Certify.Client/Certify.Client.csproj | 2 +- src/Certify.Models/Certify.Models.csproj | 2 +- .../DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj | 2 +- .../Certify.Server.Api.Public.Tests.csproj | 4 ++-- src/Certify.Service/Certify.Service.csproj | 2 +- .../Certify.Core.Tests.Integration.csproj | 4 ++-- .../Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj | 4 ++-- .../Certify.Service.Tests.Integration.csproj | 4 ++-- .../Certify.UI.Tests.Integration.csproj | 4 ++-- 9 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/Certify.Client/Certify.Client.csproj b/src/Certify.Client/Certify.Client.csproj index 5c6e9143f..b5709ac68 100644 --- a/src/Certify.Client/Certify.Client.csproj +++ b/src/Certify.Client/Certify.Client.csproj @@ -18,7 +18,7 @@ - + diff --git a/src/Certify.Models/Certify.Models.csproj b/src/Certify.Models/Certify.Models.csproj index 698d5f42e..05e517d02 100644 --- a/src/Certify.Models/Certify.Models.csproj +++ b/src/Certify.Models/Certify.Models.csproj @@ -21,7 +21,7 @@ all runtime; build; native; contentfiles; analyzers - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj index 70d4119e1..aaf4c29c7 100644 --- a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj +++ b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj b/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj index d4b3e9bc7..d653b65d0 100644 --- a/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj +++ b/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj @@ -13,8 +13,8 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Certify.Service/Certify.Service.csproj b/src/Certify.Service/Certify.Service.csproj index d92277c5a..7502b7828 100644 --- a/src/Certify.Service/Certify.Service.csproj +++ b/src/Certify.Service/Certify.Service.csproj @@ -54,7 +54,7 @@ - + diff --git a/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj b/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj index 0dd1e195e..d6707e1fa 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj @@ -93,8 +93,8 @@ - - + + diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj index 1c9f74d96..10cefad7d 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj @@ -144,8 +144,8 @@ - - + + diff --git a/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj b/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj index bde2a31f4..d5d1439a4 100644 --- a/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj @@ -78,8 +78,8 @@ - - + + diff --git a/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj b/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj index 2a5f58f27..d667c800e 100644 --- a/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj @@ -74,8 +74,8 @@ - - + + From a7eacc7db455536411af1fcfa50130a1f91e0e8d Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 1 Feb 2024 17:32:30 +0800 Subject: [PATCH 118/328] Security principle and access control updates --- .../Management/Access/AccessControl.cs | 106 +++++- .../Management/Access/IAccessControl.cs | 7 +- .../CertifyManager/CertifyManager.cs | 67 ++++ src/Certify.Models/API/AuthRequest.cs | 42 ++- src/Certify.Models/Config/AccessControl.cs | 2 + .../Certify.API.Public.cs | 354 ++++++++++++++++++ .../APITestBase.cs | 7 +- .../AccessTests.cs | 67 ++++ .../Controllers/v1/AuthController.cs | 77 ++-- .../Controllers/AccessController.cs | 118 +++--- .../Controllers/AuthController.cs | 55 +++ .../Controllers/AuthController.cs | 75 ---- .../DataStores/AccessControlDataStoreTests.cs | 7 +- .../Tests/AccessControlTests.cs | 217 +++++------ 14 files changed, 906 insertions(+), 295 deletions(-) create mode 100644 src/Certify.Server/Certify.Server.Api.Public.Tests/AccessTests.cs create mode 100644 src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AuthController.cs delete mode 100644 src/Certify.Service/Controllers/AuthController.cs diff --git a/src/Certify.Core/Management/Access/AccessControl.cs b/src/Certify.Core/Management/Access/AccessControl.cs index f73d9baa1..8fe49bbea 100644 --- a/src/Certify.Core/Management/Access/AccessControl.cs +++ b/src/Certify.Core/Management/Access/AccessControl.cs @@ -2,10 +2,15 @@ using System.Collections.Generic; using System.Linq; using System.Security.Cryptography; +using System.Text; +using System.Text.Unicode; using System.Threading.Tasks; +using Certify.Models.API; using Certify.Models.Config.AccessControl; using Certify.Models.Providers; using Certify.Providers; +using Org.BouncyCastle.Tls; +using static System.Net.WebRequestMethods; namespace Certify.Core.Management.Access { @@ -21,6 +26,23 @@ public AccessControl(ILog log, IAccessControlStore store) _log = log; } + /// + /// Check if the system has been initialized with a security principle + /// + /// + public async Task IsInitialized() + { + var list = await GetSecurityPrinciples("system"); + if (list.Count != 0) + { + return true; + } + else + { + return false; + } + } + public async Task> GetSystemRoles() { return await Task.FromResult(new List @@ -116,7 +138,28 @@ public async Task DeleteSecurityPrinciple(string contextUserId, string id, public async Task GetSecurityPrinciple(string contextUserId, string id) { - return await _store.Get(nameof(SecurityPrinciple), id); + try + { + return await _store.Get(nameof(SecurityPrinciple), id); + } + catch (Exception exp) + { + _log.Error(exp, $"User {contextUserId} attempted to retrieve security principle [{id}] but was not successful"); + + return default; + } + } + + public async Task GetSecurityPrincipleByUsername(string contextUserId, string username) + { + if (string.IsNullOrWhiteSpace(username)) + { + return default; + } + + var list = await GetSecurityPrinciples(contextUserId); + + return list?.SingleOrDefault(sp => sp.Username?.ToLowerInvariant() == username.ToLowerInvariant()); } public async Task IsAuthorised(string contextUserId, string principleId, string roleId, string resourceType, string actionId, string identifier) @@ -217,21 +260,21 @@ public async Task AddResourcePolicy(string contextUserId, ResourcePolicy r return true; } - public async Task UpdateSecurityPrinciplePassword(string contextUserId, string id, string oldpassword, string newpassword) + public async Task UpdateSecurityPrinciplePassword(string contextUserId, SecurityPrinciplePasswordUpdate passwordUpdate) { - if (id != contextUserId && !await IsPrincipleInRole(contextUserId, contextUserId, StandardRoles.Administrator.Id)) + if (passwordUpdate.SecurityPrincipleId != contextUserId && !await IsPrincipleInRole(contextUserId, contextUserId, StandardRoles.Administrator.Id)) { - _log?.Warning("User {contextUserId} attempted to use updated password for [{id}] without being in required role.", contextUserId, id); + _log?.Warning("User {contextUserId} attempted to use updated password for [{id}] without being in required role.", contextUserId, passwordUpdate.SecurityPrincipleId); return false; } var updated = false; - var principle = await GetSecurityPrinciple(contextUserId, id); + var principle = await GetSecurityPrinciple(contextUserId, passwordUpdate.SecurityPrincipleId); - if (IsPasswordValid(oldpassword, principle.Password)) + if (IsPasswordValid(passwordUpdate.Password, principle.Password)) { - principle.Password = HashPassword(newpassword); + principle.Password = HashPassword(passwordUpdate.NewPassword); updated = await UpdateSecurityPrinciple(contextUserId, principle); } else @@ -254,6 +297,11 @@ public async Task UpdateSecurityPrinciplePassword(string contextUserId, st public bool IsPasswordValid(string password, string currentHash) { + if (string.IsNullOrWhiteSpace(currentHash) && string.IsNullOrWhiteSpace(password)) + { + return true; + } + var components = currentHash.Split('.'); // hash provided password with same salt to compare result @@ -309,7 +357,6 @@ public async Task AddAction(ResourceAction action) public async Task> GetAssignedRoles(string contextUserId, string id) { - if (id != contextUserId && !await IsPrincipleInRole(contextUserId, contextUserId, StandardRoles.Administrator.Id)) { _log?.Warning("User {contextUserId} attempted to read assigned role for [{id}] without being in required role.", contextUserId, id); @@ -320,5 +367,48 @@ public async Task> GetAssignedRoles(string contextUserId, str return assignedRoles.Where(r => r.SecurityPrincipleId == id).ToList(); } + + public async Task CheckSecurityPrinciplePassword(string contextUserId, SecurityPrinciplePasswordCheck passwordCheck) + { + var principle = string.IsNullOrWhiteSpace(passwordCheck.SecurityPrincipleId) ? + await GetSecurityPrincipleByUsername(contextUserId, passwordCheck.Username) : + await GetSecurityPrinciple(contextUserId, passwordCheck.SecurityPrincipleId); + + if (principle != null && IsPasswordValid(passwordCheck.Password, principle.Password)) + { + principle.AvatarUrl = string.IsNullOrWhiteSpace(principle.Email) ? "https://gravatar.com/avatar/00000000000000000000000000000000" : $"https://gravatar.com/avatar/{GetSHA256Hash(principle.Email.Trim().ToLower())}"; + return new SecurityPrincipleCheckResponse { IsSuccess = true, SecurityPrinciple = principle }; + } + else + { + if (principle == null) + { + return new SecurityPrincipleCheckResponse { IsSuccess = false, Message = "Invalid security principle" }; + } + else + { + return new SecurityPrincipleCheckResponse { IsSuccess = false, Message = "Invalid password" }; + } + } + } + + public string GetSHA256Hash(string val) + { + using (var sha256Hash = SHA256.Create()) + { + byte[] data = sha256Hash.ComputeHash(Encoding.UTF8.GetBytes(val)); + var sBuilder = new StringBuilder(); + + // Loop through each byte of the hashed data + // and format each one as a hexadecimal string. + for (int i = 0; i < data.Length; i++) + { + sBuilder.Append(data[i].ToString("x2")); + } + + // Return the hexadecimal string. + return sBuilder.ToString(); + } + } } } diff --git a/src/Certify.Core/Management/Access/IAccessControl.cs b/src/Certify.Core/Management/Access/IAccessControl.cs index b5a4b8c2a..26e12e5f9 100644 --- a/src/Certify.Core/Management/Access/IAccessControl.cs +++ b/src/Certify.Core/Management/Access/IAccessControl.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Threading.Tasks; +using Certify.Models.API; using Certify.Models.Config.AccessControl; namespace Certify.Core.Management.Access @@ -21,12 +22,12 @@ public interface IAccessControl Task IsPrincipleInRole(string contextUserId, string id, string roleId); Task> GetAssignedRoles(string contextUserId, string id); Task UpdateSecurityPrinciple(string contextUserId, SecurityPrinciple principle); - Task UpdateSecurityPrinciplePassword(string contextUserId, string id, string oldpassword, string newpassword); + Task UpdateSecurityPrinciplePassword(string contextUserId, SecurityPrinciplePasswordUpdate passwordUpdate); + Task CheckSecurityPrinciplePassword(string contextUserId, SecurityPrinciplePasswordCheck passwordCheck); Task AddRole(Role role); Task AddAssignedRole(AssignedRole assignedRole); Task AddAction(ResourceAction action); - - + Task IsInitialized(); } } diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.cs index 505aecd3a..d3b3bc8a0 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.cs @@ -11,6 +11,7 @@ using Certify.Core.Management.Challenges; using Certify.Datastore.SQLite; using Certify.Models; +using Certify.Models.Config.AccessControl; using Certify.Models.Config.Migration; using Certify.Models.Providers; using Certify.Providers; @@ -192,6 +193,72 @@ public async Task Init() } await UpgradeSettings(); + + var accessControl = await GetCurrentAccessControl(); + + if (await accessControl.IsInitialized() == false) + { + BootstrapTestAdminUserAndRoles(accessControl).Wait(); + } + } + + private static async Task BootstrapTestAdminUserAndRoles(IAccessControl access) + { + + var adminSp = new SecurityPrinciple + { + Id = "admin_01", + Email = "admin@test.com", + Description = "Primary test admin", + PrincipleType = SecurityPrincipleType.User, + Username = "admin", + Password = "admin", + Provider = StandardProviders.INTERNAL + }; + + await access.AddSecurityPrinciple(adminSp.Id, adminSp, bypassIntegrityCheck: true); + + var actions = Policies.GetStandardResourceActions(); + + foreach (var action in actions) + { + await access.AddAction(action); + } + + // setup policies with actions + + var policies = Policies.GetStandardPolicies(); + + // add policies to store + foreach (var r in policies) + { + _ = await access.AddResourcePolicy(adminSp.Id, r, bypassIntegrityCheck: true); + } + + // setup roles with policies + var roles = await access.GetSystemRoles(); + + foreach (var r in roles) + { + // add roles and policy assignments to store + await access.AddRole(r); + } + + // assign security principles to roles + var assignedRoles = new List { + // administrator + new AssignedRole{ + Id= Guid.NewGuid().ToString(), + RoleId=StandardRoles.Administrator.Id, + SecurityPrincipleId=adminSp.Id + } + }; + + foreach (var r in assignedRoles) + { + // add roles and policy assignments to store + await access.AddAssignedRole(r); + } } /// diff --git a/src/Certify.Models/API/AuthRequest.cs b/src/Certify.Models/API/AuthRequest.cs index f04c1c8fa..5f65580bb 100644 --- a/src/Certify.Models/API/AuthRequest.cs +++ b/src/Certify.Models/API/AuthRequest.cs @@ -1,4 +1,6 @@ -namespace Certify.Models.API +using Certify.Models.Config.AccessControl; + +namespace Certify.Models.API { /// /// Required info to begin auth @@ -37,5 +39,43 @@ public class AuthResponse /// Refresh token string /// public string RefreshToken { get; set; } = string.Empty; + + public Models.Config.AccessControl.SecurityPrinciple? SecurityPrinciple { get; set; } + } + + public class SecurityPrinciplePasswordCheck + { + public string SecurityPrincipleId { get; set; } = string.Empty; + public string Username { get; set; } = string.Empty; + public string Password { get; set; } = string.Empty; + + public SecurityPrinciplePasswordCheck() { } + public SecurityPrinciplePasswordCheck(string securityPrincipleId, string password) + { + SecurityPrincipleId = securityPrincipleId; + Password = password; + } + } + + public class SecurityPrincipleCheckResponse + { + public bool IsSuccess { get; set; } + public string Message { get; set; } = string.Empty; + public SecurityPrinciple SecurityPrinciple { get; set; } + } + + public class SecurityPrinciplePasswordUpdate + { + public string SecurityPrincipleId { get; set; } = string.Empty; + public string Password { get; set; } = string.Empty; + public string NewPassword { get; set; } = string.Empty; + + public SecurityPrinciplePasswordUpdate() { } + public SecurityPrinciplePasswordUpdate(string securityPrincipleId, string password, string newPassword) + { + SecurityPrincipleId = securityPrincipleId; + Password = password; + NewPassword = newPassword; + } } } diff --git a/src/Certify.Models/Config/AccessControl.cs b/src/Certify.Models/Config/AccessControl.cs index 4a8a4830d..ee52869ca 100644 --- a/src/Certify.Models/Config/AccessControl.cs +++ b/src/Certify.Models/Config/AccessControl.cs @@ -38,6 +38,8 @@ public class SecurityPrinciple : AccessStoreItem public SecurityPrincipleType? PrincipleType { get; set; } public string? AuthKey { get; set; } + + public string AvatarUrl { get; set; } = string.Empty; } public class Role : AccessStoreItem diff --git a/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs b/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs index 3abb3f06c..8d73daaa4 100644 --- a/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs +++ b/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs @@ -326,6 +326,360 @@ public string BaseUrl } } + /// + /// Check password valid for security principle [Generated by Certify.SourceGenerators] + /// + /// Success + /// A server side error occurred. + public virtual System.Threading.Tasks.Task ValidateSecurityPrinciplePasswordAsync(SecurityPrinciplePasswordCheck body) + { + return ValidateSecurityPrinciplePasswordAsync(body, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Check password valid for security principle [Generated by Certify.SourceGenerators] + /// + /// Success + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task ValidateSecurityPrinciplePasswordAsync(SecurityPrinciplePasswordCheck body, System.Threading.CancellationToken cancellationToken) + { + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value); + var content_ = new System.Net.Http.StringContent(json_); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + // Operation Path: "internal/v1/access/validate" + urlBuilder_.Append("internal/v1/access/validate"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// + /// Update password for security principle [Generated by Certify.SourceGenerators] + /// + /// Success + /// A server side error occurred. + public virtual System.Threading.Tasks.Task UpdateSecurityPrinciplePasswordAsync(SecurityPrinciplePasswordUpdate body) + { + return UpdateSecurityPrinciplePasswordAsync(body, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Update password for security principle [Generated by Certify.SourceGenerators] + /// + /// Success + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task UpdateSecurityPrinciplePasswordAsync(SecurityPrinciplePasswordUpdate body, System.Threading.CancellationToken cancellationToken) + { + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value); + var content_ = new System.Net.Http.StringContent(json_); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + // Operation Path: "internal/v1/access/updatepassword" + urlBuilder_.Append("internal/v1/access/updatepassword"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// + /// Add new security principle [Generated by Certify.SourceGenerators] + /// + /// Success + /// A server side error occurred. + public virtual System.Threading.Tasks.Task AddSecurityPrincipleAsync(SecurityPrinciple body) + { + return AddSecurityPrincipleAsync(body, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Add new security principle [Generated by Certify.SourceGenerators] + /// + /// Success + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task AddSecurityPrincipleAsync(SecurityPrinciple body, System.Threading.CancellationToken cancellationToken) + { + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value); + var content_ = new System.Net.Http.StringContent(json_); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + // Operation Path: "internal/v1/access/securityprinciple" + urlBuilder_.Append("internal/v1/access/securityprinciple"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// + /// Delete security principle [Generated by Certify.SourceGenerators] + /// + /// Success + /// A server side error occurred. + public virtual System.Threading.Tasks.Task DeleteSecurityPrincipleAsync(string id) + { + return DeleteSecurityPrincipleAsync(id, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Delete security principle [Generated by Certify.SourceGenerators] + /// + /// Success + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task DeleteSecurityPrincipleAsync(string id, System.Threading.CancellationToken cancellationToken) + { + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("DELETE"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + // Operation Path: "internal/v1/access/securityprinciple" + urlBuilder_.Append("internal/v1/access/securityprinciple"); + urlBuilder_.Append('?'); + if (id != null) + { + urlBuilder_.Append(System.Uri.EscapeDataString("id")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(id, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); + } + urlBuilder_.Length--; + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + /// /// Operations to check current auth status for the given presented authentication tokens /// diff --git a/src/Certify.Server/Certify.Server.Api.Public.Tests/APITestBase.cs b/src/Certify.Server/Certify.Server.Api.Public.Tests/APITestBase.cs index fc469204e..7d4a25c43 100644 --- a/src/Certify.Server/Certify.Server.Api.Public.Tests/APITestBase.cs +++ b/src/Certify.Server/Certify.Server.Api.Public.Tests/APITestBase.cs @@ -40,7 +40,6 @@ public static void AssemblyInit(TestContext context) { // setup public API service and backend service - // tell backend service to uses specific host/ports if not already set if (Environment.GetEnvironmentVariable("CERTIFY_SERVICE_HOST") == null) { @@ -88,13 +87,9 @@ private static void CreateCoreServer() { var serverProcessInfo = new ProcessStartInfo() { - RedirectStandardInput = false, - RedirectStandardOutput = true, - RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true, - FileName = "Certify.Server.Core.exe", - Verb = "RunAs", + FileName = "Certify.Server.Core.exe" }; _serverProcess = Process.Start(serverProcessInfo); diff --git a/src/Certify.Server/Certify.Server.Api.Public.Tests/AccessTests.cs b/src/Certify.Server/Certify.Server.Api.Public.Tests/AccessTests.cs new file mode 100644 index 000000000..1979d62af --- /dev/null +++ b/src/Certify.Server/Certify.Server.Api.Public.Tests/AccessTests.cs @@ -0,0 +1,67 @@ +using System.Net.Http; +using System.Threading.Tasks; +using Certify.API.Public; +using Certify.Models.API; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Certify.Service.Api.Tests +{ + [TestClass] + public class AccessTests : APITestBase + { + + [TestMethod] + public async Task AddDeleteSecurityPrinciple() + { + // Act + await PerformAuth(); + + var sp = new Models.Config.AccessControl.SecurityPrinciple + { + Id = "apitest_user", + Password = "test" + }; + + var add = await _clientWithAuthorizedAccess.AddSecurityPrincipleAsync(sp); + + Assert.IsTrue(add.IsSuccess); + + var delete = await _clientWithAuthorizedAccess.DeleteSecurityPrincipleAsync(sp.Id); + + Assert.IsTrue(delete.IsSuccess); + } + + [TestMethod] + public async Task AddUpdateDeleteSecurityPrinciple() + { + // Act + await PerformAuth(); + + var sp = new Models.Config.AccessControl.SecurityPrinciple + { + Id = "apitest_user", + Password = "test" + }; + + var add = await _clientWithAuthorizedAccess.AddSecurityPrincipleAsync(sp); + + Assert.IsTrue(add.IsSuccess); + + var validation = await _clientWithAuthorizedAccess.ValidateSecurityPrinciplePasswordAsync(new SecurityPrinciplePasswordCheck(sp.Id, sp.Password)); + + Assert.IsTrue(validation.IsSuccess); + + var update = await _clientWithAuthorizedAccess.UpdateSecurityPrinciplePasswordAsync(new SecurityPrinciplePasswordUpdate(sp.Id, sp.Password, "newpwd")); + + Assert.IsTrue(update.IsSuccess); + + var updateValidation = await _clientWithAuthorizedAccess.ValidateSecurityPrinciplePasswordAsync(new SecurityPrinciplePasswordCheck(sp.Id, "newpwd")); + + Assert.IsTrue(updateValidation.IsSuccess); + + var delete = await _clientWithAuthorizedAccess.DeleteSecurityPrincipleAsync(sp.Id); + + Assert.IsTrue(delete.IsSuccess); + } + } +} diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/AuthController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/AuthController.cs index e51c31c73..a6f0213a9 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/AuthController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/AuthController.cs @@ -1,6 +1,7 @@ using System.Net.Http.Headers; using Certify.Client; using Certify.Models.API; +using Certify.Models.Config.AccessControl; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -50,21 +51,34 @@ public async Task CheckAuthStatus() /// Response contains access token and refresh token for API operations. [HttpPost] [Route("login")] - public AuthResponse Login(AuthRequest login) + [ProducesResponseType(typeof(AuthResponse), 200)] + public async Task Login(AuthRequest login) { - // TODO: check users login, if valid issue new JWT access token and refresh token based on their identity - // Refresh token should be stored or hashed for later use + // check users login, if valid issue new JWT access token and refresh token based on their identity + var validation = await _client.ValidateSecurityPrinciplePassword(new SecurityPrinciplePasswordCheck() { Username = login.Username, Password = login.Password }); - var jwt = new Api.Public.Services.JwtService(_config); - - var authResponse = new AuthResponse + if (validation.IsSuccess) { - Detail = "OK", - AccessToken = jwt.GenerateSecurityToken(login.Username, double.Parse(_config["JwtSettings:authTokenExpirationInMinutes"])), - RefreshToken = jwt.GenerateRefreshToken() - }; + // TODO: get user details from API and return as part of response instead of returning as json + + var jwt = new Api.Public.Services.JwtService(_config); + + var authResponse = new AuthResponse + { + Detail = "OK", + AccessToken = jwt.GenerateSecurityToken(login.Username, double.Parse(_config["JwtSettings:authTokenExpirationInMinutes"])), + RefreshToken = jwt.GenerateRefreshToken(), + SecurityPrinciple = validation.SecurityPrinciple + }; + + // TODO: Refresh token should be stored or hashed for later use - return authResponse; + return Ok(authResponse); + } + else + { + return Unauthorized(); + } } /// @@ -75,34 +89,39 @@ public AuthResponse Login(AuthRequest login) [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] [HttpPost] [Route("refresh")] - public AuthResponse Refresh(string refreshToken) + [ProducesResponseType(typeof(AuthResponse), 200)] + public IActionResult Refresh(string refreshToken) { // validate token and issue new one var jwt = new Api.Public.Services.JwtService(_config); var authToken = AuthenticationHeaderValue.Parse(Request.Headers["Authorization"]).Parameter; - var claimsIdentity = jwt.ClaimsIdentityFromToken(authToken, false); - var username = claimsIdentity.Name; - // var savedRefreshToken = GetRefreshToken(username); //retrieve the refresh token from a data store - // if (savedRefreshToken != refreshToken) - // throw new SecurityTokenException("Invalid refresh token"); + try + { + var claimsIdentity = jwt.ClaimsIdentityFromToken(authToken, false); + var username = claimsIdentity.Name; + + var newJwtToken = jwt.GenerateSecurityToken(username, double.Parse(_config["JwtSettings:authTokenExpirationInMinutes"])); + var newRefreshToken = jwt.GenerateRefreshToken(); - var newJwtToken = jwt.GenerateSecurityToken(username, double.Parse(_config["JwtSettings:authTokenExpirationInMinutes"])); - var newRefreshToken = jwt.GenerateRefreshToken(); + // invalidate old refresh token and store new one + // DeleteRefreshToken(username, refreshToken); + // SaveRefreshToken(username, newRefreshToken); - // invalidate old refresh token and store new one - // DeleteRefreshToken(username, refreshToken); - // SaveRefreshToken(username, newRefreshToken); + var authResponse = new AuthResponse + { + Detail = "OK", + AccessToken = newJwtToken, + RefreshToken = newRefreshToken + }; - var authResponse = new AuthResponse + return Ok(authResponse); + } + catch { - Detail = "OK", - AccessToken = newJwtToken, - RefreshToken = newRefreshToken - }; - - return authResponse; + return Unauthorized(); + } } } } diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccessController.cs b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccessController.cs index a6882a580..a5bb6f78e 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccessController.cs +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccessController.cs @@ -1,6 +1,8 @@ using Certify.Core.Management.Access; using Certify.Management; +using Certify.Models.API; using Certify.Models.Config.AccessControl; +using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Mvc; namespace Certify.Service.Controllers @@ -10,6 +12,13 @@ namespace Certify.Service.Controllers public class AccessController : ControllerBase { private ICertifyManager _certifyManager; + private IDataProtectionProvider _dataProtectionProvider; + + public AccessController(ICertifyManager certifyManager, IDataProtectionProvider dataProtectionProvider) + { + _certifyManager = certifyManager; + _dataProtectionProvider = dataProtectionProvider; + } private string GetContextUserId() { @@ -26,72 +35,32 @@ private string GetContextUserId() return contextUserId; } - public AccessController(ICertifyManager certifyManager) - { - _certifyManager = certifyManager; - } - -#if DEBUG - private async Task BootstrapTestAdminUserAndRoles(IAccessControl access) + [HttpPost, Route("securityprinciple")] + public async Task AddSecurityPrinciple([FromBody] SecurityPrinciple principle) { + var accessControl = await _certifyManager.GetCurrentAccessControl(); + var addResultOk = await accessControl.AddSecurityPrinciple(GetContextUserId(), principle); - var adminSp = new SecurityPrinciple + return new Models.Config.ActionResult { - Id = "admin_01", - Email = "admin@test.com", - Description = "Primary test admin", - PrincipleType = SecurityPrincipleType.User, - Username = "admin", - Provider = StandardProviders.INTERNAL + IsSuccess = addResultOk, + Message = addResultOk ? "Added" : "Failed to add" }; + } - await access.AddSecurityPrinciple(adminSp.Id, adminSp, bypassIntegrityCheck: true); - - var actions = Policies.GetStandardResourceActions(); - - foreach (var action in actions) - { - await access.AddAction(action); - } - - // setup policies with actions - - var policies = Policies.GetStandardPolicies(); - - // add policies to store - foreach (var r in policies) - { - _ = await access.AddResourcePolicy(adminSp.Id, r, bypassIntegrityCheck: true); - } - - // setup roles with policies - var roles = await access.GetSystemRoles(); + [HttpDelete, Route("securityprinciple/{id}")] + public async Task DeleteSecurityPrinciple(string id) + { + var accessControl = await _certifyManager.GetCurrentAccessControl(); + var resultOk = await accessControl.DeleteSecurityPrinciple(GetContextUserId(), id); - foreach (var r in roles) + return new Models.Config.ActionResult { - // add roles and policy assignments to store - await access.AddRole(r); - } - - // assign security principles to roles - var assignedRoles = new List { - // administrator - new AssignedRole{ - Id= Guid.NewGuid().ToString(), - RoleId=StandardRoles.Administrator.Id, - SecurityPrincipleId=adminSp.Id - } + IsSuccess = resultOk, + Message = resultOk ? "Deleted" : "Failed to delete security principle" }; - - foreach (var r in assignedRoles) - { - // add roles and policy assignments to store - await access.AddAssignedRole(r); - } } -#endif - [HttpGet, Route("securityprinciples")] public async Task> GetSecurityPrinciples() { @@ -99,14 +68,6 @@ public async Task> GetSecurityPrinciples() var results = await accessControl.GetSecurityPrinciples(GetContextUserId()); -#if DEBUG - // bootstrap the default user - if (!results.Any()) - { - await BootstrapTestAdminUserAndRoles(accessControl); - results = await accessControl.GetSecurityPrinciples(GetContextUserId()); - } -#endif foreach (var r in results) { r.AuthKey = ""; @@ -135,10 +96,35 @@ public async Task> GetSecurityPrincipleAssignedRoles(string i } [HttpPost, Route("updatepassword")] - public async Task UpdatePassword(string id, string oldpassword, string newpassword) + public async Task UpdatePassword([FromBody] SecurityPrinciplePasswordUpdate passwordUpdate) { var accessControl = await _certifyManager.GetCurrentAccessControl(); - return await accessControl.UpdateSecurityPrinciplePassword(GetContextUserId(), id: id, oldpassword: oldpassword, newpassword: newpassword); + var result = await accessControl.UpdateSecurityPrinciplePassword(GetContextUserId(), passwordUpdate); + + return new Models.Config.ActionResult + { + IsSuccess = result, + Message = result ? "Updated" : "Failed to update" + }; + } + + [HttpPost, Route("validate")] + public async Task Validate([FromBody] SecurityPrinciplePasswordCheck passwordCheck) + { + var accessControl = await _certifyManager.GetCurrentAccessControl(); + var result = await accessControl.CheckSecurityPrinciplePassword(GetContextUserId(), passwordCheck); + + return result; + } + + [HttpPost, Route("serviceauth")] + public async Task ValidateServiceAuth() + { + var protector = _dataProtectionProvider.CreateProtector("serviceauth"); + + protector.Unprotect(Request.Headers["X-Service-Auth"]); + + return new Models.Config.ActionResult(); } } } diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AuthController.cs b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AuthController.cs new file mode 100644 index 000000000..75f412124 --- /dev/null +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AuthController.cs @@ -0,0 +1,55 @@ +using System.Security.Claims; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.BearerToken; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http.HttpResults; +using Microsoft.AspNetCore.Mvc; + +namespace Certify.Service.Controllers +{ + [Authorize] + public class AuthController : Controller + { + + [HttpPost, Route("token")] + [AllowAnonymous] + public async Task AcquireToken(string authJwt) + { + var result = await HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme); + + if (!result.Succeeded) + { + return Unauthorized(); + } + + var claims = new Claim[] + { + new Claim(ClaimTypes.Sid, "service-client"), + }; + + var identity = new ClaimsIdentity(claims: claims, authenticationType: BearerTokenDefaults.AuthenticationScheme); + var servicePrinciple = new ClaimsPrincipal(identity: identity); + + + + // consume a service token request and return a long lived JWT token + var response = new AccessTokenResponse + { + AccessToken = "", + ExpiresIn = 3600, + RefreshToken = "", + }; + return Ok(response); + } + + [HttpPost, Route("refresh")] + public async Task Refresh(string token) + { + // validate refresh token, issue new JWT token + + var jwt = ""; + return await Task.FromResult(jwt); + } + } +} diff --git a/src/Certify.Service/Controllers/AuthController.cs b/src/Certify.Service/Controllers/AuthController.cs deleted file mode 100644 index a7bb733c9..000000000 --- a/src/Certify.Service/Controllers/AuthController.cs +++ /dev/null @@ -1,75 +0,0 @@ -using System; -using System.Threading.Tasks; -using System.Web.Http; -using Certify.Management; - -namespace Certify.Service.Controllers -{ - [RoutePrefix("api/auth")] - public class AuthController : ControllerBase - { - public class AuthModel - { - public string Key { get; set; } - public string Username { get; set; } - public string Password { get; set; } - } - - private ICertifyManager _certifyManager = null; - - public AuthController(ICertifyManager manager) - { - _certifyManager = manager; - } -#if !RELEASE //feature not production ready - [HttpGet, Route("windows")] - public async Task GetWindowsAuthKey() - { - - // user is using windows authentication, return an initial secret auth token. TODO: user must be able to invalidate existing auth key - var encryptedBytes = System.Security.Cryptography.ProtectedData.Protect( - System.Text.Encoding.UTF8.GetBytes(ActionContext.RequestContext.Principal.Identity.Name), - System.Text.Encoding.UTF8.GetBytes("authtoken"), System.Security.Cryptography.DataProtectionScope.LocalMachine - ); - - var secret = Convert.ToBase64String(encryptedBytes); - - var userIdPlusSecret = ActionContext.RequestContext.Principal.Identity.Name + ":" + secret; - - // return auth secret as Base64 string suitable for Basic Authorization https://en.wikipedia.org/wiki/Basic_access_authentication - return await Task.FromResult(Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(userIdPlusSecret))); - - } - - [HttpPost, Route("token")] - [AllowAnonymous] - public async Task AcquireToken(AuthModel model) - { - DebugLog(); - - // TODO: validate authkey and return new JWT - - if (model.Key == "windows123") - { - var jwt = GenerateJwt("certifyuser", GetAuthSecretKey()); - return await Task.FromResult(Ok(jwt)); - } - else - { - return await Task.FromResult(Unauthorized()); - } - } - - [HttpPost, Route("refresh")] - public async Task Refresh() - { - DebugLog(); - - // TODO: validate refresh token and return new JWT - - var jwt = GenerateJwt("certifyuser", GetAuthSecretKey()); - return await Task.FromResult(jwt); - } -#endif - } -} diff --git a/src/Certify.Tests/Certify.Core.Tests.Integration/DataStores/AccessControlDataStoreTests.cs b/src/Certify.Tests/Certify.Core.Tests.Integration/DataStores/AccessControlDataStoreTests.cs index dc9b5492c..ac87d6b38 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Integration/DataStores/AccessControlDataStoreTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Integration/DataStores/AccessControlDataStoreTests.cs @@ -172,7 +172,12 @@ public async Task TestStoreGeneralAccessControl(string storeType) Assert.IsTrue(access.IsPasswordValid("oldpassword", consumerSp.Password)); - var updated = await access.UpdateSecurityPrinciplePassword(adminSp.Id, consumerSp.Id, "oldpassword", "newpassword"); + var updated = await access.UpdateSecurityPrinciplePassword(adminSp.Id, new Models.API.SecurityPrinciplePasswordUpdate + { + SecurityPrincipleId = consumerSp.Id, + Password = "oldpassword", + NewPassword = "newpassword" + }); Assert.IsTrue(updated, "SP password should have been updated OK"); diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/AccessControlTests.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/AccessControlTests.cs index b4bf9786f..efc078186 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/AccessControlTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/AccessControlTests.cs @@ -96,66 +96,51 @@ public class TestAssignedRoles public class TestSecurityPrinciples { - public static SecurityPrinciple TestAdmin() - { - return new SecurityPrinciple - { - Id = "[test]", - Username = "test administrator", - Description = "Example test administrator used as context user during test", - Email = "test_admin@test.com", - Password = "ABCDEFG", - PrincipleType = SecurityPrincipleType.User - }; - } - public static SecurityPrinciple Admin() - { - return new SecurityPrinciple - { - Id = "admin_01", - Username = "admin", - Description = "Administrator account", - Email = "info@test.com", - Password = "ABCDEFG", - PrincipleType = SecurityPrincipleType.User, - }; - } - public static SecurityPrinciple DomainOwner() - { - return new SecurityPrinciple - { - Id = "domain_owner_01", - Username = "demo_owner", - Description = "Example domain owner", - Email = "domains@test.com", - Password = "ABCDEFG", - PrincipleType = SecurityPrincipleType.User, - }; - } - public static SecurityPrinciple DevopsUser() - { - return new SecurityPrinciple - { - Id = "devops_user_01", - Username = "devops_01", - Description = "Example devops user", - Email = "devops01@test.com", - Password = "ABCDEFG", - PrincipleType = SecurityPrincipleType.User, - }; - } - public static SecurityPrinciple DevopsAppDomainConsumer() - { - return new SecurityPrinciple - { - Id = "devops_app_01", - Username = "devapp_01", - Description = "Example devops app domain consumer", - Email = "dev_app01@test.com", - Password = "ABCDEFG", - PrincipleType = SecurityPrincipleType.User, - }; - } + public static SecurityPrinciple TestAdmin => new SecurityPrinciple + { + Id = "[test]", + Username = "test administrator", + Description = "Example test administrator used as context user during test", + Email = "test_admin@test.com", + Password = "ABCDEFG", + PrincipleType = SecurityPrincipleType.User + }; + public static SecurityPrinciple Admin => new SecurityPrinciple + { + Id = "admin_01", + Username = "admin", + Description = "Administrator account", + Email = "info@test.com", + Password = "ABCDEFG", + PrincipleType = SecurityPrincipleType.User, + }; + public static SecurityPrinciple DomainOwner => new SecurityPrinciple + { + Id = "domain_owner_01", + Username = "demo_owner", + Description = "Example domain owner", + Email = "domains@test.com", + Password = "ABCDEFG", + PrincipleType = SecurityPrincipleType.User, + }; + public static SecurityPrinciple DevopsUser => new SecurityPrinciple + { + Id = "devops_user_01", + Username = "devops_01", + Description = "Example devops user", + Email = "devops01@test.com", + Password = "ABCDEFG", + PrincipleType = SecurityPrincipleType.User, + }; + public static SecurityPrinciple DevopsAppDomainConsumer => new SecurityPrinciple + { + Id = "devops_app_01", + Username = "devapp_01", + Description = "Example devops app domain consumer", + Email = "dev_app01@test.com", + Password = "ABCDEFG", + PrincipleType = SecurityPrincipleType.User, + }; } [TestClass] @@ -175,10 +160,10 @@ public void TestInitialize() } [TestMethod] - public async Task TestAccessControlAddGetSecurityPrinciples() + public async Task TestAddGetSecurityPrinciples() { // Add test security principles - var adminSecurityPrinciples = new List { TestSecurityPrinciples.Admin(), TestSecurityPrinciples.TestAdmin() }; + var adminSecurityPrinciples = new List { TestSecurityPrinciples.Admin, TestSecurityPrinciples.TestAdmin }; adminSecurityPrinciples.ForEach(async p => await access.AddSecurityPrinciple(contextUserId, p, bypassIntegrityCheck: true)); // Get stored security principles @@ -194,20 +179,20 @@ public async Task TestAccessControlAddGetSecurityPrinciples() } [TestMethod] - public async Task TestAccessControlGetSecurityPrinciplesNoRoles() + public async Task TestGetSecurityPrinciplesNoRoles() { // Add test security principles - var securityPrincipleAdded = await access.AddSecurityPrinciple(contextUserId, TestSecurityPrinciples.TestAdmin()); + var securityPrincipleAdded = await access.AddSecurityPrinciple(contextUserId, TestSecurityPrinciples.TestAdmin); // Get stored security principles Assert.IsFalse(securityPrincipleAdded, $"Expected AddSecurityPrinciple() to be unsuccessful without roles defined for {contextUserId}"); } [TestMethod] - public async Task TestAccessControlAddGetSecurityPrinciple() + public async Task TestAddGetSecurityPrinciple() { // Add test security principles - var adminSecurityPrinciples = new List { TestSecurityPrinciples.Admin(), TestSecurityPrinciples.TestAdmin() }; + var adminSecurityPrinciples = new List { TestSecurityPrinciples.Admin, TestSecurityPrinciples.TestAdmin }; adminSecurityPrinciples.ForEach(async p => await access.AddSecurityPrinciple(contextUserId, p, bypassIntegrityCheck: true)); foreach (var securityPrinciple in adminSecurityPrinciples) @@ -222,10 +207,10 @@ public async Task TestAccessControlAddGetSecurityPrinciple() } [TestMethod] - public async Task TestAccessControlAddGetAssignedRoles() + public async Task TestAddGetAssignedRoles() { // Add test security principles - var adminSecurityPrinciples = new List { TestSecurityPrinciples.Admin(), TestSecurityPrinciples.TestAdmin() }; + var adminSecurityPrinciples = new List { TestSecurityPrinciples.Admin, TestSecurityPrinciples.TestAdmin }; adminSecurityPrinciples.ForEach(async p => await access.AddSecurityPrinciple(contextUserId, p, bypassIntegrityCheck: true)); // Setup security principle actions @@ -255,10 +240,10 @@ public async Task TestAccessControlAddGetAssignedRoles() } [TestMethod] - public async Task TestAccessControlGetAssignedRolesNoRoles() + public async Task TestGetAssignedRolesNoRoles() { // Add test security principles - var adminSecurityPrinciples = new List { TestSecurityPrinciples.Admin(), TestSecurityPrinciples.TestAdmin() }; + var adminSecurityPrinciples = new List { TestSecurityPrinciples.Admin, TestSecurityPrinciples.TestAdmin }; adminSecurityPrinciples.ForEach(async p => await access.AddSecurityPrinciple(contextUserId, p, bypassIntegrityCheck: true)); // Validate AssignedRole list returned by AccessControl.GetAssignedRoles() @@ -268,10 +253,10 @@ public async Task TestAccessControlGetAssignedRolesNoRoles() } [TestMethod] - public async Task TestAccessControlAddResourcePolicyNoRoles() + public async Task TestAddResourcePolicyNoRoles() { // Add test security principles - var adminSecurityPrinciples = new List { TestSecurityPrinciples.Admin(), TestSecurityPrinciples.TestAdmin() }; + var adminSecurityPrinciples = new List { TestSecurityPrinciples.Admin, TestSecurityPrinciples.TestAdmin }; adminSecurityPrinciples.ForEach(async p => await access.AddSecurityPrinciple(contextUserId, p, bypassIntegrityCheck: true)); // Setup security principle actions @@ -287,10 +272,10 @@ public async Task TestAccessControlAddResourcePolicyNoRoles() } [TestMethod] - public async Task TestAccessControlUpdateSecurityPrinciple() + public async Task TestUpdateSecurityPrinciple() { // Add test security principles - var adminSecurityPrinciples = new List { TestSecurityPrinciples.Admin(), TestSecurityPrinciples.TestAdmin() }; + var adminSecurityPrinciples = new List { TestSecurityPrinciples.Admin, TestSecurityPrinciples.TestAdmin }; adminSecurityPrinciples.ForEach(async p => await access.AddSecurityPrinciple(contextUserId, p, bypassIntegrityCheck: true)); // Setup security principle actions @@ -314,7 +299,7 @@ public async Task TestAccessControlUpdateSecurityPrinciple() Assert.AreEqual(storedSecurityPrinciple.Email, adminSecurityPrinciples[0].Email, $"Expected SecurityPrinciple returned by GetSecurityPrinciple() to match Email '{adminSecurityPrinciples[0].Email}' of SecurityPrinciple passed into AddSecurityPrinciple()"); // Update security principle in AccessControl with a new principle object of the same Id, but different email - var newSecurityPrinciple = TestSecurityPrinciples.Admin(); + var newSecurityPrinciple = TestSecurityPrinciples.Admin; newSecurityPrinciple.Email = "new_test_email@test.com"; var securityPrincipleUpdated = await access.UpdateSecurityPrinciple(contextUserId, newSecurityPrinciple); Assert.IsTrue(securityPrincipleUpdated, $"Expected security principle update for {newSecurityPrinciple.Id} to succeed"); @@ -326,10 +311,10 @@ public async Task TestAccessControlUpdateSecurityPrinciple() } [TestMethod] - public async Task TestAccessControlUpdateSecurityPrincipleNoRoles() + public async Task TestUpdateSecurityPrincipleNoRoles() { // Add test security principles - var adminSecurityPrinciples = new List { TestSecurityPrinciples.Admin(), TestSecurityPrinciples.TestAdmin() }; + var adminSecurityPrinciples = new List { TestSecurityPrinciples.Admin, TestSecurityPrinciples.TestAdmin }; adminSecurityPrinciples.ForEach(async p => await access.AddSecurityPrinciple(contextUserId, p, bypassIntegrityCheck: true)); // Validate email of SecurityPrinciple object returned by AccessControl.GetSecurityPrinciple() before update @@ -337,17 +322,17 @@ public async Task TestAccessControlUpdateSecurityPrincipleNoRoles() Assert.AreEqual(storedSecurityPrinciple.Email, adminSecurityPrinciples[0].Email, $"Expected SecurityPrinciple returned by GetSecurityPrinciple() to match Email '{adminSecurityPrinciples[0].Email}' of SecurityPrinciple passed into AddSecurityPrinciple()"); // Update security principle in AccessControl with a new principle object of the same Id, but different email, with roles undefined - var newSecurityPrinciple = TestSecurityPrinciples.Admin(); + var newSecurityPrinciple = TestSecurityPrinciples.Admin; newSecurityPrinciple.Email = "new_test_email@test.com"; var securityPrincipleUpdated = await access.UpdateSecurityPrinciple(contextUserId, newSecurityPrinciple); Assert.IsFalse(securityPrincipleUpdated, $"Expected security principle update for {newSecurityPrinciple.Id} to be unsuccessful without roles defined"); } [TestMethod] - public async Task TestAccessControlUpdateSecurityPrincipleBadUpdate() + public async Task TestUpdateSecurityPrincipleBadUpdate() { // Add test security principles - var adminSecurityPrinciples = new List { TestSecurityPrinciples.Admin(), TestSecurityPrinciples.TestAdmin() }; + var adminSecurityPrinciples = new List { TestSecurityPrinciples.Admin, TestSecurityPrinciples.TestAdmin }; adminSecurityPrinciples.ForEach(async p => await access.AddSecurityPrinciple(contextUserId, p, bypassIntegrityCheck: true)); // Setup security principle actions @@ -371,7 +356,7 @@ public async Task TestAccessControlUpdateSecurityPrincipleBadUpdate() Assert.AreEqual(storedSecurityPrinciple.Email, adminSecurityPrinciples[0].Email, $"Expected SecurityPrinciple returned by GetSecurityPrinciple() to match Email '{adminSecurityPrinciples[0].Email}' of SecurityPrinciple passed into AddSecurityPrinciple()"); // Update security principle in AccessControl with a new principle object with a bad Id name and different email - var newSecurityPrinciple = TestSecurityPrinciples.Admin(); + var newSecurityPrinciple = TestSecurityPrinciples.Admin; newSecurityPrinciple.Email = "new_test_email@test.com"; newSecurityPrinciple.Id = "missing_username"; var securityPrincipleUpdated = await access.UpdateSecurityPrinciple(contextUserId, newSecurityPrinciple); @@ -379,10 +364,10 @@ public async Task TestAccessControlUpdateSecurityPrincipleBadUpdate() } [TestMethod] - public async Task TestAccessControlUpdateSecurityPrinciplePassword() + public async Task TestUpdateSecurityPrinciplePassword() { // Add test security principles - var adminSecurityPrinciples = new List { TestSecurityPrinciples.Admin(), TestSecurityPrinciples.TestAdmin() }; + var adminSecurityPrinciples = new List { TestSecurityPrinciples.Admin, TestSecurityPrinciples.TestAdmin }; var firstPassword = adminSecurityPrinciples[0].Password; adminSecurityPrinciples.ForEach(async p => await access.AddSecurityPrinciple(contextUserId, p, bypassIntegrityCheck: true)); @@ -409,7 +394,7 @@ public async Task TestAccessControlUpdateSecurityPrinciplePassword() // Update security principle in AccessControl with a new password var newPassword = "GFEDCBA"; - var securityPrincipleUpdated = await access.UpdateSecurityPrinciplePassword(contextUserId, adminSecurityPrinciples[0].Id, firstPassword, newPassword); + var securityPrincipleUpdated = await access.UpdateSecurityPrinciplePassword(contextUserId, new Models.API.SecurityPrinciplePasswordUpdate(adminSecurityPrinciples[0].Id, firstPassword, newPassword)); Assert.IsTrue(securityPrincipleUpdated, $"Expected security principle password update for {adminSecurityPrinciples[0].Id} to succeed"); // Validate password of SecurityPrinciple object returned by AccessControl.GetSecurityPrinciple() after update @@ -420,16 +405,16 @@ public async Task TestAccessControlUpdateSecurityPrinciplePassword() } [TestMethod] - public async Task TestAccessControlUpdateSecurityPrinciplePasswordNoRoles() + public async Task TestUpdateSecurityPrinciplePasswordNoRoles() { // Add test security principles - var adminSecurityPrinciples = new List { TestSecurityPrinciples.Admin(), TestSecurityPrinciples.TestAdmin() }; + var adminSecurityPrinciples = new List { TestSecurityPrinciples.Admin, TestSecurityPrinciples.TestAdmin }; var firstPassword = adminSecurityPrinciples[0].Password; adminSecurityPrinciples.ForEach(async p => await access.AddSecurityPrinciple(contextUserId, p, bypassIntegrityCheck: true)); // Update security principle in AccessControl with a new password var newPassword = "GFEDCBA"; - var securityPrincipleUpdated = await access.UpdateSecurityPrinciplePassword(contextUserId, adminSecurityPrinciples[0].Id, firstPassword, newPassword); + var securityPrincipleUpdated = await access.UpdateSecurityPrinciplePassword(contextUserId, new Models.API.SecurityPrinciplePasswordUpdate(adminSecurityPrinciples[0].Id, firstPassword, newPassword)); Assert.IsFalse(securityPrincipleUpdated, $"Expected security principle password update for {adminSecurityPrinciples[0].Id} to fail without roles"); // Validate password of SecurityPrinciple object returned by AccessControl.GetSecurityPrinciple() after failed update @@ -439,10 +424,10 @@ public async Task TestAccessControlUpdateSecurityPrinciplePasswordNoRoles() } [TestMethod] - public async Task TestAccessControlUpdateSecurityPrinciplePasswordBadPassword() + public async Task TestUpdateSecurityPrinciplePasswordBadPassword() { // Add test security principles - var adminSecurityPrinciples = new List { TestSecurityPrinciples.Admin(), TestSecurityPrinciples.TestAdmin() }; + var adminSecurityPrinciples = new List { TestSecurityPrinciples.Admin, TestSecurityPrinciples.TestAdmin }; var firstPassword = adminSecurityPrinciples[0].Password; adminSecurityPrinciples.ForEach(async p => await access.AddSecurityPrinciple(contextUserId, p, bypassIntegrityCheck: true)); @@ -464,7 +449,7 @@ public async Task TestAccessControlUpdateSecurityPrinciplePasswordBadPassword() // Update security principle in AccessControl with a new password, but wrong original password var newPassword = "GFEDCBA"; - var securityPrincipleUpdated = await access.UpdateSecurityPrinciplePassword(contextUserId, adminSecurityPrinciples[0].Id, firstPassword.ToLower(), newPassword); + var securityPrincipleUpdated = await access.UpdateSecurityPrinciplePassword(contextUserId, new Models.API.SecurityPrinciplePasswordUpdate(adminSecurityPrinciples[0].Id, firstPassword.ToLower(), newPassword)); Assert.IsFalse(securityPrincipleUpdated, $"Expected security principle password update for {adminSecurityPrinciples[0].Id} to fail with wrong password"); // Validate password of SecurityPrinciple object returned by AccessControl.GetSecurityPrinciple() after failed update @@ -474,10 +459,10 @@ public async Task TestAccessControlUpdateSecurityPrinciplePasswordBadPassword() } [TestMethod] - public async Task TestAccessControlDeleteSecurityPrinciple() + public async Task TestDeleteSecurityPrinciple() { // Add test security principles - var adminSecurityPrinciples = new List { TestSecurityPrinciples.Admin(), TestSecurityPrinciples.TestAdmin() }; + var adminSecurityPrinciples = new List { TestSecurityPrinciples.Admin, TestSecurityPrinciples.TestAdmin }; adminSecurityPrinciples.ForEach(async p => await access.AddSecurityPrinciple(contextUserId, p, bypassIntegrityCheck: true)); // Setup security principle actions @@ -511,10 +496,10 @@ public async Task TestAccessControlDeleteSecurityPrinciple() } [TestMethod] - public async Task TestAccessControlDeleteSecurityPrincipleNoRoles() + public async Task TestDeleteSecurityPrincipleNoRoles() { // Add test security principles - var adminSecurityPrinciples = new List { TestSecurityPrinciples.Admin(), TestSecurityPrinciples.TestAdmin() }; + var adminSecurityPrinciples = new List { TestSecurityPrinciples.Admin, TestSecurityPrinciples.TestAdmin }; adminSecurityPrinciples.ForEach(async p => await access.AddSecurityPrinciple(contextUserId, p, bypassIntegrityCheck: true)); // Validate SecurityPrinciple object returned by AccessControl.GetSecurityPrinciple() before delete is not null @@ -532,10 +517,10 @@ public async Task TestAccessControlDeleteSecurityPrincipleNoRoles() } [TestMethod] - public async Task TestAccessControlDeleteSecurityPrincipleSelfDeletion() + public async Task TestDeleteSecurityPrincipleSelfDeletion() { // Add test security principles - var adminSecurityPrinciples = new List { TestSecurityPrinciples.Admin(), TestSecurityPrinciples.TestAdmin() }; + var adminSecurityPrinciples = new List { TestSecurityPrinciples.Admin, TestSecurityPrinciples.TestAdmin }; adminSecurityPrinciples.ForEach(async p => await access.AddSecurityPrinciple(contextUserId, p, bypassIntegrityCheck: true)); // Setup security principle actions @@ -569,10 +554,10 @@ public async Task TestAccessControlDeleteSecurityPrincipleSelfDeletion() } [TestMethod] - public async Task TestAccessControlDeleteSecurityPrincipleBadId() + public async Task TestDeleteSecurityPrincipleBadId() { // Add test security principles - var adminSecurityPrinciples = new List { TestSecurityPrinciples.Admin(), TestSecurityPrinciples.TestAdmin() }; + var adminSecurityPrinciples = new List { TestSecurityPrinciples.Admin, TestSecurityPrinciples.TestAdmin }; adminSecurityPrinciples.ForEach(async p => await access.AddSecurityPrinciple(contextUserId, p, bypassIntegrityCheck: true)); // Setup security principle actions @@ -606,10 +591,10 @@ public async Task TestAccessControlDeleteSecurityPrincipleBadId() } [TestMethod] - public async Task TestAccessControlIsPrincipleInRole() + public async Task TestIsPrincipleInRole() { // Add test security principles - var adminSecurityPrinciples = new List { TestSecurityPrinciples.Admin(), TestSecurityPrinciples.TestAdmin() }; + var adminSecurityPrinciples = new List { TestSecurityPrinciples.Admin, TestSecurityPrinciples.TestAdmin }; adminSecurityPrinciples.ForEach(async p => await access.AddSecurityPrinciple(contextUserId, p, bypassIntegrityCheck: true)); // Setup security principle actions @@ -642,10 +627,10 @@ public async Task TestAccessControlIsPrincipleInRole() } [TestMethod] - public async Task TestAccessControlDomainAuth() + public async Task TestDomainAuth() { // Add test devops user security principle - _ = await access.AddSecurityPrinciple(contextUserId, TestSecurityPrinciples.DevopsUser(), bypassIntegrityCheck: true); + _ = await access.AddSecurityPrinciple(contextUserId, TestSecurityPrinciples.DevopsUser, bypassIntegrityCheck: true); // Setup security principle actions await access.AddAction(Policies.GetStandardResourceActions().Find(r => r.Id == "certificate_download")); @@ -671,10 +656,10 @@ public async Task TestAccessControlDomainAuth() } [TestMethod] - public async Task TestAccessControlWildcardDomainAuth() + public async Task TestWildcardDomainAuth() { // Add test devops user security principle - _ = await access.AddSecurityPrinciple(contextUserId, TestSecurityPrinciples.DevopsUser(), bypassIntegrityCheck: true); + _ = await access.AddSecurityPrinciple(contextUserId, TestSecurityPrinciples.DevopsUser, bypassIntegrityCheck: true); // Setup security principle actions await access.AddAction(Policies.GetStandardResourceActions().Find(r => r.Id == "certificate_download")); @@ -704,10 +689,10 @@ public async Task TestAccessControlWildcardDomainAuth() } [TestMethod] - public async Task TestAccessControlRandomUserAuth() + public async Task TestRandomUserAuth() { // Add test devops user security principle - _ = await access.AddSecurityPrinciple(contextUserId, TestSecurityPrinciples.DevopsUser(), bypassIntegrityCheck: true); + _ = await access.AddSecurityPrinciple(contextUserId, TestSecurityPrinciples.DevopsUser, bypassIntegrityCheck: true); // Setup security principle actions await access.AddAction(Policies.GetStandardResourceActions().Find(r => r.Id == "certificate_download")); @@ -727,5 +712,25 @@ public async Task TestAccessControlRandomUserAuth() var isAuthorised = await access.IsAuthorised(contextUserId, "randomuser", StandardRoles.CertificateConsumer.Id, ResourceTypes.Domain, "certificate_download", "random.microsoft.com"); Assert.IsFalse(isAuthorised, "Unknown user should not be a cert consumer for this subdomain via wildcard"); } + + [TestMethod] + public async Task TestSecurityPrinciplePwdValid() + { + // Add test devops user security principle + _ = await access.AddSecurityPrinciple(contextUserId, TestSecurityPrinciples.DevopsUser, bypassIntegrityCheck: true); + var check = await access.CheckSecurityPrinciplePassword(contextUserId, new Models.API.SecurityPrinciplePasswordCheck(TestSecurityPrinciples.DevopsUser.Id, TestSecurityPrinciples.DevopsUser.Password)); + + Assert.IsTrue(check.IsSuccess, "Password should be valid"); + } + + [TestMethod] + public async Task TestSecurityPrinciplePwdInvalid() + { + // Add test devops user security principle + _ = await access.AddSecurityPrinciple(contextUserId, TestSecurityPrinciples.DevopsUser, bypassIntegrityCheck: true); + var check = await access.CheckSecurityPrinciplePassword(contextUserId, new Models.API.SecurityPrinciplePasswordCheck(TestSecurityPrinciples.DevopsUser.Id, "INVALID_PWD")); + + Assert.IsFalse(check.IsSuccess, "Password should not be valid"); + } } } From 166c31001d14e237e8d1eeb65fbcaefeeda965a1 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 1 Feb 2024 17:33:06 +0800 Subject: [PATCH 119/328] Package updates --- src/Certify.Service/App.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Certify.Service/App.config b/src/Certify.Service/App.config index 559510f55..398a3c579 100644 --- a/src/Certify.Service/App.config +++ b/src/Certify.Service/App.config @@ -33,7 +33,7 @@ - + From 7a2fa17695156546dcefcc29a404be7939ee788f Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Fri, 2 Feb 2024 18:02:10 +0800 Subject: [PATCH 120/328] Add/Update security principles --- .../Management/Access/AccessControl.cs | 30 ++++++- .../Certify.API.Public.cs | 88 +++++++++++++++++++ .../Controllers/v1/SystemController.cs | 8 +- .../Controllers/AccessController.cs | 13 +++ 4 files changed, 133 insertions(+), 6 deletions(-) diff --git a/src/Certify.Core/Management/Access/AccessControl.cs b/src/Certify.Core/Management/Access/AccessControl.cs index 8fe49bbea..96e2e9a04 100644 --- a/src/Certify.Core/Management/Access/AccessControl.cs +++ b/src/Certify.Core/Management/Access/AccessControl.cs @@ -66,10 +66,23 @@ public async Task AddSecurityPrinciple(string contextUserId, SecurityPrinc return false; } + var existing = await GetSecurityPrinciple(contextUserId, principle.Id); + if (existing != null) + { + _log?.Warning($"User {contextUserId} attempted to use AddSecurityPrinciple [{principle?.Id}] which already exists."); + return false; + } + if (!string.IsNullOrWhiteSpace(principle.Password)) { principle.Password = HashPassword(principle.Password); } + else + { + principle.Password = HashPassword(Guid.NewGuid().ToString()); + } + + principle.AvatarUrl = GetAvatarUrlForPrinciple(principle); await _store.Add(nameof(SecurityPrinciple), principle); @@ -77,6 +90,11 @@ public async Task AddSecurityPrinciple(string contextUserId, SecurityPrinc return true; } + public string GetAvatarUrlForPrinciple(SecurityPrinciple principle) + { + return string.IsNullOrWhiteSpace(principle.Email) ? "https://gravatar.com/avatar/00000000000000000000000000000000" : $"https://gravatar.com/avatar/{GetSHA256Hash(principle.Email.Trim().ToLower())}"; + } + public async Task UpdateSecurityPrinciple(string contextUserId, SecurityPrinciple principle) { @@ -88,7 +106,14 @@ public async Task UpdateSecurityPrinciple(string contextUserId, SecurityPr try { - var updated = _store.Update(nameof(SecurityPrinciple), principle); + var updateSp = await _store.Get(nameof(SecurityPrinciple), principle.Id); + updateSp.Email = principle.Email; + updateSp.Description = principle.Description; + updateSp.Title = principle.Title; + + updateSp.AvatarUrl = GetAvatarUrlForPrinciple(principle); + + var updated = _store.Update(nameof(SecurityPrinciple), updateSp); } catch { @@ -375,8 +400,7 @@ await GetSecurityPrincipleByUsername(contextUserId, passwordCheck.Username) : await GetSecurityPrinciple(contextUserId, passwordCheck.SecurityPrincipleId); if (principle != null && IsPasswordValid(passwordCheck.Password, principle.Password)) - { - principle.AvatarUrl = string.IsNullOrWhiteSpace(principle.Email) ? "https://gravatar.com/avatar/00000000000000000000000000000000" : $"https://gravatar.com/avatar/{GetSHA256Hash(principle.Email.Trim().ToLower())}"; + { return new SecurityPrincipleCheckResponse { IsSuccess = true, SecurityPrinciple = principle }; } else diff --git a/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs b/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs index 8d73daaa4..000986ec3 100644 --- a/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs +++ b/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs @@ -680,6 +680,94 @@ public virtual async System.Threading.Tasks.Task DeleteSecurityPri } } + /// + /// Update existing security principle [Generated by Certify.SourceGenerators] + /// + /// Success + /// A server side error occurred. + public virtual System.Threading.Tasks.Task UpdateSecurityPrincipleAsync(SecurityPrinciple body) + { + return UpdateSecurityPrincipleAsync(body, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Update existing security principle [Generated by Certify.SourceGenerators] + /// + /// Success + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task UpdateSecurityPrincipleAsync(SecurityPrinciple body, System.Threading.CancellationToken cancellationToken) + { + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value); + var content_ = new System.Net.Http.StringContent(json_); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + // Operation Path: "internal/v1/access/securityprinciple/update" + urlBuilder_.Append("internal/v1/access/securityprinciple/update"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + /// /// Operations to check current auth status for the given presented authentication tokens /// diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/SystemController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/SystemController.cs index 5fced44fd..b87dc90c2 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/SystemController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/SystemController.cs @@ -58,9 +58,11 @@ public async Task GetHealth() } catch { } - var env = Environment.GetEnvironmentVariables(); - - var health = new { API = "OK", Service = versionInfo, ServiceAvailable = serviceAvailable, env = env }; +#if DEBUG + var health = new { API = "OK", Service = versionInfo, ServiceAvailable = serviceAvailable, env = Environment.GetEnvironmentVariables() }; +#else + var health = new { API = "OK", Service = versionInfo, ServiceAvailable = serviceAvailable}; +#endif return new OkObjectResult(health); } diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccessController.cs b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccessController.cs index a5bb6f78e..a15ac5417 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccessController.cs +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccessController.cs @@ -48,6 +48,19 @@ private string GetContextUserId() }; } + [HttpPost, Route("securityprinciple/update")] + public async Task UpdateSecurityPrinciple([FromBody] SecurityPrinciple principle) + { + var accessControl = await _certifyManager.GetCurrentAccessControl(); + var addResultOk = await accessControl.UpdateSecurityPrinciple(GetContextUserId(), principle); + + return new Models.Config.ActionResult + { + IsSuccess = addResultOk, + Message = addResultOk ? "Updated" : "Failed to update" + }; + } + [HttpDelete, Route("securityprinciple/{id}")] public async Task DeleteSecurityPrinciple(string id) { From 1bca87ed954c08683955e6a28236a21a14f34b15 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Mon, 5 Feb 2024 16:37:05 +0800 Subject: [PATCH 121/328] Package updates --- src/Certify.Client/Certify.Client.csproj | 2 +- src/Certify.Models/Certify.Models.csproj | 2 +- .../DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Certify.Client/Certify.Client.csproj b/src/Certify.Client/Certify.Client.csproj index b5709ac68..f16b6abed 100644 --- a/src/Certify.Client/Certify.Client.csproj +++ b/src/Certify.Client/Certify.Client.csproj @@ -18,7 +18,7 @@ - + diff --git a/src/Certify.Models/Certify.Models.csproj b/src/Certify.Models/Certify.Models.csproj index 05e517d02..b4ef8f946 100644 --- a/src/Certify.Models/Certify.Models.csproj +++ b/src/Certify.Models/Certify.Models.csproj @@ -21,7 +21,7 @@ all runtime; build; native; contentfiles; analyzers - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj index aaf4c29c7..6f75e2295 100644 --- a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj +++ b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj @@ -7,7 +7,7 @@ - + From ac08ba8e6d14b1b540d56f56815fc3400d5153f6 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Tue, 6 Feb 2024 17:56:56 +0800 Subject: [PATCH 122/328] API: Implement update assigned roles --- .../Management/Access/AccessControl.cs | 32 ++ .../Management/Access/IAccessControl.cs | 1 + src/Certify.Models/Config/AccessControl.cs | 8 +- .../Certify.API.Public.cs | 310 +++++++++++------- .../Controllers/AccessController.cs | 13 + 5 files changed, 253 insertions(+), 111 deletions(-) diff --git a/src/Certify.Core/Management/Access/AccessControl.cs b/src/Certify.Core/Management/Access/AccessControl.cs index 96e2e9a04..12b48db8f 100644 --- a/src/Certify.Core/Management/Access/AccessControl.cs +++ b/src/Certify.Core/Management/Access/AccessControl.cs @@ -6,6 +6,7 @@ using System.Text.Unicode; using System.Threading.Tasks; using Certify.Models.API; +using Certify.Models.Config; using Certify.Models.Config.AccessControl; using Certify.Models.Providers; using Certify.Providers; @@ -393,6 +394,37 @@ public async Task> GetAssignedRoles(string contextUserId, str return assignedRoles.Where(r => r.SecurityPrincipleId == id).ToList(); } + public async Task UpdateAssignedRoles(string contextUserId, SecurityPrincipleAssignedRoleUpdate update) + { + if (!await IsPrincipleInRole(contextUserId, contextUserId, StandardRoles.Administrator.Id)) + { + _log?.Warning("User {contextUserId} attempted to update assigned role for [{id}] without being in required role.", contextUserId, update.SecurityPrincipleId); + return false; + } + + // remove items from assigned roles + var existing = await GetAssignedRoles(contextUserId, update.SecurityPrincipleId); + foreach (var deleted in update.RemovedAssignedRoles) + { + var e = existing.FirstOrDefault(r => r.RoleId == deleted.RoleId); + if (e!=null){ + await _store.Delete(nameof(AssignedRole), e.Id); + } + } + + // add items to assigned roles + existing = await GetAssignedRoles(contextUserId, update.SecurityPrincipleId); + foreach (var added in update.AddedAssignedRoles) + { + if (!existing.Exists(r => r.RoleId == added.RoleId)) + { + await _store.Add(nameof(AssignedRole), added); + } + } + + return true; + } + public async Task CheckSecurityPrinciplePassword(string contextUserId, SecurityPrinciplePasswordCheck passwordCheck) { var principle = string.IsNullOrWhiteSpace(passwordCheck.SecurityPrincipleId) ? diff --git a/src/Certify.Core/Management/Access/IAccessControl.cs b/src/Certify.Core/Management/Access/IAccessControl.cs index 26e12e5f9..cb8b22ca7 100644 --- a/src/Certify.Core/Management/Access/IAccessControl.cs +++ b/src/Certify.Core/Management/Access/IAccessControl.cs @@ -22,6 +22,7 @@ public interface IAccessControl Task IsPrincipleInRole(string contextUserId, string id, string roleId); Task> GetAssignedRoles(string contextUserId, string id); Task UpdateSecurityPrinciple(string contextUserId, SecurityPrinciple principle); + Task UpdateAssignedRoles(string contextUserId, SecurityPrincipleAssignedRoleUpdate update); Task UpdateSecurityPrinciplePassword(string contextUserId, SecurityPrinciplePasswordUpdate passwordUpdate); Task CheckSecurityPrinciplePassword(string contextUserId, SecurityPrinciplePasswordCheck passwordCheck); diff --git a/src/Certify.Models/Config/AccessControl.cs b/src/Certify.Models/Config/AccessControl.cs index ee52869ca..3f820c91e 100644 --- a/src/Certify.Models/Config/AccessControl.cs +++ b/src/Certify.Models/Config/AccessControl.cs @@ -73,7 +73,7 @@ public class AssignedRole : AccessStoreItem /// public string? SecurityPrincipleId { get; set; } - public List IncludedResources { get; set; } + public List? IncludedResources { get; set; } } /// @@ -125,4 +125,10 @@ public ResourceAction(string id, string title, string resourceType) public string? ResourceType { get; set; } } + public class SecurityPrincipleAssignedRoleUpdate + { + public string SecurityPrincipleId { get; set; } = String.Empty; + public List AddedAssignedRoles { get; set; } = new List(); + public List RemovedAssignedRoles { get; set; } = new List(); + } } diff --git a/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs b/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs index 000986ec3..2a659c600 100644 --- a/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs +++ b/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs @@ -1,6 +1,6 @@ //---------------------- // -// Generated using the NSwag toolchain v14.0.1.0 (NJsonSchema v11.0.0.0 (Newtonsoft.Json v13.0.0.0)) (http://NSwag.org) +// Generated using the NSwag toolchain v14.0.3.0 (NJsonSchema v11.0.0.0 (Newtonsoft.Json v13.0.0.0)) (http://NSwag.org) // //---------------------- @@ -28,12 +28,13 @@ namespace Certify.API.Public { using System = global::System; - [System.CodeDom.Compiler.GeneratedCode("NSwag", "14.0.1.0 (NJsonSchema v11.0.0.0 (Newtonsoft.Json v13.0.0.0))")] + [System.CodeDom.Compiler.GeneratedCode("NSwag", "14.0.3.0 (NJsonSchema v11.0.0.0 (Newtonsoft.Json v13.0.0.0))")] public partial class Client { - #pragma warning disable 8618 // Set by constructor via BaseUrl property + #pragma warning disable 8618 private string _baseUrl; - #pragma warning restore 8618 // Set by constructor via BaseUrl property + #pragma warning restore 8618 + private System.Net.Http.HttpClient _httpClient; private static System.Lazy _settings = new System.Lazy(CreateSerializerSettings, true); @@ -53,6 +54,7 @@ private static Newtonsoft.Json.JsonSerializerSettings CreateSerializerSettings() public string BaseUrl { get { return _baseUrl; } + [System.Diagnostics.CodeAnalysis.MemberNotNull(nameof(_baseUrl))] set { _baseUrl = value; @@ -100,7 +102,7 @@ public string BaseUrl request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); var urlBuilder_ = new System.Text.StringBuilder(); - if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); // Operation Path: "internal/v1/access/securityprinciple/{id}/assignedroles" urlBuilder_.Append("internal/v1/access/securityprinciple/"); urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(id, System.Globalization.CultureInfo.InvariantCulture))); @@ -186,7 +188,7 @@ public string BaseUrl request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); var urlBuilder_ = new System.Text.StringBuilder(); - if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); // Operation Path: "internal/v1/access/roles" urlBuilder_.Append("internal/v1/access/roles"); @@ -270,7 +272,7 @@ public string BaseUrl request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); var urlBuilder_ = new System.Text.StringBuilder(); - if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); // Operation Path: "internal/v1/access/securityprinciples" urlBuilder_.Append("internal/v1/access/securityprinciples"); @@ -358,7 +360,7 @@ public virtual async System.Threading.Tasks.Task request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); var urlBuilder_ = new System.Text.StringBuilder(); - if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); // Operation Path: "internal/v1/access/validate" urlBuilder_.Append("internal/v1/access/validate"); @@ -446,7 +448,7 @@ public virtual async System.Threading.Tasks.Task UpdateSecurityPri request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); var urlBuilder_ = new System.Text.StringBuilder(); - if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); // Operation Path: "internal/v1/access/updatepassword" urlBuilder_.Append("internal/v1/access/updatepassword"); @@ -534,7 +536,7 @@ public virtual async System.Threading.Tasks.Task AddSecurityPrinci request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); var urlBuilder_ = new System.Text.StringBuilder(); - if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); // Operation Path: "internal/v1/access/securityprinciple" urlBuilder_.Append("internal/v1/access/securityprinciple"); @@ -618,15 +620,15 @@ public virtual async System.Threading.Tasks.Task DeleteSecurityPri request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); var urlBuilder_ = new System.Text.StringBuilder(); - if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); // Operation Path: "internal/v1/access/securityprinciple" urlBuilder_.Append("internal/v1/access/securityprinciple"); - urlBuilder_.Append('?'); - if (id != null) - { - urlBuilder_.Append(System.Uri.EscapeDataString("id")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(id, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); - } - urlBuilder_.Length--; + urlBuilder_.Append('?'); + if (id != null) + { + urlBuilder_.Append(System.Uri.EscapeDataString("id")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(id, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); + } + urlBuilder_.Length--; PrepareRequest(client_, request_, urlBuilder_); @@ -712,7 +714,7 @@ public virtual async System.Threading.Tasks.Task UpdateSecurityPri request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); var urlBuilder_ = new System.Text.StringBuilder(); - if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); // Operation Path: "internal/v1/access/securityprinciple/update" urlBuilder_.Append("internal/v1/access/securityprinciple/update"); @@ -768,6 +770,94 @@ public virtual async System.Threading.Tasks.Task UpdateSecurityPri } } + /// + /// Update assigned roles for a security principle [Generated by Certify.SourceGenerators] + /// + /// Success + /// A server side error occurred. + public virtual System.Threading.Tasks.Task UpdateSecurityPrincipleAssignedRolesAsync(SecurityPrincipleAssignedRoleUpdate body) + { + return UpdateSecurityPrincipleAssignedRolesAsync(body, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Update assigned roles for a security principle [Generated by Certify.SourceGenerators] + /// + /// Success + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task UpdateSecurityPrincipleAssignedRolesAsync(SecurityPrincipleAssignedRoleUpdate body, System.Threading.CancellationToken cancellationToken) + { + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value); + var content_ = new System.Net.Http.StringContent(json_); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); + // Operation Path: "internal/v1/access/securityprinciple/roles/update" + urlBuilder_.Append("internal/v1/access/securityprinciple/roles/update"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + /// /// Operations to check current auth status for the given presented authentication tokens /// @@ -795,7 +885,7 @@ public virtual async System.Threading.Tasks.Task CheckAuthStatusAsync(System.Thr request_.Method = new System.Net.Http.HttpMethod("GET"); var urlBuilder_ = new System.Text.StringBuilder(); - if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); // Operation Path: "api/v1/auth/status" urlBuilder_.Append("api/v1/auth/status"); @@ -880,7 +970,7 @@ public virtual async System.Threading.Tasks.Task LoginAsync(AuthRe request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); var urlBuilder_ = new System.Text.StringBuilder(); - if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); // Operation Path: "api/v1/auth/login" urlBuilder_.Append("api/v1/auth/login"); @@ -965,15 +1055,15 @@ public virtual async System.Threading.Tasks.Task RefreshAsync(stri request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); var urlBuilder_ = new System.Text.StringBuilder(); - if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); // Operation Path: "api/v1/auth/refresh" urlBuilder_.Append("api/v1/auth/refresh"); - urlBuilder_.Append('?'); - if (refreshToken != null) - { - urlBuilder_.Append(System.Uri.EscapeDataString("refreshToken")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(refreshToken, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); - } - urlBuilder_.Length--; + urlBuilder_.Append('?'); + if (refreshToken != null) + { + urlBuilder_.Append(System.Uri.EscapeDataString("refreshToken")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(refreshToken, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); + } + urlBuilder_.Length--; PrepareRequest(client_, request_, urlBuilder_); @@ -1061,18 +1151,18 @@ public virtual async System.Threading.Tasks.Task DownloadAsync(str request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); var urlBuilder_ = new System.Text.StringBuilder(); - if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); // Operation Path: "api/v1/certificate/{managedCertId}/download/{format}" urlBuilder_.Append("api/v1/certificate/"); urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(managedCertId, System.Globalization.CultureInfo.InvariantCulture))); urlBuilder_.Append("/download/"); urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(format, System.Globalization.CultureInfo.InvariantCulture))); - urlBuilder_.Append('?'); - if (mode != null) - { - urlBuilder_.Append(System.Uri.EscapeDataString("mode")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(mode, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); - } - urlBuilder_.Length--; + urlBuilder_.Append('?'); + if (mode != null) + { + urlBuilder_.Append(System.Uri.EscapeDataString("mode")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(mode, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); + } + urlBuilder_.Length--; PrepareRequest(client_, request_, urlBuilder_); @@ -1155,17 +1245,17 @@ public virtual async System.Threading.Tasks.Task DownloadLogAsync(str request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); var urlBuilder_ = new System.Text.StringBuilder(); - if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); // Operation Path: "api/v1/certificate/{managedCertId}/log" urlBuilder_.Append("api/v1/certificate/"); urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(managedCertId, System.Globalization.CultureInfo.InvariantCulture))); urlBuilder_.Append("/log"); - urlBuilder_.Append('?'); - if (maxLines != null) - { - urlBuilder_.Append(System.Uri.EscapeDataString("maxLines")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(maxLines, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); - } - urlBuilder_.Length--; + urlBuilder_.Append('?'); + if (maxLines != null) + { + urlBuilder_.Append(System.Uri.EscapeDataString("maxLines")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(maxLines, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); + } + urlBuilder_.Length--; PrepareRequest(client_, request_, urlBuilder_); @@ -1247,23 +1337,23 @@ public virtual async System.Threading.Tasks.Task GetManagedCertificateS request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); var urlBuilder_ = new System.Text.StringBuilder(); - if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); // Operation Path: "api/v1/certificate/summary" urlBuilder_.Append("api/v1/certificate/summary"); - urlBuilder_.Append('?'); - if (keyword != null) - { - urlBuilder_.Append(System.Uri.EscapeDataString("keyword")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(keyword, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); - } - urlBuilder_.Length--; + urlBuilder_.Append('?'); + if (keyword != null) + { + urlBuilder_.Append(System.Uri.EscapeDataString("keyword")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(keyword, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); + } + urlBuilder_.Length--; PrepareRequest(client_, request_, urlBuilder_); @@ -1439,7 +1529,7 @@ public virtual async System.Threading.Tasks.Task GetManagedC request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); var urlBuilder_ = new System.Text.StringBuilder(); - if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); // Operation Path: "api/v1/certificate/settings/{managedCertId}" urlBuilder_.Append("api/v1/certificate/settings/"); urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(managedCertId, System.Globalization.CultureInfo.InvariantCulture))); @@ -1528,7 +1618,7 @@ public virtual async System.Threading.Tasks.Task UpdateManag request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); var urlBuilder_ = new System.Text.StringBuilder(); - if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); // Operation Path: "api/v1/certificate/settings/update" urlBuilder_.Append("api/v1/certificate/settings/update"); @@ -1613,15 +1703,15 @@ public virtual async System.Threading.Tasks.Task BeginOrderA request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); var urlBuilder_ = new System.Text.StringBuilder(); - if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); // Operation Path: "api/v1/certificate/order" urlBuilder_.Append("api/v1/certificate/order"); - urlBuilder_.Append('?'); - if (id != null) - { - urlBuilder_.Append(System.Uri.EscapeDataString("id")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(id, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); - } - urlBuilder_.Length--; + urlBuilder_.Append('?'); + if (id != null) + { + urlBuilder_.Append(System.Uri.EscapeDataString("id")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(id, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); + } + urlBuilder_.Length--; PrepareRequest(client_, request_, urlBuilder_); @@ -1707,7 +1797,7 @@ public virtual async System.Threading.Tasks.Task PerformRene request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); var urlBuilder_ = new System.Text.StringBuilder(); - if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); // Operation Path: "api/v1/certificate/renew" urlBuilder_.Append("api/v1/certificate/renew"); @@ -1795,7 +1885,7 @@ public virtual async System.Threading.Tasks.Task PerformRene request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); var urlBuilder_ = new System.Text.StringBuilder(); - if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); // Operation Path: "api/v1/certificate/test" urlBuilder_.Append("api/v1/certificate/test"); @@ -1882,7 +1972,7 @@ public virtual async System.Threading.Tasks.Task RemoveManagedCertificateA request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); var urlBuilder_ = new System.Text.StringBuilder(); - if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); // Operation Path: "api/v1/certificate/certificate/{id}" urlBuilder_.Append("api/v1/certificate/certificate/"); urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(id, System.Globalization.CultureInfo.InvariantCulture))); @@ -1967,7 +2057,7 @@ public virtual async System.Threading.Tasks.Task RemoveManagedCertificateA request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); var urlBuilder_ = new System.Text.StringBuilder(); - if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); // Operation Path: "internal/v1/certificateauthority" urlBuilder_.Append("internal/v1/certificateauthority"); @@ -2051,7 +2141,7 @@ public virtual async System.Threading.Tasks.Task RemoveManagedCertificateA request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); var urlBuilder_ = new System.Text.StringBuilder(); - if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); // Operation Path: "internal/v1/certificateauthority/accounts" urlBuilder_.Append("internal/v1/certificateauthority/accounts"); @@ -2139,7 +2229,7 @@ public virtual async System.Threading.Tasks.Task AddAcmeAccountAsy request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); var urlBuilder_ = new System.Text.StringBuilder(); - if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); // Operation Path: "internal/v1/certificateauthority/account" urlBuilder_.Append("internal/v1/certificateauthority/account"); @@ -2227,7 +2317,7 @@ public virtual async System.Threading.Tasks.Task AddCertificateAut request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); var urlBuilder_ = new System.Text.StringBuilder(); - if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); // Operation Path: "internal/v1/certificateauthority/authority" urlBuilder_.Append("internal/v1/certificateauthority/authority"); @@ -2314,7 +2404,7 @@ public virtual async System.Threading.Tasks.Task RemoveCertificate request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); var urlBuilder_ = new System.Text.StringBuilder(); - if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); // Operation Path: "internal/v1/certificateauthority/authority/{id}" urlBuilder_.Append("internal/v1/certificateauthority/authority/"); urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(id, System.Globalization.CultureInfo.InvariantCulture))); @@ -2405,7 +2495,7 @@ public virtual async System.Threading.Tasks.Task RemoveAcmeAccount request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); var urlBuilder_ = new System.Text.StringBuilder(); - if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); // Operation Path: "internal/v1/certificateauthority/accounts/{storageKey}/{deactivate}" urlBuilder_.Append("internal/v1/certificateauthority/accounts/"); urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(storageKey, System.Globalization.CultureInfo.InvariantCulture))); @@ -2492,7 +2582,7 @@ public virtual async System.Threading.Tasks.Task RemoveAcmeAccount request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); var urlBuilder_ = new System.Text.StringBuilder(); - if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); // Operation Path: "internal/v1/challengeprovider" urlBuilder_.Append("internal/v1/challengeprovider"); @@ -2576,19 +2666,19 @@ public virtual async System.Threading.Tasks.Task RemoveAcmeAccount request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); var urlBuilder_ = new System.Text.StringBuilder(); - if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); // Operation Path: "internal/v1/challengeprovider/dnszones" urlBuilder_.Append("internal/v1/challengeprovider/dnszones"); - urlBuilder_.Append('?'); - if (providerTypeId != null) - { - urlBuilder_.Append(System.Uri.EscapeDataString("providerTypeId")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(providerTypeId, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); - } - if (credentialsId != null) - { - urlBuilder_.Append(System.Uri.EscapeDataString("credentialsId")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(credentialsId, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); - } - urlBuilder_.Length--; + urlBuilder_.Append('?'); + if (providerTypeId != null) + { + urlBuilder_.Append(System.Uri.EscapeDataString("providerTypeId")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(providerTypeId, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); + } + if (credentialsId != null) + { + urlBuilder_.Append(System.Uri.EscapeDataString("credentialsId")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(credentialsId, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); + } + urlBuilder_.Length--; PrepareRequest(client_, request_, urlBuilder_); @@ -2670,7 +2760,7 @@ public virtual async System.Threading.Tasks.Task RemoveAcmeAccount request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); var urlBuilder_ = new System.Text.StringBuilder(); - if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); // Operation Path: "internal/v1/deploymenttask/providers" urlBuilder_.Append("internal/v1/deploymenttask/providers"); @@ -2758,7 +2848,7 @@ public virtual async System.Threading.Tasks.Task RemoveAcmeAccount request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); var urlBuilder_ = new System.Text.StringBuilder(); - if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); // Operation Path: "internal/v1/preview" urlBuilder_.Append("internal/v1/preview"); @@ -2842,7 +2932,7 @@ public virtual async System.Threading.Tasks.Task RemoveAcmeAccount request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); var urlBuilder_ = new System.Text.StringBuilder(); - if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); // Operation Path: "internal/v1/storedcredential" urlBuilder_.Append("internal/v1/storedcredential"); @@ -2930,7 +3020,7 @@ public virtual async System.Threading.Tasks.Task UpdateStoredC request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); var urlBuilder_ = new System.Text.StringBuilder(); - if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); // Operation Path: "internal/v1/storedcredential" urlBuilder_.Append("internal/v1/storedcredential"); @@ -3017,7 +3107,7 @@ public virtual async System.Threading.Tasks.Task RemoveStoredCrede request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); var urlBuilder_ = new System.Text.StringBuilder(); - if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); // Operation Path: "internal/v1/storedcredential/storedcredential/{storageKey}" urlBuilder_.Append("internal/v1/storedcredential/storedcredential/"); urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(storageKey, System.Globalization.CultureInfo.InvariantCulture))); @@ -3102,7 +3192,7 @@ public virtual async System.Threading.Tasks.Task GetSystemVersionAsync( request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); var urlBuilder_ = new System.Text.StringBuilder(); - if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); // Operation Path: "api/v1/system/version" urlBuilder_.Append("api/v1/system/version"); @@ -3186,7 +3276,7 @@ public virtual async System.Threading.Tasks.Task GetHealthAsync(System.T request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); var urlBuilder_ = new System.Text.StringBuilder(); - if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); // Operation Path: "api/v1/system/health" urlBuilder_.Append("api/v1/system/health"); @@ -3274,7 +3364,7 @@ public virtual async System.Threading.Tasks.Task PerformExp request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); var urlBuilder_ = new System.Text.StringBuilder(); - if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); // Operation Path: "api/v1/system/system/migration/export" urlBuilder_.Append("api/v1/system/system/migration/export"); @@ -3362,7 +3452,7 @@ public virtual async System.Threading.Tasks.Task PerformExp request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); var urlBuilder_ = new System.Text.StringBuilder(); - if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); // Operation Path: "api/v1/system/system/migration/import" urlBuilder_.Append("api/v1/system/system/migration/import"); @@ -3449,7 +3539,7 @@ public virtual async System.Threading.Tasks.Task PerformExp request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); var urlBuilder_ = new System.Text.StringBuilder(); - if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); // Operation Path: "internal/v1/target/{serverType}/items" urlBuilder_.Append("internal/v1/target/"); urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(serverType, System.Globalization.CultureInfo.InvariantCulture))); @@ -3541,7 +3631,7 @@ public virtual async System.Threading.Tasks.Task GetTargetServiceItemA request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); var urlBuilder_ = new System.Text.StringBuilder(); - if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); // Operation Path: "internal/v1/target/{serverType}/item/{itemId}" urlBuilder_.Append("internal/v1/target/"); urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(serverType, System.Globalization.CultureInfo.InvariantCulture))); @@ -3634,7 +3724,7 @@ public virtual async System.Threading.Tasks.Task GetTargetServiceItemA request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); var urlBuilder_ = new System.Text.StringBuilder(); - if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); // Operation Path: "internal/v1/target/{serverType}/item/{itemId}/identifiers" urlBuilder_.Append("internal/v1/target/"); urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(serverType, System.Globalization.CultureInfo.InvariantCulture))); @@ -3722,7 +3812,7 @@ public virtual async System.Threading.Tasks.Task GetTargetServiceItemA request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); var urlBuilder_ = new System.Text.StringBuilder(); - if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); // Operation Path: "internal/v1/target/services" urlBuilder_.Append("internal/v1/target/services"); @@ -3812,7 +3902,7 @@ public virtual async System.Threading.Tasks.Task GetTargetServiceItemA request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); var urlBuilder_ = new System.Text.StringBuilder(); - if (!string.IsNullOrEmpty(BaseUrl)) urlBuilder_.Append(BaseUrl); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); // Operation Path: "api/v1/validation/{type}/{key}" urlBuilder_.Append("api/v1/validation/"); urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(type, System.Globalization.CultureInfo.InvariantCulture))); @@ -3985,7 +4075,7 @@ private string ConvertToString(object value, System.Globalization.CultureInfo cu - [System.CodeDom.Compiler.GeneratedCode("NSwag", "14.0.1.0 (NJsonSchema v11.0.0.0 (Newtonsoft.Json v13.0.0.0))")] + [System.CodeDom.Compiler.GeneratedCode("NSwag", "14.0.3.0 (NJsonSchema v11.0.0.0 (Newtonsoft.Json v13.0.0.0))")] public partial class FileResponse : System.IDisposable { private System.IDisposable _client; @@ -4022,7 +4112,7 @@ public void Dispose() } - [System.CodeDom.Compiler.GeneratedCode("NSwag", "14.0.1.0 (NJsonSchema v11.0.0.0 (Newtonsoft.Json v13.0.0.0))")] + [System.CodeDom.Compiler.GeneratedCode("NSwag", "14.0.3.0 (NJsonSchema v11.0.0.0 (Newtonsoft.Json v13.0.0.0))")] public partial class ApiException : System.Exception { public int StatusCode { get; private set; } @@ -4045,7 +4135,7 @@ public override string ToString() } } - [System.CodeDom.Compiler.GeneratedCode("NSwag", "14.0.1.0 (NJsonSchema v11.0.0.0 (Newtonsoft.Json v13.0.0.0))")] + [System.CodeDom.Compiler.GeneratedCode("NSwag", "14.0.3.0 (NJsonSchema v11.0.0.0 (Newtonsoft.Json v13.0.0.0))")] public partial class ApiException : ApiException { public TResult Result { get; private set; } diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccessController.cs b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccessController.cs index a15ac5417..c939b41d7 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccessController.cs +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccessController.cs @@ -61,6 +61,19 @@ private string GetContextUserId() }; } + [HttpPost, Route("securityprinciple/roles/update")] + public async Task UpdateSecurityPrincipleAssignedRoles([FromBody] SecurityPrincipleAssignedRoleUpdate update) + { + var accessControl = await _certifyManager.GetCurrentAccessControl(); + var resultOk = await accessControl.UpdateAssignedRoles(GetContextUserId(), update); + + return new Models.Config.ActionResult + { + IsSuccess = resultOk, + Message = resultOk ? "Updated" : "Failed to update" + }; + } + [HttpDelete, Route("securityprinciple/{id}")] public async Task DeleteSecurityPrinciple(string id) { From e5b8ed4e1b1e38a1b044050d7a7008dab69d6f7c Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 8 Feb 2024 10:59:17 +0800 Subject: [PATCH 123/328] Package updates --- src/Certify.Client/Certify.Client.csproj | 2 +- src/Certify.Core.Service.sln | 10 ---------- src/Certify.Core/Certify.Core.csproj | 2 +- .../Certify.Server.Api.Public.Tests.csproj | 2 +- .../Certify.Server.Core/Certify.Server.Core.csproj | 2 +- .../Certify.Core.Tests.Integration.csproj | 4 ++-- .../Certify.Core.Tests.Unit.csproj | 4 ++-- .../Certify.Service.Tests.Integration.csproj | 4 ++-- .../Certify.UI.Tests.Integration.csproj | 2 +- 9 files changed, 11 insertions(+), 21 deletions(-) diff --git a/src/Certify.Client/Certify.Client.csproj b/src/Certify.Client/Certify.Client.csproj index f16b6abed..e706c7b60 100644 --- a/src/Certify.Client/Certify.Client.csproj +++ b/src/Certify.Client/Certify.Client.csproj @@ -20,7 +20,7 @@ - + diff --git a/src/Certify.Core.Service.sln b/src/Certify.Core.Service.sln index 6af14c6ef..410c05d24 100644 --- a/src/Certify.Core.Service.sln +++ b/src/Certify.Core.Service.sln @@ -69,8 +69,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Certify.Providers.ACME.Anvi EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Certify.ACME.Anvil", "..\..\libs\anvil\src\Certify.ACME.Anvil\Certify.ACME.Anvil.csproj", "{443202E1-B6E5-4625-BC3E-B3CB54CF4055}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Certify.Service.Worker", "Certify.Server\Certify.Service.Worker\Certify.Service.Worker\Certify.Service.Worker.csproj", "{D786EFD1-7402-43F0-B74F-504DB7C25FF6}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Certify.Server.Api.Public", "Certify.Server\Certify.Server.Api.Public\Certify.Server.Api.Public.csproj", "{2DB50C13-7535-4D01-8EA5-1839F1472D7B}" EndProject Global @@ -281,14 +279,6 @@ Global {443202E1-B6E5-4625-BC3E-B3CB54CF4055}.Release|Any CPU.Build.0 = Release|Any CPU {443202E1-B6E5-4625-BC3E-B3CB54CF4055}.Release|x64.ActiveCfg = Release|Any CPU {443202E1-B6E5-4625-BC3E-B3CB54CF4055}.Release|x64.Build.0 = Release|Any CPU - {D786EFD1-7402-43F0-B74F-504DB7C25FF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D786EFD1-7402-43F0-B74F-504DB7C25FF6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D786EFD1-7402-43F0-B74F-504DB7C25FF6}.Debug|x64.ActiveCfg = Debug|Any CPU - {D786EFD1-7402-43F0-B74F-504DB7C25FF6}.Debug|x64.Build.0 = Debug|Any CPU - {D786EFD1-7402-43F0-B74F-504DB7C25FF6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D786EFD1-7402-43F0-B74F-504DB7C25FF6}.Release|Any CPU.Build.0 = Release|Any CPU - {D786EFD1-7402-43F0-B74F-504DB7C25FF6}.Release|x64.ActiveCfg = Release|Any CPU - {D786EFD1-7402-43F0-B74F-504DB7C25FF6}.Release|x64.Build.0 = Release|Any CPU {2DB50C13-7535-4D01-8EA5-1839F1472D7B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2DB50C13-7535-4D01-8EA5-1839F1472D7B}.Debug|Any CPU.Build.0 = Debug|Any CPU {2DB50C13-7535-4D01-8EA5-1839F1472D7B}.Debug|x64.ActiveCfg = Debug|Any CPU diff --git a/src/Certify.Core/Certify.Core.csproj b/src/Certify.Core/Certify.Core.csproj index c3badb08b..d170b8882 100644 --- a/src/Certify.Core/Certify.Core.csproj +++ b/src/Certify.Core/Certify.Core.csproj @@ -83,7 +83,7 @@ 1701;1702;CA1068 - + diff --git a/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj b/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj index d653b65d0..8b1b7011b 100644 --- a/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj +++ b/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj @@ -12,7 +12,7 @@ - + diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj index 176fa7e38..595e7b14f 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj @@ -9,7 +9,7 @@ - + diff --git a/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj b/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj index d6707e1fa..aa0cc3371 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj @@ -88,7 +88,7 @@ - + @@ -96,7 +96,7 @@ - + diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj index 10cefad7d..c4a746e23 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj @@ -143,10 +143,10 @@ - + - + diff --git a/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj b/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj index d5d1439a4..6cecb4f74 100644 --- a/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj @@ -77,10 +77,10 @@ - + - + diff --git a/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj b/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj index d667c800e..8e0d6ea3f 100644 --- a/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj @@ -73,7 +73,7 @@ - + From ee8b8a85024e8624e87819cb4ac321dd72c6fb63 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 8 Feb 2024 12:38:31 +0800 Subject: [PATCH 124/328] Tasks: perform validation in preview mode --- .../Management/DeploymentTasks/DeploymentTask.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Certify.Core/Management/DeploymentTasks/DeploymentTask.cs b/src/Certify.Core/Management/DeploymentTasks/DeploymentTask.cs index 474dc6d6f..44b0a2b00 100644 --- a/src/Certify.Core/Management/DeploymentTasks/DeploymentTask.cs +++ b/src/Certify.Core/Management/DeploymentTasks/DeploymentTask.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Certify.Config; @@ -46,7 +47,15 @@ public async Task> Execute( } else { - return new List { new ActionResult { IsSuccess = true, Message = "Task is review mode only. Not action performed." } }; + var validation = await TaskProvider.Validate(execParams); + if (validation == null || !validation.Any(r => r.IsSuccess == false)) + { + return new List { new ActionResult { IsSuccess = true, Message = "Task is valid and ready to execute." } }; + } + else + { + return validation; + } } } catch (Exception exp) From e58894c3b44e7d831b76c9203e1ede0b6dd66c41 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 8 Feb 2024 12:41:37 +0800 Subject: [PATCH 125/328] API: add user auth context passed through to service during calls --- src/Certify.CLI/CertifyCLI.Backup.cs | 5 +- src/Certify.Client/CertifyApiClient.cs | 345 ++++++++++-------- src/Certify.Client/ICertifyClient.cs | 109 +++--- .../Controllers/internal/AccessController.cs | 2 +- .../Controllers/internal/ApiControllerBase.cs | 48 +++ .../CertificateAuthorityController.cs | 2 +- .../internal/ChallengeProviderController.cs | 2 +- .../internal/DeploymentTaskController.cs | 2 +- .../Controllers/internal/PreviewController.cs | 2 +- .../internal/StoredCredentialController.cs | 2 +- .../Controllers/internal/TargetController.cs | 2 +- .../Controllers/v1/AuthController.cs | 9 +- .../Controllers/v1/CertificateController.cs | 24 +- .../Controllers/v1/SystemController.cs | 2 +- .../Controllers/v1/ValidationController.cs | 2 +- .../Middleware/AuthenticationExtension.cs | 1 + .../Controllers/AccessController.cs | 9 +- .../ServiceAuthTests.cs | 12 +- .../ViewModelTests.cs | 9 +- .../AppViewModel/AppViewModel.Settings.cs | 12 +- 20 files changed, 339 insertions(+), 262 deletions(-) create mode 100644 src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/ApiControllerBase.cs diff --git a/src/Certify.CLI/CertifyCLI.Backup.cs b/src/Certify.CLI/CertifyCLI.Backup.cs index bdba39de3..695a47215 100644 --- a/src/Certify.CLI/CertifyCLI.Backup.cs +++ b/src/Certify.CLI/CertifyCLI.Backup.cs @@ -2,6 +2,7 @@ using System.IO; using System.Linq; using System.Threading.Tasks; +using Certify.Client; using Certify.Models.Config.Migration; using Newtonsoft.Json; @@ -26,7 +27,7 @@ public async Task PerformBackupExport(string[] args) var exportRequest = new ExportRequest { IsPreviewMode = false, Settings = new ExportSettings { EncryptionSecret = secret, ExportAllStoredCredentials = true } }; - var export = await _certifyClient.PerformExport(exportRequest); + var export = await _certifyClient.PerformExport(exportRequest, null); System.IO.File.WriteAllText(filename, JsonConvert.SerializeObject(export)); @@ -64,7 +65,7 @@ public async Task PerformBackupImport(string[] args) return; } - var importSteps = await _certifyClient.PerformImport(importRequest); + var importSteps = await _certifyClient.PerformImport(importRequest, null); foreach (var s in importSteps) { diff --git a/src/Certify.Client/CertifyApiClient.cs b/src/Certify.Client/CertifyApiClient.cs index bb06dfbd9..9ded60dd1 100644 --- a/src/Certify.Client/CertifyApiClient.cs +++ b/src/Certify.Client/CertifyApiClient.cs @@ -54,6 +54,12 @@ protected override Task SendAsync( ); } + public class AuthContext + { + public string Username { get; set; } + public string Token { get; set; } + } + // This version of the client communicates with the Certify.Service instance on the local machine public partial class CertifyApiClient : ICertifyInternalApiClient { @@ -134,19 +140,33 @@ public void SetConnectionAuthMode(string mode) CreateHttpClient(); } - private async Task FetchAsync(string endpoint) + private void SetAuthContextForRequest(HttpRequestMessage request, AuthContext authContext) + { + if (authContext != null) + { + request.Headers.Add("X-Context-User-Id", authContext.Username); + } + } + + private async Task FetchAsync(string endpoint, AuthContext authContext) { try { - var response = await _client.GetAsync(_baseUri + endpoint); - if (response.IsSuccessStatusCode) - { - return await response.Content.ReadAsStringAsync(); - } - else + using (var request = new HttpRequestMessage(HttpMethod.Get, new Uri(_baseUri + endpoint))) { - var error = await response.Content.ReadAsStringAsync(); - throw new ServiceCommsException($"Internal Service Error: {endpoint}: {error} "); + SetAuthContextForRequest(request, authContext); + + var response = await _client.SendAsync(request); + + if (response.IsSuccessStatusCode) + { + return await response.Content.ReadAsStringAsync(); + } + else + { + var error = await response.Content.ReadAsStringAsync(); + throw new ServiceCommsException($"Internal Service Error: {endpoint}: {error} "); + } } } catch (HttpRequestException exp) @@ -160,7 +180,7 @@ public class ServerErrorMsg public string Message; } - private async Task PostAsync(string endpoint, object data) + private async Task PostAsync(string endpoint, object data, AuthContext authContext) { if (data != null) { @@ -169,30 +189,37 @@ private async Task PostAsync(string endpoint, object data) try { - var response = await _client.PostAsync(_baseUri + endpoint, content); - if (response.IsSuccessStatusCode) + using (var request = new HttpRequestMessage(HttpMethod.Post, new Uri(_baseUri + endpoint))) { - return response; - } - else - { - var error = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + SetAuthContextForRequest(request, authContext); + + request.Content = content; - if (response.StatusCode == HttpStatusCode.Unauthorized) + var response = await _client.SendAsync(request); + if (response.IsSuccessStatusCode) { - throw new ServiceCommsException($"API Access Denied: {endpoint}: {error}"); + return response; } else { + var error = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - if (response.StatusCode == HttpStatusCode.InternalServerError && error.Contains("\"message\"")) + if (response.StatusCode == HttpStatusCode.Unauthorized) { - var err = JsonConvert.DeserializeObject(error); - throw new ServiceCommsException($"Internal Service Error: {endpoint}: {err.Message}"); + throw new ServiceCommsException($"API Access Denied: {endpoint}: {error}"); } else { - throw new ServiceCommsException($"Internal Service Error: {endpoint}: {error}"); + + if (response.StatusCode == HttpStatusCode.InternalServerError && error.Contains("\"message\"")) + { + var err = JsonConvert.DeserializeObject(error); + throw new ServiceCommsException($"Internal Service Error: {endpoint}: {err.Message}"); + } + else + { + throw new ServiceCommsException($"Internal Service Error: {endpoint}: {error}"); + } } } } @@ -217,29 +244,35 @@ private async Task PostAsync(string endpoint, object data) } } - private async Task DeleteAsync(string endpoint) + private async Task DeleteAsync(string endpoint, AuthContext authContext) { - var response = await _client.DeleteAsync(_baseUri + endpoint); - if (response.IsSuccessStatusCode) - { - return response; - } - else + using (var request = new HttpRequestMessage(HttpMethod.Delete, new Uri(_baseUri + endpoint))) { - var error = await response.Content.ReadAsStringAsync(); - throw new ServiceCommsException($"Internal Service Error: {endpoint}: {error}"); + SetAuthContextForRequest(request, authContext); + + var response = await _client.SendAsync(request); + + if (response.IsSuccessStatusCode) + { + return response; + } + else + { + var error = await response.Content.ReadAsStringAsync(); + throw new ServiceCommsException($"Internal Service Error: {endpoint}: {error}"); + } } } #region System - public async Task GetAppVersion() => await FetchAsync("system/appversion"); + public async Task GetAppVersion(AuthContext authContext = null) => await FetchAsync("system/appversion", authContext); - public async Task CheckForUpdates() + public async Task CheckForUpdates(AuthContext authContext = null) { try { - var result = await FetchAsync("system/updatecheck"); + var result = await FetchAsync("system/updatecheck", authContext); return JsonConvert.DeserializeObject(result); } catch (Exception) @@ -249,97 +282,89 @@ public async Task CheckForUpdates() } } - public async Task> PerformServiceDiagnostics() + public async Task> PerformServiceDiagnostics(AuthContext authContext = null) { - var result = await FetchAsync("system/diagnostics"); + var result = await FetchAsync("system/diagnostics", authContext); return JsonConvert.DeserializeObject>(result); } - /*public async Task PerformExport(ExportRequest exportRequest) + public async Task> SetDefaultDataStore(string dataStoreId, AuthContext authContext = null) { - var result = await PostAsync("system/migration/export", exportRequest); - return JsonConvert.DeserializeObject(await result.Content.ReadAsStringAsync()); - } - - public async Task> PerformImport(ImportRequest importRequest) - { - var result = await PostAsync("system/migration/import", importRequest); - return JsonConvert.DeserializeObject>(await result.Content.ReadAsStringAsync()); - }*/ - public async Task> SetDefaultDataStore(string dataStoreId) - { - var result = await PostAsync($"system/datastores/setdefault/{dataStoreId}", null); + var result = await PostAsync($"system/datastores/setdefault/{dataStoreId}", null, authContext); return JsonConvert.DeserializeObject>(await result.Content.ReadAsStringAsync()); } - public async Task> GetDataStoreProviders() + + public async Task> GetDataStoreProviders(AuthContext authContext = null) { - var result = await FetchAsync("system/datastores/providers"); + var result = await FetchAsync("system/datastores/providers", authContext); return JsonConvert.DeserializeObject>(result); } - public async Task> GetDataStoreConnections() + + public async Task> GetDataStoreConnections(AuthContext authContext = null) { - var result = await FetchAsync("system/datastores/"); + var result = await FetchAsync("system/datastores/", authContext); return JsonConvert.DeserializeObject>(result); } - public async Task> CopyDataStore(string sourceId, string targetId) + + public async Task> CopyDataStore(string sourceId, string targetId, AuthContext authContext = null) { - var result = await PostAsync($"system/datastores/copy/{sourceId}/{targetId}", null); + var result = await PostAsync($"system/datastores/copy/{sourceId}/{targetId}", null, authContext: authContext); return JsonConvert.DeserializeObject>(await result.Content.ReadAsStringAsync()); } - public async Task> UpdateDataStoreConnection(DataStoreConnection dataStoreConnection) + public async Task> UpdateDataStoreConnection(DataStoreConnection dataStoreConnection, AuthContext authContext = null) { - var result = await PostAsync($"system/datastores/update", dataStoreConnection); + var result = await PostAsync($"system/datastores/update", dataStoreConnection, authContext); return JsonConvert.DeserializeObject>(await result.Content.ReadAsStringAsync()); } - public async Task> TestDataStoreConnection(DataStoreConnection dataStoreConnection) + public async Task> TestDataStoreConnection(DataStoreConnection dataStoreConnection, AuthContext authContext = null) { - var result = await PostAsync($"system/datastores/test", dataStoreConnection); + var result = await PostAsync($"system/datastores/test", dataStoreConnection, authContext); return JsonConvert.DeserializeObject>(await result.Content.ReadAsStringAsync()); } #endregion System #region Server - public async Task IsServerAvailable(StandardServerTypes serverType) + public async Task IsServerAvailable(StandardServerTypes serverType, AuthContext authContext = null) { - var result = await FetchAsync($"server/isavailable/{serverType}"); + var result = await FetchAsync($"server/isavailable/{serverType}", authContext); return bool.Parse(result); } - public async Task> GetServerSiteList(StandardServerTypes serverType, string itemId = null) + public async Task> GetServerSiteList(StandardServerTypes serverType, string itemId = null, AuthContext authContext = null) { if (string.IsNullOrEmpty(itemId)) { - var result = await FetchAsync($"server/sitelist/{serverType}"); + var result = await FetchAsync($"server/sitelist/{serverType}", authContext); return JsonConvert.DeserializeObject>(result); } else { - var result = await FetchAsync($"server/sitelist/{serverType}/{itemId}"); + var result = await FetchAsync($"server/sitelist/{serverType}/{itemId}", authContext); return JsonConvert.DeserializeObject>(result); } } - public async Task GetServerVersion(StandardServerTypes serverType) + public async Task GetServerVersion(StandardServerTypes serverType, AuthContext authContext = null) { - var result = await FetchAsync($"server/version/{serverType}"); + var result = await FetchAsync($"server/version/{serverType}", authContext); var versionString = JsonConvert.DeserializeObject(result, new Newtonsoft.Json.Converters.VersionConverter()); var version = Version.Parse(versionString); return version; } - public async Task> GetServerSiteDomains(StandardServerTypes serverType, string serverSiteId) + public async Task> GetServerSiteDomains(StandardServerTypes serverType, string serverSiteId, AuthContext authContext = null) { - var result = await FetchAsync($"server/sitedomains/{serverType}/{serverSiteId}"); + var result = await FetchAsync($"server/sitedomains/{serverType}/{serverSiteId}", authContext); return JsonConvert.DeserializeObject>(result); } - public async Task> RunConfigurationDiagnostics(StandardServerTypes serverType, string serverSiteId) + public async Task> RunConfigurationDiagnostics(StandardServerTypes serverType, string serverSiteId, AuthContext authContext = null) { - var results = await FetchAsync($"server/diagnostics/{serverType}/{serverSiteId}"); + var results = await FetchAsync($"server/diagnostics/{serverType}/{serverSiteId}", authContext); return JsonConvert.DeserializeObject>(results); } @@ -347,15 +372,15 @@ public async Task> RunConfigurationDiagnostics(StandardServerTy #region Preferences - public async Task GetPreferences() + public async Task GetPreferences(AuthContext authContext = null) { - var result = await FetchAsync("preferences/"); + var result = await FetchAsync("preferences/", authContext); return JsonConvert.DeserializeObject(result); } - public async Task SetPreferences(Preferences preferences) + public async Task SetPreferences(Preferences preferences, AuthContext authContext = null) { - _ = await PostAsync("preferences/", preferences); + _ = await PostAsync("preferences/", preferences, authContext); return true; } @@ -363,9 +388,9 @@ public async Task SetPreferences(Preferences preferences) #region Managed Certificates - public async Task> GetManagedCertificates(ManagedCertificateFilter filter) + public async Task> GetManagedCertificates(ManagedCertificateFilter filter, AuthContext authContext = null) { - var response = await PostAsync("managedcertificates/search/", filter); + var response = await PostAsync("managedcertificates/search/", filter, authContext); var serializer = new JsonSerializer(); using (var sr = new StreamReader(await response.Content.ReadAsStreamAsync())) @@ -386,9 +411,9 @@ public async Task> GetManagedCertificates(ManagedCertif /// /// /// - public async Task GetManagedCertificateSearchResult(ManagedCertificateFilter filter) + public async Task GetManagedCertificateSearchResult(ManagedCertificateFilter filter, AuthContext authContext = null) { - var response = await PostAsync("managedcertificates/results/", filter); + var response = await PostAsync("managedcertificates/results/", filter, authContext); var serializer = new JsonSerializer(); using (var sr = new StreamReader(await response.Content.ReadAsStreamAsync())) @@ -399,9 +424,9 @@ public async Task GetManagedCertificateSearchRes } } - public async Task GetManagedCertificateSummary(ManagedCertificateFilter filter) + public async Task GetManagedCertificateSummary(ManagedCertificateFilter filter, AuthContext authContext = null) { - var response = await PostAsync("managedcertificates/summary/", filter); + var response = await PostAsync("managedcertificates/summary/", filter, authContext); var serializer = new JsonSerializer(); using (var sr = new StreamReader(await response.Content.ReadAsStreamAsync())) @@ -412,9 +437,9 @@ public async Task GetManagedCertificateSummary(ManagedCertificateFilter } } - public async Task GetManagedCertificate(string managedItemId) + public async Task GetManagedCertificate(string managedItemId, AuthContext authContext = null) { - var result = await FetchAsync($"managedcertificates/{managedItemId}"); + var result = await FetchAsync($"managedcertificates/{managedItemId}", authContext); var site = JsonConvert.DeserializeObject(result); if (site != null) { @@ -424,28 +449,28 @@ public async Task GetManagedCertificate(string managedItemId return site; } - public async Task UpdateManagedCertificate(ManagedCertificate site) + public async Task UpdateManagedCertificate(ManagedCertificate site, AuthContext authContext = null) { - var response = await PostAsync("managedcertificates/update", site); + var response = await PostAsync("managedcertificates/update", site, authContext); var json = await response.Content.ReadAsStringAsync(); return JsonConvert.DeserializeObject(json); } - public async Task DeleteManagedCertificate(string managedItemId) + public async Task DeleteManagedCertificate(string managedItemId, AuthContext authContext = null) { - var response = await DeleteAsync($"managedcertificates/delete/{managedItemId}"); + var response = await DeleteAsync($"managedcertificates/delete/{managedItemId}", authContext); return JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); } - public async Task RevokeManageSiteCertificate(string managedItemId) + public async Task RevokeManageSiteCertificate(string managedItemId, AuthContext authContext = null) { - var response = await FetchAsync($"managedcertificates/revoke/{managedItemId}"); + var response = await FetchAsync($"managedcertificates/revoke/{managedItemId}", authContext); return JsonConvert.DeserializeObject(response); } - public async Task> BeginAutoRenewal(RenewalSettings settings) + public async Task> BeginAutoRenewal(RenewalSettings settings, AuthContext authContext) { - var response = await PostAsync("managedcertificates/autorenew", settings); + var response = await PostAsync("managedcertificates/autorenew", settings, authContext); var serializer = new JsonSerializer(); using (var sr = new StreamReader(await response.Content.ReadAsStreamAsync())) using (var reader = new JsonTextReader(sr)) @@ -455,11 +480,11 @@ public async Task> BeginAutoRenewal(RenewalSettin } } - public async Task BeginCertificateRequest(string managedItemId, bool resumePaused, bool isInteractive) + public async Task BeginCertificateRequest(string managedItemId, bool resumePaused, bool isInteractive, AuthContext authContext = null) { try { - var response = await FetchAsync($"managedcertificates/renewcert/{managedItemId}/{resumePaused}/{isInteractive}"); + var response = await FetchAsync($"managedcertificates/renewcert/{managedItemId}/{resumePaused}/{isInteractive}", authContext); return JsonConvert.DeserializeObject(response); } catch (Exception exp) @@ -473,82 +498,82 @@ public async Task BeginCertificateRequest(string manag } } - public async Task> TestChallengeConfiguration(ManagedCertificate site) + public async Task> TestChallengeConfiguration(ManagedCertificate site, AuthContext authContext = null) { - var response = await PostAsync($"managedcertificates/testconfig", site); + var response = await PostAsync($"managedcertificates/testconfig", site, authContext); return JsonConvert.DeserializeObject>(await response.Content.ReadAsStringAsync()); } - public async Task> PerformChallengeCleanup(ManagedCertificate site) + public async Task> PerformChallengeCleanup(ManagedCertificate site, AuthContext authContext = null) { - var response = await PostAsync($"managedcertificates/challengecleanup", site); + var response = await PostAsync($"managedcertificates/challengecleanup", site, authContext); return JsonConvert.DeserializeObject>(await response.Content.ReadAsStringAsync()); } - public async Task> GetDnsProviderZones(string providerTypeId, string credentialsId) + public async Task> GetDnsProviderZones(string providerTypeId, string credentialsId, AuthContext authContext = null) { - var json = await FetchAsync($"managedcertificates/dnszones/{providerTypeId}/{credentialsId}"); + var json = await FetchAsync($"managedcertificates/dnszones/{providerTypeId}/{credentialsId}", authContext); return JsonConvert.DeserializeObject>(json); } - public async Task> PreviewActions(ManagedCertificate site) + public async Task> PreviewActions(ManagedCertificate site, AuthContext authContext = null) { - var response = await PostAsync($"managedcertificates/preview", site); + var response = await PostAsync($"managedcertificates/preview", site, authContext); return JsonConvert.DeserializeObject>(await response.Content.ReadAsStringAsync()); } - public async Task> RedeployManagedCertificates(bool isPreviewOnly, bool includeDeploymentTasks) + public async Task> RedeployManagedCertificates(bool isPreviewOnly, bool includeDeploymentTasks, AuthContext authContext = null) { - var response = await FetchAsync($"managedcertificates/redeploy/{isPreviewOnly}/{includeDeploymentTasks}"); + var response = await FetchAsync($"managedcertificates/redeploy/{isPreviewOnly}/{includeDeploymentTasks}", authContext); return JsonConvert.DeserializeObject>(response); } - public async Task ReapplyCertificateBindings(string managedItemId, bool isPreviewOnly, bool includeDeploymentTasks) + public async Task ReapplyCertificateBindings(string managedItemId, bool isPreviewOnly, bool includeDeploymentTasks, AuthContext authContext = null) { - var response = await FetchAsync($"managedcertificates/reapply/{managedItemId}/{isPreviewOnly}/{includeDeploymentTasks}"); + var response = await FetchAsync($"managedcertificates/reapply/{managedItemId}/{isPreviewOnly}/{includeDeploymentTasks}", authContext); return JsonConvert.DeserializeObject(response); } - public async Task RefetchCertificate(string managedItemId) + public async Task RefetchCertificate(string managedItemId, AuthContext authContext = null) { - var response = await FetchAsync($"managedcertificates/fetch/{managedItemId}/{false}"); + var response = await FetchAsync($"managedcertificates/fetch/{managedItemId}/{false}", authContext); return JsonConvert.DeserializeObject(response); } - public async Task> GetChallengeAPIList() + public async Task> GetChallengeAPIList(AuthContext authContext = null) { - var response = await FetchAsync($"managedcertificates/challengeapis/"); + var response = await FetchAsync($"managedcertificates/challengeapis/", authContext); return JsonConvert.DeserializeObject>(response); } - public async Task> GetCurrentChallenges(string type, string key) + public async Task> GetCurrentChallenges(string type, string key, AuthContext authContext = null) { - var result = await FetchAsync($"managedcertificates/currentchallenges/{type}/{key}"); + var result = await FetchAsync($"managedcertificates/currentchallenges/{type}/{key}", authContext); return JsonConvert.DeserializeObject>(result); } - public async Task> GetDeploymentProviderList() + public async Task> GetDeploymentProviderList(AuthContext authContext = null) { - var response = await FetchAsync($"managedcertificates/deploymentproviders/"); + var response = await FetchAsync($"managedcertificates/deploymentproviders/", authContext); return JsonConvert.DeserializeObject>(response); } - public async Task GetDeploymentProviderDefinition(string id, Config.DeploymentTaskConfig config) + public async Task GetDeploymentProviderDefinition(string id, Config.DeploymentTaskConfig config, AuthContext authContext) { - var response = await PostAsync($"managedcertificates/deploymentprovider/{id}", config); + var response = await PostAsync($"managedcertificates/deploymentprovider/{id}", config, authContext); return JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); } - public async Task> PerformDeployment(string managedCertificateId, string taskId, bool isPreviewOnly, bool forceTaskExecute) + public async Task> PerformDeployment(string managedCertificateId, string taskId, bool isPreviewOnly, bool forceTaskExecute, AuthContext authContext) { if (!forceTaskExecute) { if (string.IsNullOrEmpty(taskId)) { - var response = await FetchAsync($"managedcertificates/performdeployment/{isPreviewOnly}/{managedCertificateId}"); + var response = await FetchAsync($"managedcertificates/performdeployment/{isPreviewOnly}/{managedCertificateId}", authContext); return JsonConvert.DeserializeObject>(response); } else { - var response = await FetchAsync($"managedcertificates/performdeployment/{isPreviewOnly}/{managedCertificateId}/{taskId}"); + var response = await FetchAsync($"managedcertificates/performdeployment/{isPreviewOnly}/{managedCertificateId}/{taskId}", authContext); return JsonConvert.DeserializeObject>(response); } } @@ -556,32 +581,32 @@ public async Task> PerformDeployment(string managedCertificateI { if (string.IsNullOrEmpty(taskId)) { - var response = await FetchAsync($"managedcertificates/performforceddeployment/{isPreviewOnly}/{managedCertificateId}"); + var response = await FetchAsync($"managedcertificates/performforceddeployment/{isPreviewOnly}/{managedCertificateId}", authContext); return JsonConvert.DeserializeObject>(response); } else { - var response = await FetchAsync($"managedcertificates/performforceddeployment/{isPreviewOnly}/{managedCertificateId}/{taskId}"); + var response = await FetchAsync($"managedcertificates/performforceddeployment/{isPreviewOnly}/{managedCertificateId}/{taskId}", authContext); return JsonConvert.DeserializeObject>(response); } } } - public async Task> ValidateDeploymentTask(DeploymentTaskValidationInfo info) + public async Task> ValidateDeploymentTask(DeploymentTaskValidationInfo info, AuthContext authContext = null) { - var result = await PostAsync($"managedcertificates/validatedeploymenttask", info); + var result = await PostAsync($"managedcertificates/validatedeploymenttask", info, authContext); return JsonConvert.DeserializeObject>(await result.Content.ReadAsStringAsync()); } - public async Task GetItemLog(string id, int limit) + public async Task GetItemLog(string id, int limit, AuthContext authContext = null) { - var response = await FetchAsync($"managedcertificates/log/{id}/{limit}"); + var response = await FetchAsync($"managedcertificates/log/{id}/{limit}", authContext); return JsonConvert.DeserializeObject(response); } - public async Task> PerformManagedCertMaintenance(string id = null) + public async Task> PerformManagedCertMaintenance(string id = null, AuthContext authContext = null) { - var result = await FetchAsync($"managedcertificates/maintenance/{id}"); + var result = await FetchAsync($"managedcertificates/maintenance/{id}", authContext); return JsonConvert.DeserializeObject>(result); } @@ -589,50 +614,50 @@ public async Task> PerformManagedCertMaintenance(string id = #region Accounts - public async Task> GetCertificateAuthorities() + public async Task> GetCertificateAuthorities(AuthContext authContext = null) { - var result = await FetchAsync("accounts/authorities"); + var result = await FetchAsync("accounts/authorities", authContext); return JsonConvert.DeserializeObject>(result); } - public async Task UpdateCertificateAuthority(CertificateAuthority ca) + public async Task UpdateCertificateAuthority(CertificateAuthority ca, AuthContext authContext =null) { - var result = await PostAsync("accounts/authorities", ca); + var result = await PostAsync("accounts/authorities", ca, authContext); return JsonConvert.DeserializeObject(await result.Content.ReadAsStringAsync()); } - public async Task DeleteCertificateAuthority(string id) + public async Task DeleteCertificateAuthority(string id, AuthContext authContext = null) { - var result = await DeleteAsync("accounts/authorities/" + id); + var result = await DeleteAsync("accounts/authorities/" + id, authContext); return JsonConvert.DeserializeObject(await result.Content.ReadAsStringAsync()); } - public async Task> GetAccounts() + public async Task> GetAccounts(AuthContext authContext = null) { - var result = await FetchAsync("accounts"); + var result = await FetchAsync("accounts", authContext); return JsonConvert.DeserializeObject>(result); } - public async Task AddAccount(ContactRegistration contact) + public async Task AddAccount(ContactRegistration contact, AuthContext authContext = null) { - var result = await PostAsync("accounts", contact); + var result = await PostAsync("accounts", contact, authContext); return JsonConvert.DeserializeObject(await result.Content.ReadAsStringAsync()); } - public async Task UpdateAccountContact(ContactRegistration contact) + public async Task UpdateAccountContact(ContactRegistration contact, AuthContext authContext = null) { - var result = await PostAsync($"accounts/update/{contact.StorageKey}", contact); + var result = await PostAsync($"accounts/update/{contact.StorageKey}", contact, authContext); return JsonConvert.DeserializeObject(await result.Content.ReadAsStringAsync()); } - public async Task RemoveAccount(string storageKey, bool deactivate) + public async Task RemoveAccount(string storageKey, bool deactivate, AuthContext authContext = null) { - var result = await DeleteAsync($"accounts/remove/{storageKey}/{deactivate}"); + var result = await DeleteAsync($"accounts/remove/{storageKey}/{deactivate}", authContext); return JsonConvert.DeserializeObject(await result.Content.ReadAsStringAsync()); } - public async Task ChangeAccountKey(string storageKey, string newKeyPEM) + public async Task ChangeAccountKey(string storageKey, string newKeyPEM, AuthContext authContext = null) { - var result = await PostAsync($"accounts/changekey/{storageKey}", new { newKeyPem = newKeyPEM }); + var result = await PostAsync($"accounts/changekey/{storageKey}", new { newKeyPem = newKeyPEM }, authContext); return JsonConvert.DeserializeObject(await result.Content.ReadAsStringAsync()); } @@ -640,42 +665,42 @@ public async Task ChangeAccountKey(string storageKey, string newKe #region Credentials - public async Task> GetCredentials() + public async Task> GetCredentials(AuthContext authContext = null) { - var result = await FetchAsync("credentials"); + var result = await FetchAsync("credentials", authContext); return JsonConvert.DeserializeObject>(result); } - public async Task UpdateCredentials(StoredCredential credential) + public async Task UpdateCredentials(StoredCredential credential, AuthContext authContext = null) { - var result = await PostAsync("credentials", credential); + var result = await PostAsync("credentials", credential, authContext); return JsonConvert.DeserializeObject(await result.Content.ReadAsStringAsync()); } - public async Task DeleteCredential(string credentialKey) + public async Task DeleteCredential(string credentialKey, AuthContext authContext = null) { - var result = await DeleteAsync($"credentials/{credentialKey}"); + var result = await DeleteAsync($"credentials/{credentialKey}", authContext); return JsonConvert.DeserializeObject(await result.Content.ReadAsStringAsync()); } - public async Task TestCredentials(string credentialKey) + public async Task TestCredentials(string credentialKey, AuthContext authContext = null) { - var result = await PostAsync($"credentials/{credentialKey}/test", new { }); + var result = await PostAsync($"credentials/{credentialKey}/test", new { }, authContext); return JsonConvert.DeserializeObject(await result.Content.ReadAsStringAsync()); } #endregion #region Auth - public async Task GetAuthKeyWindows() + public async Task GetAuthKeyWindows(AuthContext authContext) { - var result = await FetchAsync("auth/windows"); + var result = await FetchAsync("auth/windows", authContext); return JsonConvert.DeserializeObject(result); } - public async Task GetAccessToken(string key) + public async Task GetAccessToken(string key, AuthContext authContext) { - var result = await PostAsync("auth/token", new { Key = key }); + var result = await PostAsync("auth/token", new { Key = key }, authContext); _accessToken = JsonConvert.DeserializeObject(await result.Content.ReadAsStringAsync()); if (!string.IsNullOrEmpty(_accessToken)) @@ -686,9 +711,9 @@ public async Task GetAccessToken(string key) return _accessToken; } - public async Task GetAccessToken(string username, string password) + public async Task GetAccessToken(string username, string password, AuthContext authContext = null) { - var result = await PostAsync("auth/token", new { Username = username, Password = password }); + var result = await PostAsync("auth/token", new { Username = username, Password = password }, authContext); _accessToken = JsonConvert.DeserializeObject(await result.Content.ReadAsStringAsync()); if (!string.IsNullOrEmpty(_accessToken)) @@ -699,9 +724,9 @@ public async Task GetAccessToken(string username, string password) return _accessToken; } - public async Task RefreshAccessToken() + public async Task RefreshAccessToken(AuthContext authContext) { - var result = await PostAsync("auth/refresh", new { Token = _accessToken }); + var result = await PostAsync("auth/refresh", new { Token = _accessToken }, authContext); _accessToken = JsonConvert.DeserializeObject(await result.Content.ReadAsStringAsync()); if (!string.IsNullOrEmpty(_accessToken)) @@ -712,9 +737,9 @@ public async Task RefreshAccessToken() return _refreshToken; } - public async Task> GetAccessSecurityPrinciples() + public async Task> GetAccessSecurityPrinciples(AuthContext authContext) { - var result = await FetchAsync("access/securityprinciples"); + var result = await FetchAsync("access/securityprinciples", authContext); return JsonToObject>(result); } diff --git a/src/Certify.Client/ICertifyClient.cs b/src/Certify.Client/ICertifyClient.cs index f8bd95f1b..e546d0f2d 100644 --- a/src/Certify.Client/ICertifyClient.cs +++ b/src/Certify.Client/ICertifyClient.cs @@ -18,115 +18,111 @@ public partial interface ICertifyInternalApiClient #region System - Task GetAppVersion(); + Task GetAppVersion(AuthContext authContext = null); - Task CheckForUpdates(); + Task CheckForUpdates(AuthContext authContext = null); - Task> PerformServiceDiagnostics(); - Task> PerformManagedCertMaintenance(string id = null); + Task> PerformServiceDiagnostics(AuthContext authContext = null); + Task> PerformManagedCertMaintenance(string id = null, AuthContext authContext = null); - // Task PerformExport(ExportRequest exportRequest); - // Task> PerformImport(ImportRequest importRequest); - - Task> SetDefaultDataStore(string dataStoreId); - Task> GetDataStoreProviders(); - Task> GetDataStoreConnections(); - Task> CopyDataStore(string sourceId, string targetId); - Task> UpdateDataStoreConnection(DataStoreConnection dataStoreConnection); - Task> TestDataStoreConnection(DataStoreConnection dataStoreConnection); + Task> SetDefaultDataStore(string dataStoreId, AuthContext authContext = null); + Task> GetDataStoreProviders(AuthContext authContext = null); + Task> GetDataStoreConnections(AuthContext authContext = null); + Task> CopyDataStore(string sourceId, string targetId, AuthContext authContext = null); + Task> UpdateDataStoreConnection(DataStoreConnection dataStoreConnection, AuthContext authContext = null); + Task> TestDataStoreConnection(DataStoreConnection dataStoreConnection, AuthContext authContext = null); #endregion System #region Server + Task IsServerAvailable(StandardServerTypes serverType, AuthContext authContext = null); - Task IsServerAvailable(StandardServerTypes serverType); - - Task> GetServerSiteList(StandardServerTypes serverType, string itemId = null); + Task> GetServerSiteList(StandardServerTypes serverType, string itemId = null, AuthContext authContext = null); - Task GetServerVersion(StandardServerTypes serverType); + Task GetServerVersion(StandardServerTypes serverType, AuthContext authContext = null); - Task> GetServerSiteDomains(StandardServerTypes serverType, string serverSiteId); + Task> GetServerSiteDomains(StandardServerTypes serverType, string serverSiteId, AuthContext authContext = null); - Task> RunConfigurationDiagnostics(StandardServerTypes serverType, string serverSiteId); + Task> RunConfigurationDiagnostics(StandardServerTypes serverType, string serverSiteId, AuthContext authContext = null); - Task> GetCurrentChallenges(string type, string key); + Task> GetCurrentChallenges(string type, string key, AuthContext authContext = null); #endregion Server #region Preferences - Task GetPreferences(); + Task GetPreferences(AuthContext authContext = null); - Task SetPreferences(Preferences preferences); + Task SetPreferences(Preferences preferences, AuthContext authContext = null); #endregion Preferences #region Credentials - Task> GetCredentials(); + Task> GetCredentials(AuthContext authContext = null); - Task UpdateCredentials(StoredCredential credential); + Task UpdateCredentials(StoredCredential credential, AuthContext authContext = null); - Task DeleteCredential(string credentialKey); + Task DeleteCredential(string credentialKey, AuthContext authContext = null); - Task TestCredentials(string credentialKey); + Task TestCredentials(string credentialKey, AuthContext authContext = null); #endregion Credentials #region Managed Certificates - Task> GetManagedCertificates(ManagedCertificateFilter filter); - Task GetManagedCertificateSearchResult(ManagedCertificateFilter filter); - Task GetManagedCertificateSummary(ManagedCertificateFilter filter); + Task> GetManagedCertificates(ManagedCertificateFilter filter, AuthContext authContext = null); + Task GetManagedCertificateSearchResult(ManagedCertificateFilter filter, AuthContext authContext = null); + Task GetManagedCertificateSummary(ManagedCertificateFilter filter, AuthContext authContext = null); - Task GetManagedCertificate(string managedItemId); + Task GetManagedCertificate(string managedItemId, AuthContext authContext = null); - Task UpdateManagedCertificate(ManagedCertificate site); + Task UpdateManagedCertificate(ManagedCertificate site, AuthContext authContext = null); - Task DeleteManagedCertificate(string managedItemId); + Task DeleteManagedCertificate(string managedItemId, AuthContext authContext = null); - Task RevokeManageSiteCertificate(string managedItemId); + Task RevokeManageSiteCertificate(string managedItemId, AuthContext authContext = null); - Task> BeginAutoRenewal(RenewalSettings settings); + Task> BeginAutoRenewal(RenewalSettings settings, AuthContext authContext = null); - Task> RedeployManagedCertificates(bool isPreviewOnly, bool includeDeploymentTasks); + Task> RedeployManagedCertificates(bool isPreviewOnly, bool includeDeploymentTasks, AuthContext authContext = null); - Task ReapplyCertificateBindings(string managedItemId, bool isPreviewOnly, bool includeDeploymentTasks); + Task ReapplyCertificateBindings(string managedItemId, bool isPreviewOnly, bool includeDeploymentTasks, AuthContext authContext = null); - Task RefetchCertificate(string managedItemId); + Task RefetchCertificate(string managedItemId, AuthContext authContext = null); - Task BeginCertificateRequest(string managedItemId, bool resumePaused, bool isInteractive); + Task BeginCertificateRequest(string managedItemId, bool resumePaused, bool isInteractive, AuthContext authContext = null); - Task> TestChallengeConfiguration(ManagedCertificate site); - Task> PerformChallengeCleanup(ManagedCertificate site); + Task> TestChallengeConfiguration(ManagedCertificate site, AuthContext authContext = null); + Task> PerformChallengeCleanup(ManagedCertificate site, AuthContext authContext = null); - Task> GetDnsProviderZones(string providerTypeId, string credentialsId); + Task> GetDnsProviderZones(string providerTypeId, string credentialsId, AuthContext authContext = null); - Task> PreviewActions(ManagedCertificate site); + Task> PreviewActions(ManagedCertificate site, AuthContext authContext = null); - Task> GetChallengeAPIList(); + Task> GetChallengeAPIList(AuthContext authContext = null); - Task> GetDeploymentProviderList(); + Task> GetDeploymentProviderList(AuthContext authContext = null); - Task GetDeploymentProviderDefinition(string id, Config.DeploymentTaskConfig config); + Task GetDeploymentProviderDefinition(string id, Config.DeploymentTaskConfig config, AuthContext authContext = null); - Task> PerformDeployment(string managedCertificateId, string taskId, bool isPreviewOnly, bool forceTaskExecute); + Task> PerformDeployment(string managedCertificateId, string taskId, bool isPreviewOnly, bool forceTaskExecute, AuthContext authContext = null); - Task> ValidateDeploymentTask(DeploymentTaskValidationInfo info); + Task> ValidateDeploymentTask(DeploymentTaskValidationInfo info, AuthContext authContext = null); - Task GetItemLog(string id, int limit); + Task GetItemLog(string id, int limit, AuthContext authContext = null); #endregion Managed Certificates #region Accounts - Task> GetCertificateAuthorities(); - Task UpdateCertificateAuthority(CertificateAuthority ca); - Task DeleteCertificateAuthority(string id); - Task> GetAccounts(); - Task AddAccount(ContactRegistration contact); - Task UpdateAccountContact(ContactRegistration contact); - Task RemoveAccount(string storageKey, bool deactivate); - Task ChangeAccountKey(string storageKey, string newKeyPEM = null); + Task> GetCertificateAuthorities(AuthContext authContext = null); + Task UpdateCertificateAuthority(CertificateAuthority ca, AuthContext authContext = null); + Task DeleteCertificateAuthority(string id, AuthContext authContext = null); + Task> GetAccounts(AuthContext authContext = null); + Task AddAccount(ContactRegistration contact, AuthContext authContext = null); + Task UpdateAccountContact(ContactRegistration contact, AuthContext authContext = null); + Task RemoveAccount(string storageKey, bool deactivate, AuthContext authContext = null); + Task ChangeAccountKey(string storageKey, string newKeyPEM = null, AuthContext authContext = null); #endregion Accounts @@ -137,7 +133,6 @@ public partial interface ICertifyInternalApiClient /// public interface ICertifyClient : ICertifyInternalApiClient { - event Action OnMessageFromService; event Action OnRequestProgressStateUpdated; diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/AccessController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/AccessController.cs index fd92b18e4..91feadc4d 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/AccessController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/AccessController.cs @@ -8,7 +8,7 @@ namespace Certify.Server.Api.Public.Controllers /// [Route("internal/v1/[controller]")] [ApiController] - public partial class AccessController : ControllerBase + public partial class AccessController : ApiControllerBase { private readonly ILogger _logger; diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/ApiControllerBase.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/ApiControllerBase.cs new file mode 100644 index 000000000..4b46ef317 --- /dev/null +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/ApiControllerBase.cs @@ -0,0 +1,48 @@ +using System.Net.Http.Headers; +using Certify.Client; +using Microsoft.AspNetCore.Mvc; + +namespace Certify.Server.Api.Public.Controllers +{ + /// + /// Base class for public api controllers + /// + public partial class ApiControllerBase : ControllerBase + { + /// + /// Get the corresponding auth context to pass to the backend service + /// + /// + /// + internal AuthContext CurrentAuthContext + { + get + { + var _config = HttpContext.RequestServices.GetRequiredService(); + var jwt = new Api.Public.Services.JwtService(_config); + + var authHeader = Request.Headers["Authorization"]; + if (string.IsNullOrWhiteSpace(authHeader)) + { + return null; + } + + var authToken = AuthenticationHeaderValue.Parse(authHeader).Parameter; + + try + { + var claimsIdentity = jwt.ClaimsIdentityFromToken(authToken, false); + var username = claimsIdentity.Name; + + var authContext = new AuthContext { Token = authToken, Username = username }; + + return authContext; + } + catch (Exception) + { + return null; + } + } + } + } +} diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/CertificateAuthorityController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/CertificateAuthorityController.cs index 733fb2204..42964187c 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/CertificateAuthorityController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/CertificateAuthorityController.cs @@ -10,7 +10,7 @@ namespace Certify.Server.Api.Public.Controllers /// [ApiController] [Route("internal/v1/[controller]")] - public partial class CertificateAuthorityController : ControllerBase + public partial class CertificateAuthorityController : ApiControllerBase { private readonly ILogger _logger; diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/ChallengeProviderController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/ChallengeProviderController.cs index f0623000b..5b8002ab9 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/ChallengeProviderController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/ChallengeProviderController.cs @@ -11,7 +11,7 @@ namespace Certify.Server.Api.Public.Controllers /// [ApiController] [Route("internal/v1/[controller]")] - public partial class ChallengeProviderController : ControllerBase + public partial class ChallengeProviderController : ApiControllerBase { private readonly ILogger _logger; diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/DeploymentTaskController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/DeploymentTaskController.cs index f6689980a..c26c79d6d 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/DeploymentTaskController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/DeploymentTaskController.cs @@ -10,7 +10,7 @@ namespace Certify.Server.Api.Public.Controllers /// [ApiController] [Route("internal/v1/[controller]")] - public partial class DeploymentTaskController : ControllerBase + public partial class DeploymentTaskController : ApiControllerBase { private readonly ILogger _logger; diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/PreviewController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/PreviewController.cs index 5ba7728c9..412d787e9 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/PreviewController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/PreviewController.cs @@ -11,7 +11,7 @@ namespace Certify.Server.Api.Public.Controllers /// [ApiController] [Route("internal/v1/[controller]")] - public partial class PreviewController : ControllerBase + public partial class PreviewController : ApiControllerBase { private readonly ILogger _logger; diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/StoredCredentialController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/StoredCredentialController.cs index c22fe5c57..54f31f8c7 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/StoredCredentialController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/StoredCredentialController.cs @@ -10,7 +10,7 @@ namespace Certify.Server.Api.Public.Controllers /// [ApiController] [Route("internal/v1/[controller]")] - public partial class StoredCredentialController : ControllerBase + public partial class StoredCredentialController : ApiControllerBase { private readonly ILogger _logger; diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/TargetController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/TargetController.cs index 895fbcb26..3f1d8148e 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/TargetController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/TargetController.cs @@ -11,7 +11,7 @@ namespace Certify.Server.Api.Public.Controllers /// [ApiController] [Route("internal/v1/[controller]")] - public partial class TargetController : ControllerBase + public partial class TargetController : ApiControllerBase { private readonly ILogger _logger; diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/AuthController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/AuthController.cs index a6f0213a9..f71fb8a79 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/AuthController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/AuthController.cs @@ -13,7 +13,7 @@ namespace Certify.Server.Api.Public.Controllers /// [ApiController] [Route("api/v1/[controller]")] - public partial class AuthController : ControllerBase + public partial class AuthController : ApiControllerBase { private readonly ILogger _logger; private readonly ICertifyInternalApiClient _client; @@ -54,8 +54,9 @@ public async Task CheckAuthStatus() [ProducesResponseType(typeof(AuthResponse), 200)] public async Task Login(AuthRequest login) { + // check users login, if valid issue new JWT access token and refresh token based on their identity - var validation = await _client.ValidateSecurityPrinciplePassword(new SecurityPrinciplePasswordCheck() { Username = login.Username, Password = login.Password }); + var validation = await _client.ValidateSecurityPrinciplePassword(new SecurityPrinciplePasswordCheck() { Username = login.Username, Password = login.Password }, CurrentAuthContext); if (validation.IsSuccess) { @@ -68,9 +69,11 @@ public async Task Login(AuthRequest login) Detail = "OK", AccessToken = jwt.GenerateSecurityToken(login.Username, double.Parse(_config["JwtSettings:authTokenExpirationInMinutes"])), RefreshToken = jwt.GenerateRefreshToken(), - SecurityPrinciple = validation.SecurityPrinciple + SecurityPrinciple = validation.SecurityPrinciple, + RoleStatus = await _client.GetSecurityPrincipleRoleStatus(validation.SecurityPrinciple.Id, CurrentAuthContext) }; + // TODO: Refresh token should be stored or hashed for later use return Ok(authResponse); diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs index 1974fb214..91bfe91b2 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs @@ -12,7 +12,7 @@ namespace Certify.Server.Api.Public.Controllers /// [ApiController] [Route("api/v1/[controller]")] - public partial class CertificateController : ControllerBase + public partial class CertificateController : ApiControllerBase { private readonly ILogger _logger; @@ -55,9 +55,9 @@ public async Task Download(string managedCertId, string format, s } // TODO: certify manager to do all the cert conversion work, server may be on another machine - var managedCert = await _client.GetManagedCertificate(managedCertId); + var managedCert = await _client.GetManagedCertificate(managedCertId, CurrentAuthContext); - if (managedCert == null) + if (managedCert?.CertificatePath == null) { return new NotFoundResult(); } @@ -80,7 +80,7 @@ public async Task Download(string managedCertId, string format, s [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(LogResult))] public async Task DownloadLog(string managedCertId, int maxLines = 1000) { - var managedCert = await _client.GetManagedCertificate(managedCertId); + var managedCert = await _client.GetManagedCertificate(managedCertId, CurrentAuthContext); if (managedCert == null) { @@ -92,7 +92,7 @@ public async Task DownloadLog(string managedCertId, int maxLines maxLines = 1000; } - var log = await _client.GetItemLog(managedCertId, maxLines); + var log = await _client.GetItemLog(managedCertId, maxLines, CurrentAuthContext); return new OkObjectResult(new LogResult { Items = log }); } @@ -115,7 +115,7 @@ public async Task GetManagedCertificates(string? keyword, int? pa Keyword = keyword, PageIndex = page, PageSize = pageSize - }); + }, CurrentAuthContext); var list = managedCertResult.Results.Select(i => new ManagedCertificateSummary { @@ -157,7 +157,7 @@ public async Task GetManagedCertificateSummary(string? keyword) new Models.ManagedCertificateFilter { Keyword = keyword - }); + }, CurrentAuthContext); return new OkObjectResult(summary); } @@ -174,7 +174,7 @@ public async Task GetManagedCertificateSummary(string? keyword) public async Task GetManagedCertificateDetails(string managedCertId) { - var managedCert = await _client.GetManagedCertificate(managedCertId); + var managedCert = await _client.GetManagedCertificate(managedCertId, CurrentAuthContext); return new OkObjectResult(managedCert); } @@ -191,7 +191,7 @@ public async Task GetManagedCertificateDetails(string managedCert public async Task UpdateManagedCertificateDetails(Models.ManagedCertificate managedCertificate) { - var result = await _client.UpdateManagedCertificate(managedCertificate); + var result = await _client.UpdateManagedCertificate(managedCertificate, CurrentAuthContext); if (result != null) { return new OkObjectResult(result); @@ -214,7 +214,7 @@ public async Task UpdateManagedCertificateDetails(Models.ManagedC public async Task BeginOrder(string id) { - var result = await _client.BeginCertificateRequest(id, true, false); + var result = await _client.BeginCertificateRequest(id, true, false, CurrentAuthContext); if (result != null) { return new OkObjectResult(result); @@ -237,7 +237,7 @@ public async Task BeginOrder(string id) public async Task PerformRenewal(Models.RenewalSettings settings) { - var results = await _client.BeginAutoRenewal(settings); + var results = await _client.BeginAutoRenewal(settings, CurrentAuthContext); if (results != null) { return new OkObjectResult(results); @@ -260,7 +260,7 @@ public async Task PerformRenewal(Models.RenewalSettings settings) public async Task PerformConfigurationTest(Models.ManagedCertificate item) { - var results = await _client.TestChallengeConfiguration(item); + var results = await _client.TestChallengeConfiguration(item, CurrentAuthContext); if (results != null) { return new OkObjectResult(results); diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/SystemController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/SystemController.cs index b87dc90c2..11488d3eb 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/SystemController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/SystemController.cs @@ -8,7 +8,7 @@ namespace Certify.Server.Api.Public.Controllers /// [ApiController] [Route("api/v1/[controller]")] - public partial class SystemController : ControllerBase + public partial class SystemController : ApiControllerBase { private readonly ILogger _logger; diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/ValidationController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/ValidationController.cs index 71b4127bc..fa1aabf02 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/ValidationController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/ValidationController.cs @@ -10,7 +10,7 @@ namespace Certify.Server.Api.Public.Controllers /// [ApiController] [Route("api/v1/[controller]")] - public partial class ValidationController : ControllerBase + public partial class ValidationController : ApiControllerBase { private readonly ILogger _logger; diff --git a/src/Certify.Server/Certify.Server.Api.Public/Middleware/AuthenticationExtension.cs b/src/Certify.Server/Certify.Server.Api.Public/Middleware/AuthenticationExtension.cs index 990ee518f..459f2d2e1 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Middleware/AuthenticationExtension.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Middleware/AuthenticationExtension.cs @@ -45,6 +45,7 @@ public static IServiceCollection AddTokenAuthentication(this IServiceCollection }; }); + return services; } } diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccessController.cs b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccessController.cs index c939b41d7..2f7f9264c 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccessController.cs +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccessController.cs @@ -1,4 +1,4 @@ -using Certify.Core.Management.Access; +using Certify.Core.Management.Access; using Certify.Management; using Certify.Models.API; using Certify.Models.Config.AccessControl; @@ -25,13 +25,6 @@ private string GetContextUserId() // TODO: sign passed value provided by public API using public APIs access token var contextUserId = Request.Headers["X-Context-User-Id"]; -#if DEBUG - if (string.IsNullOrEmpty(contextUserId)) - { - // TODO: our context user has to at least come from a valid JWT claim - contextUserId = "admin_01"; - } -#endif return contextUserId; } diff --git a/src/Certify.Tests/Certify.Service.Tests.Integration/ServiceAuthTests.cs b/src/Certify.Tests/Certify.Service.Tests.Integration/ServiceAuthTests.cs index 8380a8096..8880b2258 100644 --- a/src/Certify.Tests/Certify.Service.Tests.Integration/ServiceAuthTests.cs +++ b/src/Certify.Tests/Certify.Service.Tests.Integration/ServiceAuthTests.cs @@ -10,8 +10,10 @@ public class ServiceAuthTests : ServiceTestBase [TestMethod] public async Task TestAuthFlow() { + AuthContext authContext = null; + // use windows auth to acquire initial auth key - var authKey = await _client.GetAuthKeyWindows(); + var authKey = await _client.GetAuthKeyWindows(authContext); Assert.IsNotNull(authKey); // attempt request without jwt auth being set yet @@ -20,21 +22,21 @@ public async Task TestAuthFlow() // check should throw exception await Assert.ThrowsExceptionAsync(async () => { - var noAuthResult = await _client.GetManagedCertificates(new Models.ManagedCertificateFilter { }); + var noAuthResult = await _client.GetManagedCertificates(new Models.ManagedCertificateFilter { }, authContext); Assert.IsNull(noAuthResult); }); // use auth key to get JWT - var jwt = await _client.GetAccessToken(authKey); + var jwt = await _client.GetAccessToken(authKey, authContext); Assert.IsNotNull(jwt); // attempt request with JWT set - var authedResult = await _client.GetManagedCertificates(new Models.ManagedCertificateFilter { }); + var authedResult = await _client.GetManagedCertificates(new Models.ManagedCertificateFilter { }, authContext); Assert.IsNotNull(authedResult); // refresh JWT - var refreshedToken = await _client.RefreshAccessToken(); + var refreshedToken = await _client.RefreshAccessToken(authContext); Assert.IsNotNull(jwt); } diff --git a/src/Certify.Tests/Certify.UI.Tests.Integration/ViewModelTests.cs b/src/Certify.Tests/Certify.UI.Tests.Integration/ViewModelTests.cs index 5c2ff6794..840e3f074 100644 --- a/src/Certify.Tests/Certify.UI.Tests.Integration/ViewModelTests.cs +++ b/src/Certify.Tests/Certify.UI.Tests.Integration/ViewModelTests.cs @@ -19,11 +19,12 @@ public async Task TestViewModelSetup() { var mockClient = new Mock(); - mockClient.Setup(c => c.GetPreferences()).Returns( + AuthContext authContext = null; + mockClient.Setup(c => c.GetPreferences(authContext)).Returns( Task.FromResult(new Models.Preferences { }) ); - mockClient.Setup(c => c.GetManagedCertificates(It.IsAny())) + mockClient.Setup(c => c.GetManagedCertificates(It.IsAny(), authContext)) .Returns( Task.FromResult(new List { new ManagedCertificate{ @@ -33,7 +34,7 @@ public async Task TestViewModelSetup() }) ); - mockClient.Setup(c => c.GetAccounts()) + mockClient.Setup(c => c.GetAccounts(authContext)) .Returns( Task.FromResult( new List { @@ -45,7 +46,7 @@ public async Task TestViewModelSetup() }) ); - mockClient.Setup(c => c.GetCredentials()) + mockClient.Setup(c => c.GetCredentials(authContext)) .Returns( Task.FromResult(new List { }) ); diff --git a/src/Certify.UI.Shared/ViewModel/AppViewModel/AppViewModel.Settings.cs b/src/Certify.UI.Shared/ViewModel/AppViewModel/AppViewModel.Settings.cs index a6ff83306..66f0d9790 100644 --- a/src/Certify.UI.Shared/ViewModel/AppViewModel/AppViewModel.Settings.cs +++ b/src/Certify.UI.Shared/ViewModel/AppViewModel/AppViewModel.Settings.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Certify.Client; using Certify.Models; using Certify.Models.Config.Migration; using Certify.UI.Settings; @@ -162,6 +163,13 @@ public virtual async Task LoadSettingsAsync() } + public AuthContext DefaultAuthContext + { + get + { + return null; + } + } /// /// Perform full export of app configuration /// @@ -171,7 +179,7 @@ public virtual async Task LoadSettingsAsync() /// public async Task GetSettingsExport(ManagedCertificateFilter filter, ExportSettings settings, bool isPreview) { - var pkg = await _certifyClient.PerformExport(new ExportRequest { Filter = filter, Settings = settings, IsPreviewMode = isPreview }); + var pkg = await _certifyClient.PerformExport(new ExportRequest { Filter = filter, Settings = settings, IsPreviewMode = isPreview }, DefaultAuthContext); return pkg; } @@ -184,7 +192,7 @@ public async Task GetSettingsExport(ManagedCertificateFilte /// public async Task> PerformSettingsImport(ImportExportPackage package, ImportSettings settings, bool isPreviewMode) { - var results = await _certifyClient.PerformImport(new ImportRequest { Package = package, Settings = settings, IsPreviewMode = isPreviewMode }); + var results = await _certifyClient.PerformImport(new ImportRequest { Package = package, Settings = settings, IsPreviewMode = isPreviewMode }, DefaultAuthContext); return results.ToList(); } } From ec54c573aad689bb325e7067019ea9e692497276 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 8 Feb 2024 12:43:29 +0800 Subject: [PATCH 126/328] Access: add user role status query on login --- .../Management/Access/AccessControl.cs | 69 +++++++++++--- .../Management/Access/IAccessControl.cs | 1 + src/Certify.Models/API/AuthRequest.cs | 5 +- src/Certify.Models/Config/AccessControl.cs | 10 ++- .../Certify.API.Public.cs | 89 +++++++++++++++++++ .../Controllers/AccessController.cs | 12 ++- 6 files changed, 171 insertions(+), 15 deletions(-) diff --git a/src/Certify.Core/Management/Access/AccessControl.cs b/src/Certify.Core/Management/Access/AccessControl.cs index 12b48db8f..32c874ead 100644 --- a/src/Certify.Core/Management/Access/AccessControl.cs +++ b/src/Certify.Core/Management/Access/AccessControl.cs @@ -27,6 +27,20 @@ public AccessControl(ILog log, IAccessControlStore store) _log = log; } + public async Task AuditWarning(string template, params object[] propertyvalues) + { + _log.Warning(template, propertyvalues); + } + + public async Task AuditError(string template, params object[] propertyvalues) + { + _log.Error(template, propertyvalues); + } + + public async Task AuditInformation(string template, params object[] propertyvalues) + { + _log.Error(template, propertyvalues); + } /// /// Check if the system has been initialized with a security principle /// @@ -63,14 +77,14 @@ public async Task AddSecurityPrinciple(string contextUserId, SecurityPrinc { if (!bypassIntegrityCheck && !await IsPrincipleInRole(contextUserId, contextUserId, StandardRoles.Administrator.Id)) { - _log?.Warning($"User {contextUserId} attempted to use AddSecurityPrinciple [{principle?.Id}] without being in required role."); + await AuditWarning("User {contextUserId} attempted to use AddSecurityPrinciple [{principleId}] without being in required role.", contextUserId, principle?.Id); return false; } var existing = await GetSecurityPrinciple(contextUserId, principle.Id); if (existing != null) { - _log?.Warning($"User {contextUserId} attempted to use AddSecurityPrinciple [{principle?.Id}] which already exists."); + await AuditWarning("User {contextUserId} attempted to use AddSecurityPrinciple [{principleId}] which already exists.", contextUserId, principle?.Id); return false; } @@ -87,7 +101,7 @@ public async Task AddSecurityPrinciple(string contextUserId, SecurityPrinc await _store.Add(nameof(SecurityPrinciple), principle); - _log?.Information($"User {contextUserId} added security principle [{principle?.Id}] {principle?.Username}"); + await AuditInformation("User {contextUserId} added security principle [{principleId}] {username}", contextUserId, principle?.Id, principle?.Username); return true; } @@ -101,7 +115,7 @@ public async Task UpdateSecurityPrinciple(string contextUserId, SecurityPr if (!await IsPrincipleInRole(contextUserId, contextUserId, StandardRoles.Administrator.Id)) { - _log?.Warning($"User {contextUserId} attempted to use UpdateSecurityPrinciple [{principle?.Id}] without being in required role."); + await AuditWarning($"User {contextUserId} attempted to use UpdateSecurityPrinciple [{principle?.Id}] without being in required role."); return false; } @@ -118,7 +132,7 @@ public async Task UpdateSecurityPrinciple(string contextUserId, SecurityPr } catch { - _log?.Warning($"User {contextUserId} attempted to use UpdateSecurityPrinciple [{principle?.Id}], but was not successful"); + await AuditWarning($"User {contextUserId} attempted to use UpdateSecurityPrinciple [{principle?.Id}], but was not successful"); return false; } @@ -136,7 +150,7 @@ public async Task DeleteSecurityPrinciple(string contextUserId, string id, { if (!await IsPrincipleInRole(contextUserId, contextUserId, StandardRoles.Administrator.Id)) { - _log?.Warning($"User {contextUserId} attempted to use DeleteSecurityPrinciple [{id}] without being in required role."); + await AuditWarning($"User {contextUserId} attempted to use DeleteSecurityPrinciple [{id}] without being in required role."); return false; } @@ -152,7 +166,7 @@ public async Task DeleteSecurityPrinciple(string contextUserId, string id, if (deleted != true) { - _log?.Warning($"User {contextUserId} attempted to delete security principle [{id}] {existing?.Username}, but was not successful"); + await AuditWarning($"User {contextUserId} attempted to delete security principle [{id}] {existing?.Username}, but was not successful"); return false; } // TODO: remove assigned roles @@ -193,6 +207,8 @@ public async Task IsAuthorised(string contextUserId, string principleId, s // to determine is a principle has access to perform a particular action // for each group the principle is part of + // TODO: cache results for performance + var allAssignedRoles = await _store.GetItems(nameof(AssignedRole)); var spAssigned = allAssignedRoles.Where(a => a.SecurityPrincipleId == principleId); @@ -276,7 +292,7 @@ public async Task AddResourcePolicy(string contextUserId, ResourcePolicy r { if (!bypassIntegrityCheck && !await IsPrincipleInRole(contextUserId, contextUserId, StandardRoles.Administrator.Id)) { - _log?.Warning($"User {contextUserId} attempted to use AddResourcePolicy [{resourceProfile.Id}] without being in required role."); + await AuditWarning($"User {contextUserId} attempted to use AddResourcePolicy [{resourceProfile.Id}] without being in required role."); return false; } @@ -290,7 +306,7 @@ public async Task UpdateSecurityPrinciplePassword(string contextUserId, Se { if (passwordUpdate.SecurityPrincipleId != contextUserId && !await IsPrincipleInRole(contextUserId, contextUserId, StandardRoles.Administrator.Id)) { - _log?.Warning("User {contextUserId} attempted to use updated password for [{id}] without being in required role.", contextUserId, passwordUpdate.SecurityPrincipleId); + await AuditWarning("User {contextUserId} attempted to use updated password for [{id}] without being in required role.", contextUserId, passwordUpdate.SecurityPrincipleId); return false; } @@ -315,7 +331,7 @@ public async Task UpdateSecurityPrinciplePassword(string contextUserId, Se else { - _log?.Warning("User {contextUserId} failed to update password for [{username} - {id}]", contextUserId, principle.Username, principle.Id); + await AuditWarning("User {contextUserId} failed to update password for [{username} - {id}]", contextUserId, principle.Username, principle.Id); } return updated; @@ -385,7 +401,7 @@ public async Task> GetAssignedRoles(string contextUserId, str { if (id != contextUserId && !await IsPrincipleInRole(contextUserId, contextUserId, StandardRoles.Administrator.Id)) { - _log?.Warning("User {contextUserId} attempted to read assigned role for [{id}] without being in required role.", contextUserId, id); + await AuditWarning("User {contextUserId} attempted to read assigned role for [{id}] without being in required role.", contextUserId, id); return new List(); } @@ -394,11 +410,40 @@ public async Task> GetAssignedRoles(string contextUserId, str return assignedRoles.Where(r => r.SecurityPrincipleId == id).ToList(); } + public async Task GetSecurityPrincipleRoleStatus(string contextUserId, string id) + { + if (id != contextUserId && !await IsPrincipleInRole(contextUserId, contextUserId, StandardRoles.Administrator.Id)) + { + await AuditWarning("User {contextUserId} attempted to read role status role for [{id}] without being in required role.", contextUserId, id); + + } + + var allAssignedRoles = await _store.GetItems(nameof(AssignedRole)); + var allRoles = await _store.GetItems(nameof(Role)); + var allPolicies = await _store.GetItems(nameof(ResourcePolicy)); + var allActions = await _store.GetItems(nameof(ResourceAction)); + + var spAssignedRoles = allAssignedRoles.Where(a => a.SecurityPrincipleId == id); + var spRoles = allRoles.Where(r => spAssignedRoles.Any(t => t.RoleId == r.Id)); + var spPolicies = allPolicies.Where(r => spRoles.Any(p => p.Policies.Contains(r.Id))); + var spActions = allActions.Where(r => spPolicies.Any(p => p.ResourceActions.Contains(r.Id))); + + var roleStatus = new RoleStatus + { + AssignedRoles = spAssignedRoles, + Roles = spRoles, + Policies = spPolicies, + Action = spActions + }; + + return roleStatus; + } + public async Task UpdateAssignedRoles(string contextUserId, SecurityPrincipleAssignedRoleUpdate update) { if (!await IsPrincipleInRole(contextUserId, contextUserId, StandardRoles.Administrator.Id)) { - _log?.Warning("User {contextUserId} attempted to update assigned role for [{id}] without being in required role.", contextUserId, update.SecurityPrincipleId); + await AuditWarning("User {contextUserId} attempted to update assigned role for [{id}] without being in required role.", contextUserId, update.SecurityPrincipleId); return false; } diff --git a/src/Certify.Core/Management/Access/IAccessControl.cs b/src/Certify.Core/Management/Access/IAccessControl.cs index cb8b22ca7..a4c8a7e71 100644 --- a/src/Certify.Core/Management/Access/IAccessControl.cs +++ b/src/Certify.Core/Management/Access/IAccessControl.cs @@ -21,6 +21,7 @@ public interface IAccessControl Task IsAuthorised(string contextUserId, string principleId, string roleId, string resourceType, string actionId, string identifier); Task IsPrincipleInRole(string contextUserId, string id, string roleId); Task> GetAssignedRoles(string contextUserId, string id); + Task GetSecurityPrincipleRoleStatus(string contextUserId, string id); Task UpdateSecurityPrinciple(string contextUserId, SecurityPrinciple principle); Task UpdateAssignedRoles(string contextUserId, SecurityPrincipleAssignedRoleUpdate update); Task UpdateSecurityPrinciplePassword(string contextUserId, SecurityPrinciplePasswordUpdate passwordUpdate); diff --git a/src/Certify.Models/API/AuthRequest.cs b/src/Certify.Models/API/AuthRequest.cs index 5f65580bb..12487ad57 100644 --- a/src/Certify.Models/API/AuthRequest.cs +++ b/src/Certify.Models/API/AuthRequest.cs @@ -1,4 +1,5 @@ -using Certify.Models.Config.AccessControl; +using System.Collections.Generic; +using Certify.Models.Config.AccessControl; namespace Certify.Models.API { @@ -41,6 +42,8 @@ public class AuthResponse public string RefreshToken { get; set; } = string.Empty; public Models.Config.AccessControl.SecurityPrinciple? SecurityPrinciple { get; set; } + + public RoleStatus? RoleStatus { get; set; } } public class SecurityPrinciplePasswordCheck diff --git a/src/Certify.Models/Config/AccessControl.cs b/src/Certify.Models/Config/AccessControl.cs index 3f820c91e..689797a7f 100644 --- a/src/Certify.Models/Config/AccessControl.cs +++ b/src/Certify.Models/Config/AccessControl.cs @@ -127,8 +127,16 @@ public ResourceAction(string id, string title, string resourceType) } public class SecurityPrincipleAssignedRoleUpdate { - public string SecurityPrincipleId { get; set; } = String.Empty; + public string SecurityPrincipleId { get; set; } = string.Empty; public List AddedAssignedRoles { get; set; } = new List(); public List RemovedAssignedRoles { get; set; } = new List(); } + + public class RoleStatus + { + public IEnumerable AssignedRoles { get; set; } = new List(); + public IEnumerable Roles { get; set; } = new List(); + public IEnumerable Policies { get; set; } = new List(); + public IEnumerable Action { get; set; } = new List(); + } } diff --git a/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs b/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs index 2a659c600..3ddc9ec30 100644 --- a/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs +++ b/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs @@ -160,6 +160,95 @@ public string BaseUrl } } + /// + /// Get list of Assigned Roles etc for a given security principle [Generated by Certify.SourceGenerators] + /// + /// Success + /// A server side error occurred. + public virtual System.Threading.Tasks.Task GetSecurityPrincipleRoleStatusAsync(string id) + { + return GetSecurityPrincipleRoleStatusAsync(id, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Get list of Assigned Roles etc for a given security principle [Generated by Certify.SourceGenerators] + /// + /// Success + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task GetSecurityPrincipleRoleStatusAsync(string id, System.Threading.CancellationToken cancellationToken) + { + if (id == null) + throw new System.ArgumentNullException("id"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); + // Operation Path: "internal/v1/access/securityprinciple/{id}/rolestatus" + urlBuilder_.Append("internal/v1/access/securityprinciple/"); + urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(id, System.Globalization.CultureInfo.InvariantCulture))); + urlBuilder_.Append("/rolestatus"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + /// /// Get list of available security Roles [Generated by Certify.SourceGenerators] /// diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccessController.cs b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccessController.cs index 2f7f9264c..199b50e95 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccessController.cs +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccessController.cs @@ -1,4 +1,4 @@ -using Certify.Core.Management.Access; +using Certify.Core.Management.Access; using Certify.Management; using Certify.Models.API; using Certify.Models.Config.AccessControl; @@ -114,6 +114,16 @@ public async Task> GetSecurityPrincipleAssignedRoles(string i return results; } + [HttpGet, Route("securityprinciple/{id}/rolestatus")] + public async Task GetSecurityPrincipleRoleStatus(string id) + { + var accessControl = await _certifyManager.GetCurrentAccessControl(); + + var result = await accessControl.GetSecurityPrincipleRoleStatus(GetContextUserId(), id); + + return result; + } + [HttpPost, Route("updatepassword")] public async Task UpdatePassword([FromBody] SecurityPrinciplePasswordUpdate passwordUpdate) { From 97cb5525e3ae72ba862cd92f1b20e18706b35220 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 8 Feb 2024 12:44:59 +0800 Subject: [PATCH 127/328] WIP: resource action standard naming --- src/Certify.Models/Config/AccessControlConfig.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/Certify.Models/Config/AccessControlConfig.cs b/src/Certify.Models/Config/AccessControlConfig.cs index ffa269302..02a717c7b 100644 --- a/src/Certify.Models/Config/AccessControlConfig.cs +++ b/src/Certify.Models/Config/AccessControlConfig.cs @@ -63,13 +63,20 @@ public class ResourceTypes public static string CertificateAuthority { get; } = "ca"; } + public static class StandardResourceActions + { + public const string CertificateDownload = "certificate_download"; + public const string ManagedItemAdd = "manageditem_add"; + public const string ManagedItemList = "manageditem_list"; + } + public static class Policies { public static List GetStandardResourceActions() { return new List { - new ResourceAction("certificate_download", "Certificate Download", ResourceTypes.Certificate), + new ResourceAction(StandardResourceActions.CertificateDownload, "Certificate Download", ResourceTypes.Certificate), new ResourceAction("storedcredential_add", "Add New Stored Credential", ResourceTypes.StoredCredential), new ResourceAction("storedcredential_update", "Update Stored Credential", ResourceTypes.StoredCredential), @@ -83,6 +90,7 @@ public static List GetStandardResourceActions() new ResourceAction("manageditem_requester", "Request New Managed Items", ResourceTypes.ManagedItem), new ResourceAction("manageditem_add", "Add Managed Items", ResourceTypes.ManagedItem), + new ResourceAction("manageditem_list", "List Managed Items", ResourceTypes.ManagedItem), new ResourceAction("manageditem_update", "Update Managed Items", ResourceTypes.ManagedItem), new ResourceAction("manageditem_delete", "Delete Managed Items", ResourceTypes.ManagedItem), new ResourceAction("manageditem_test", "Test Managed Item Renewal Checks", ResourceTypes.ManagedItem), @@ -98,7 +106,8 @@ public static List GetStandardPolicies() return new List { new ResourcePolicy{ Id="managed_item_admin", Title="Managed Item Administration", SecurityPermissionType= SecurityPermissionType.ALLOW, ResourceActions= new List{ - "manageditem_add", + StandardResourceActions.ManagedItemList, + StandardResourceActions.ManagedItemAdd, "manageditem_update", "manageditem_delete", "manageditem_test", @@ -118,7 +127,7 @@ public static List GetStandardPolicies() }, new ResourcePolicy{ Id="certificate_consumer", Title="Consume Certificates", SecurityPermissionType= SecurityPermissionType.ALLOW, ResourceActions= new List{ - "certificate_download", + StandardResourceActions.CertificateDownload, "certificate_key_download" } }, From 3941c28247568eeaf93e02bc3fab8f4bf65c696d Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 8 Feb 2024 13:35:53 +0800 Subject: [PATCH 128/328] Cleanup --- src/Certify.Core/Management/Access/AccessControl.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Certify.Core/Management/Access/AccessControl.cs b/src/Certify.Core/Management/Access/AccessControl.cs index 32c874ead..81f82018a 100644 --- a/src/Certify.Core/Management/Access/AccessControl.cs +++ b/src/Certify.Core/Management/Access/AccessControl.cs @@ -128,7 +128,7 @@ public async Task UpdateSecurityPrinciple(string contextUserId, SecurityPr updateSp.AvatarUrl = GetAvatarUrlForPrinciple(principle); - var updated = _store.Update(nameof(SecurityPrinciple), updateSp); + await _store.Update(nameof(SecurityPrinciple), updateSp); } catch { @@ -317,7 +317,7 @@ public async Task UpdateSecurityPrinciplePassword(string contextUserId, Se if (IsPasswordValid(passwordUpdate.Password, principle.Password)) { principle.Password = HashPassword(passwordUpdate.NewPassword); - updated = await UpdateSecurityPrinciple(contextUserId, principle); + await UpdateSecurityPrinciple(contextUserId, principle); } else { @@ -497,12 +497,12 @@ public string GetSHA256Hash(string val) { using (var sha256Hash = SHA256.Create()) { - byte[] data = sha256Hash.ComputeHash(Encoding.UTF8.GetBytes(val)); + var data = sha256Hash.ComputeHash(Encoding.UTF8.GetBytes(val)); var sBuilder = new StringBuilder(); // Loop through each byte of the hashed data // and format each one as a hexadecimal string. - for (int i = 0; i < data.Length; i++) + for (var i = 0; i < data.Length; i++) { sBuilder.Append(data[i].ToString("x2")); } From ccc9fcc43c445ddfd9e9c5cf0f5ccd63a3b4e777 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 8 Feb 2024 13:36:28 +0800 Subject: [PATCH 129/328] Tests: fix access control test referencing mutated in-memory value --- .../Tests/AccessControlTests.cs | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/AccessControlTests.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/AccessControlTests.cs index efc078186..1fd6deb70 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/AccessControlTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/AccessControlTests.cs @@ -8,6 +8,7 @@ using Certify.Models.Config.AccessControl; using Certify.Providers; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Newtonsoft.Json; using Serilog; namespace Certify.Core.Tests.Unit @@ -276,6 +277,7 @@ public async Task TestUpdateSecurityPrinciple() { // Add test security principles var adminSecurityPrinciples = new List { TestSecurityPrinciples.Admin, TestSecurityPrinciples.TestAdmin }; + adminSecurityPrinciples.ForEach(async p => await access.AddSecurityPrinciple(contextUserId, p, bypassIntegrityCheck: true)); // Setup security principle actions @@ -294,20 +296,29 @@ public async Task TestUpdateSecurityPrinciple() var assignedRoles = new List { TestAssignedRoles.Admin, TestAssignedRoles.TestAdmin }; assignedRoles.ForEach(async r => await access.AddAssignedRole(r)); + // clone the test security principles to avoid reference issues as we are using an in-memory store + adminSecurityPrinciples = JsonConvert.DeserializeObject>(JsonConvert.SerializeObject(adminSecurityPrinciples)); + // Validate email of SecurityPrinciple object returned by AccessControl.GetSecurityPrinciple() before update var storedSecurityPrinciple = await access.GetSecurityPrinciple(contextUserId, adminSecurityPrinciples[0].Id); Assert.AreEqual(storedSecurityPrinciple.Email, adminSecurityPrinciples[0].Email, $"Expected SecurityPrinciple returned by GetSecurityPrinciple() to match Email '{adminSecurityPrinciples[0].Email}' of SecurityPrinciple passed into AddSecurityPrinciple()"); // Update security principle in AccessControl with a new principle object of the same Id, but different email - var newSecurityPrinciple = TestSecurityPrinciples.Admin; - newSecurityPrinciple.Email = "new_test_email@test.com"; - var securityPrincipleUpdated = await access.UpdateSecurityPrinciple(contextUserId, newSecurityPrinciple); - Assert.IsTrue(securityPrincipleUpdated, $"Expected security principle update for {newSecurityPrinciple.Id} to succeed"); + var updateSecurityPrinciple = new SecurityPrinciple + { + Id = TestSecurityPrinciples.Admin.Id, + Username = TestSecurityPrinciples.Admin.Username, + Description = TestSecurityPrinciples.Admin.Description, + Email = "new_test_email@test.com" + }; + + var securityPrincipleUpdated = await access.UpdateSecurityPrinciple(contextUserId, updateSecurityPrinciple); + Assert.IsTrue(securityPrincipleUpdated, $"Expected security principle update for {updateSecurityPrinciple.Id} to succeed"); // Validate email of SecurityPrinciple object returned by AccessControl.GetSecurityPrinciple() after update - storedSecurityPrinciple = await access.GetSecurityPrinciple(contextUserId, newSecurityPrinciple.Id); + storedSecurityPrinciple = await access.GetSecurityPrinciple(contextUserId, updateSecurityPrinciple.Id); Assert.AreNotEqual(storedSecurityPrinciple.Email, adminSecurityPrinciples[0].Email, $"Expected SecurityPrinciple returned by GetSecurityPrinciple() to not match previous Email '{adminSecurityPrinciples[0].Email}' of SecurityPrinciple passed into AddSecurityPrinciple()"); - Assert.AreEqual(storedSecurityPrinciple.Email, newSecurityPrinciple.Email, $"Expected SecurityPrinciple returned by GetSecurityPrinciple() to match updated Email '{newSecurityPrinciple.Email}' of SecurityPrinciple passed into AddSecurityPrinciple()"); + Assert.AreEqual(storedSecurityPrinciple.Email, updateSecurityPrinciple.Email, $"Expected SecurityPrinciple returned by GetSecurityPrinciple() to match updated Email '{updateSecurityPrinciple.Email}' of SecurityPrinciple passed into AddSecurityPrinciple()"); } [TestMethod] From 9da1a5bc55e0f3ab65f310ff1b2ae5d48fb7c23e Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 8 Feb 2024 13:36:54 +0800 Subject: [PATCH 130/328] Test: update check assert not null result --- .../Certify.Core.Tests.Unit/Tests/UpdateCheckTest.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/UpdateCheckTest.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/UpdateCheckTest.cs index 5cc91ed9b..efc82bd71 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/UpdateCheckTest.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/UpdateCheckTest.cs @@ -22,6 +22,8 @@ public void TestUpdateCheck() result = updateChecker.CheckForUpdates("6.1.1").Result; + Assert.IsNotNull(result, "Update check result should not be null"); + // current version is newer than update version Assert.IsFalse(result.IsNewerVersion); Assert.IsFalse(result.MustUpdate, "No mandatory update required"); From 0fab338168a2d688398c17e5a32630ca08130ff7 Mon Sep 17 00:00:00 2001 From: Fritz Otlinghaus Date: Wed, 31 Jan 2024 10:29:26 +0100 Subject: [PATCH 131/328] Add hosting.de as posh acme option --- src/Certify.Shared.Extensions/Providers/DnsProviderPoshACME.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Certify.Shared.Extensions/Providers/DnsProviderPoshACME.cs b/src/Certify.Shared.Extensions/Providers/DnsProviderPoshACME.cs index c358f87f9..7d4abcd11 100644 --- a/src/Certify.Shared.Extensions/Providers/DnsProviderPoshACME.cs +++ b/src/Certify.Shared.Extensions/Providers/DnsProviderPoshACME.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; From 3f00126c47cd3088b126488b08f6410157cc2c17 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 8 Feb 2024 17:39:35 +0800 Subject: [PATCH 132/328] Renewal: Port fix for recurring renewal attempts which should be on hold --- src/Certify.Models/Config/ManagedCertificate.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Certify.Models/Config/ManagedCertificate.cs b/src/Certify.Models/Config/ManagedCertificate.cs index 9dbd35627..f602ae767 100644 --- a/src/Certify.Models/Config/ManagedCertificate.cs +++ b/src/Certify.Models/Config/ManagedCertificate.cs @@ -785,10 +785,9 @@ public static bool IsDomainOrWildcardMatch(List dnsNames, string? hostna { targetRenewalPercentage = selectedRenewalInterval; - var targetRenewalMinutesAfterCertStart = certLifetime.Value.TotalMinutes * (targetRenewalPercentage / 100); - var targetRenewalDate = s.DateStart != null ? s.DateStart.Value.AddMinutes(targetRenewalMinutesAfterCertStart) : s.DateRenewed.Value; - nextRenewalAttemptDate = targetRenewalDate; - + if (targetRenewalPercentage > 100) { targetRenewalPercentage = 100; } + } + var targetRenewalMinutesAfterCertStart = certLifetime.Value.TotalMinutes * (targetRenewalPercentage / 100); var targetRenewalDate = s.DateStart != null ? s.DateStart.Value.AddMinutes(targetRenewalMinutesAfterCertStart) : s.DateRenewed.Value; nextRenewalAttemptDate = targetRenewalDate; From c720afd035637a3df21a4df055c3f1c18f9fa3bb Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Fri, 9 Feb 2024 18:15:51 +0800 Subject: [PATCH 133/328] UI/service: SignalR breaks if BCL Async version wrong --- src/Certify.Client/CertifyServiceClient.cs | 2 +- src/Certify.Service/Certify.Service.csproj | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Certify.Client/CertifyServiceClient.cs b/src/Certify.Client/CertifyServiceClient.cs index d09128a3e..714b3a8d9 100644 --- a/src/Certify.Client/CertifyServiceClient.cs +++ b/src/Certify.Client/CertifyServiceClient.cs @@ -58,7 +58,7 @@ public async Task ConnectStatusStreamAsync() _legacyConnection.Closed += OnConnectionClosed; #if DEBUG - var logPath = Path.Combine(EnvironmentUtil.CreateAppDataPath("logs"), "hubconnection.log"); + var logPath = Path.Combine(EnvironmentUtil.GetAppDataFolder("logs"), "hubconnection.log"); var writer = new StreamWriter(logPath); writer.AutoFlush = true; _legacyConnection.TraceLevel = TraceLevels.All; diff --git a/src/Certify.Service/Certify.Service.csproj b/src/Certify.Service/Certify.Service.csproj index 7502b7828..da0635f5d 100644 --- a/src/Certify.Service/Certify.Service.csproj +++ b/src/Certify.Service/Certify.Service.csproj @@ -55,6 +55,7 @@ + From c27896db87a266f0b5fe09d0dd862f01e5197e5f Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 15 Feb 2024 14:12:26 +0800 Subject: [PATCH 134/328] Move API method source generators into main repo --- src/Certify.Client/Certify.Client.csproj | 2 +- .../Certify.Server.Api.Public.csproj | 4 +- src/Certify.SourceGenerators/ApiMethods.cs | 225 +++++++++++++++++ .../Certify.SourceGenerators.csproj | 20 ++ .../Properties/launchSettings.json | 1 + .../PublicAPISourceGenerator.cs | 238 ++++++++++++++++++ 6 files changed, 487 insertions(+), 3 deletions(-) create mode 100644 src/Certify.SourceGenerators/ApiMethods.cs create mode 100644 src/Certify.SourceGenerators/Certify.SourceGenerators.csproj create mode 100644 src/Certify.SourceGenerators/Properties/launchSettings.json create mode 100644 src/Certify.SourceGenerators/PublicAPISourceGenerator.cs diff --git a/src/Certify.Client/Certify.Client.csproj b/src/Certify.Client/Certify.Client.csproj index e706c7b60..ca1a1f5c1 100644 --- a/src/Certify.Client/Certify.Client.csproj +++ b/src/Certify.Client/Certify.Client.csproj @@ -25,9 +25,9 @@ - + diff --git a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj index 0fd764c92..721796c11 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj +++ b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj @@ -1,4 +1,4 @@ - + net8.0 enable @@ -23,7 +23,7 @@ - + diff --git a/src/Certify.SourceGenerators/ApiMethods.cs b/src/Certify.SourceGenerators/ApiMethods.cs new file mode 100644 index 000000000..d83836397 --- /dev/null +++ b/src/Certify.SourceGenerators/ApiMethods.cs @@ -0,0 +1,225 @@ +using SourceGenerator; +using System.Collections.Generic; + +namespace Certify.SourceGenerators +{ + internal class ApiMethods + { + public static List GetApiDefinitions() + { + // declaring an API definition here is then used by the source generators to: + // - create the public API endpoint + // - map the call from the public API to the background service API in the service API Client (interface and implementation) + // - to then generate the public API clients, run nswag when the public API is running. + + return new List { + + new GeneratedAPI { + + OperationName = "GetSecurityPrincipleAssignedRoles", + OperationMethod = "HttpGet", + Comment = "Get list of Assigned Roles for a given security principle", + PublicAPIController = "Access", + PublicAPIRoute = "securityprinciple/{id}/assignedroles", + ServiceAPIRoute = "access/securityprinciple/{id}/assignedroles", + ReturnType = "ICollection", + Params =new Dictionary{{"id","string"}} + }, + new GeneratedAPI { + + OperationName = "GetSecurityPrincipleRoleStatus", + OperationMethod = "HttpGet", + Comment = "Get list of Assigned Roles etc for a given security principle", + PublicAPIController = "Access", + PublicAPIRoute = "securityprinciple/{id}/rolestatus", + ServiceAPIRoute = "access/securityprinciple/{id}/rolestatus", + ReturnType = "RoleStatus", + Params =new Dictionary{{"id","string"}} + }, + new GeneratedAPI { + + OperationName = "GetAccessRoles", + OperationMethod = "HttpGet", + Comment = "Get list of available security Roles", + PublicAPIController = "Access", + PublicAPIRoute = "roles", + ServiceAPIRoute = "access/roles", + ReturnType = "ICollection" + }, + new GeneratedAPI { + + OperationName = "GetSecurityPrinciples", + OperationMethod = "HttpGet", + Comment = "Get list of available security principles", + PublicAPIController = "Access", + PublicAPIRoute = "securityprinciples", + ServiceAPIRoute = "access/securityprinciples", + ReturnType = "ICollection" + }, + new GeneratedAPI { + OperationName = "ValidateSecurityPrinciplePassword", + OperationMethod = "HttpPost", + Comment = "Check password valid for security principle", + PublicAPIController = "Access", + PublicAPIRoute = "validate", + ServiceAPIRoute = "access/validate", + ReturnType = "Certify.Models.API.SecurityPrincipleCheckResponse", + Params = new Dictionary{{"passwordCheck", "Certify.Models.API.SecurityPrinciplePasswordCheck" } } + }, + new GeneratedAPI { + + OperationName = "UpdateSecurityPrinciplePassword", + OperationMethod = "HttpPost", + Comment = "Update password for security principle", + PublicAPIController = "Access", + PublicAPIRoute = "updatepassword", + ServiceAPIRoute = "access/updatepassword", + ReturnType = "Models.Config.ActionResult", + Params = new Dictionary{{"passwordUpdate", "Certify.Models.API.SecurityPrinciplePasswordUpdate" } } + }, + new GeneratedAPI { + + OperationName = "AddSecurityPrinciple", + OperationMethod = "HttpPost", + Comment = "Add new security principle", + PublicAPIController = "Access", + PublicAPIRoute = "securityprinciple", + ServiceAPIRoute = "access/securityprinciple", + ReturnType = "Models.Config.ActionResult", + Params = new Dictionary{{"principle", "Certify.Models.Config.AccessControl.SecurityPrinciple" } } + }, + new GeneratedAPI { + + OperationName = "UpdateSecurityPrinciple", + OperationMethod = "HttpPost", + Comment = "Update existing security principle", + PublicAPIController = "Access", + PublicAPIRoute = "securityprinciple/update", + ServiceAPIRoute = "access/securityprinciple/update", + ReturnType = "Models.Config.ActionResult", + Params = new Dictionary{ + { "principle", "Certify.Models.Config.AccessControl.SecurityPrinciple" } + } + }, + new GeneratedAPI { + + OperationName = "UpdateSecurityPrincipleAssignedRoles", + OperationMethod = "HttpPost", + Comment = "Update assigned roles for a security principle", + PublicAPIController = "Access", + PublicAPIRoute = "securityprinciple/roles/update", + ServiceAPIRoute = "access/securityprinciple/roles/update", + ReturnType = "Models.Config.ActionResult", + Params = new Dictionary{ + { "update", "Certify.Models.Config.AccessControl.SecurityPrincipleAssignedRoleUpdate" } + } + }, + new GeneratedAPI { + + OperationName = "DeleteSecurityPrinciple", + OperationMethod = "HttpDelete", + Comment = "Delete security principle", + PublicAPIController = "Access", + PublicAPIRoute = "securityprinciple", + ServiceAPIRoute = "access/securityprinciple/{id}", + ReturnType = "Models.Config.ActionResult", + Params = new Dictionary{{"id","string"}} + }, + new GeneratedAPI { + + OperationName = "GetAcmeAccounts", + OperationMethod = "HttpGet", + Comment = "Get All Acme Accounts", + PublicAPIController = "CertificateAuthority", + PublicAPIRoute = "accounts", + ServiceAPIRoute = "accounts", + ReturnType = "ICollection" + }, + new GeneratedAPI { + + OperationName = "AddAcmeAccount", + OperationMethod = "HttpPost", + Comment = "Add New Acme Account", + PublicAPIController = "CertificateAuthority", + PublicAPIRoute = "account", + ServiceAPIRoute = "accounts", + ReturnType = "Models.Config.ActionResult", + Params =new Dictionary{{"registration", "Certify.Models.ContactRegistration" } } + }, + new GeneratedAPI { + + OperationName = "AddCertificateAuthority", + OperationMethod = "HttpPost", + Comment = "Add New Certificate Authority", + PublicAPIController = "CertificateAuthority", + PublicAPIRoute = "authority", + ServiceAPIRoute = "accounts/authorities", + ReturnType = "Models.Config.ActionResult", + Params =new Dictionary{{ "certificateAuthority", "Certify.Models.CertificateAuthority" } } + }, + new GeneratedAPI { + + OperationName = "RemoveManagedCertificate", + OperationMethod = "HttpDelete", + Comment = "Remove Managed Certificate", + PublicAPIController = "Certificate", + PublicAPIRoute = "certificate/{id}", + ServiceAPIRoute = "managedcertificates/delete/{id}", + ReturnType = "bool", + Params =new Dictionary{{ "id", "string" } } + }, + new GeneratedAPI { + + OperationName = "RemoveCertificateAuthority", + OperationMethod = "HttpDelete", + Comment = "Remove Certificate Authority", + PublicAPIController = "CertificateAuthority", + PublicAPIRoute = "authority/{id}", + ServiceAPIRoute = "accounts/authorities/{id}", + ReturnType = "Models.Config.ActionResult", + Params =new Dictionary{{ "id", "string" } } + }, + new GeneratedAPI { + OperationName = "RemoveAcmeAccount", + OperationMethod = "HttpDelete", + Comment = "Remove ACME Account", + PublicAPIController = "CertificateAuthority", + PublicAPIRoute = "accounts/{storageKey}/{deactivate}", + ServiceAPIRoute = "accounts/remove/{storageKey}/{deactivate}", + ReturnType = "Models.Config.ActionResult", + Params =new Dictionary{{ "storageKey", "string" }, { "deactivate", "bool" } } + }, + new GeneratedAPI { + OperationName = "RemoveStoredCredential", + OperationMethod = "HttpDelete", + Comment = "Remove Stored Credential", + PublicAPIController = "StoredCredential", + PublicAPIRoute = "storedcredential/{storageKey}", + ServiceAPIRoute = "credentials", + ReturnType = "Models.Config.ActionResult", + Params =new Dictionary{{ "storageKey", "string" } } + }, + new GeneratedAPI { + OperationName = "PerformExport", + OperationMethod = "HttpPost", + Comment = "Perform an export of all settings", + PublicAPIController = "System", + PublicAPIRoute = "system/migration/export", + ServiceAPIRoute = "system/migration/export", + ReturnType = "Models.Config.Migration.ImportExportPackage", + Params =new Dictionary{{ "exportRequest", "Certify.Models.Config.Migration.ExportRequest" } } + }, + new GeneratedAPI { + OperationName = "PerformImport", + OperationMethod = "HttpPost", + Comment = "Perform an import of all settings", + PublicAPIController = "System", + PublicAPIRoute = "system/migration/import", + ServiceAPIRoute = "system/migration/import", + ReturnType = "ICollection", + Params =new Dictionary{{ "importRequest", "Certify.Models.Config.Migration.ImportRequest" } } + } + }; + } + } +} diff --git a/src/Certify.SourceGenerators/Certify.SourceGenerators.csproj b/src/Certify.SourceGenerators/Certify.SourceGenerators.csproj new file mode 100644 index 000000000..133497c48 --- /dev/null +++ b/src/Certify.SourceGenerators/Certify.SourceGenerators.csproj @@ -0,0 +1,20 @@ + + + + netstandard2.0 + true + + + + + + + + + + + + + + + diff --git a/src/Certify.SourceGenerators/Properties/launchSettings.json b/src/Certify.SourceGenerators/Properties/launchSettings.json new file mode 100644 index 000000000..9e26dfeeb --- /dev/null +++ b/src/Certify.SourceGenerators/Properties/launchSettings.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/src/Certify.SourceGenerators/PublicAPISourceGenerator.cs b/src/Certify.SourceGenerators/PublicAPISourceGenerator.cs new file mode 100644 index 000000000..84b361621 --- /dev/null +++ b/src/Certify.SourceGenerators/PublicAPISourceGenerator.cs @@ -0,0 +1,238 @@ +using Certify.SourceGenerators; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; + +namespace SourceGenerator +{ + public class GeneratedAPI + { + public string OperationName { get; set; } = string.Empty; + public string OperationMethod { get; set; } = string.Empty; + public string Comment { get; set; } = string.Empty; + public string PublicAPIController { get; set; } = string.Empty; + + public string PublicAPIRoute { get; set; } = string.Empty; + public string ServiceAPIRoute { get; set; } = string.Empty; + public string ReturnType { get; set; } = string.Empty; + public Dictionary Params { get; set; } = new Dictionary(); + } + [Generator] + public class PublicAPISourceGenerator : ISourceGenerator + { + public void Execute(GeneratorExecutionContext context) + { + + // get list of items we want to generate for our API glue + var list = ApiMethods.GetApiDefinitions(); + + Debug.WriteLine(context.Compilation.AssemblyName); + + foreach (var config in list) + { + var paramSet = config.Params.ToList(); + paramSet.Add(new KeyValuePair("authContext", "AuthContext")); + var apiParamDecl = paramSet.Any() ? string.Join(", ", paramSet.Select(p => $"{p.Value} {p.Key}")) : ""; + var apiParamDeclWithoutAuthContext = config.Params.Any() ? string.Join(", ", config.Params.Select(p => $"{p.Value} {p.Key}")) : ""; + + var apiParamCall = paramSet.Any() ? string.Join(", ", paramSet.Select(p => $"{p.Key}")): ""; + var apiParamCallWithoutAuthContext = config.Params.Any() ? string.Join(", ", config.Params.Select(p => $"{p.Key}")) : ""; + + if (context.Compilation.AssemblyName.EndsWith("Api.Public")) + { + context.AddSource($"{config.PublicAPIController}Controller.{config.OperationName}.g.cs", SourceText.From($@" + +using Certify.Client; +using Certify.Server.Api.Public.Controllers; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Authorization; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using Certify.Models; +using Certify.Models.Config.AccessControl; + + + namespace Certify.Server.Api.Public.Controllers + {{ + public partial class {config.PublicAPIController}Controller + {{ + /// + /// {config.Comment} [Generated by Certify.SourceGenerators] + /// + /// + [{config.OperationMethod}] + [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof({config.ReturnType}))] + [Route(""""""{config.PublicAPIRoute}"""""")] + public async Task {config.OperationName}({apiParamDeclWithoutAuthContext}) + {{ + var result = await _client.{config.OperationName}({apiParamCall.Replace("authContext","CurrentAuthContext")}); + return new OkObjectResult(result); + }} + }} + }}", Encoding.UTF8)); + + } + + if (context.Compilation.AssemblyName.EndsWith("Certify.Client")) + { + + if (config.OperationMethod == "HttpGet") + { + context.AddSource($"{config.PublicAPIController}.{config.OperationName}.ICertifyInternalApiClient.g.cs", SourceText.From($@" +using Certify.Models; +using Certify.Models.Config.AccessControl; +using System.Collections.Generic; +using System.Threading.Tasks; + + namespace Certify.Client + {{ + public partial interface ICertifyInternalApiClient + {{ + /// + /// {config.Comment} [Generated by Certify.SourceGenerators] + /// + /// + Task<{config.ReturnType}> {config.OperationName}({apiParamDecl}); + + }} + + public partial class CertifyApiClient + {{ + /// + /// {config.Comment} [Generated by Certify.SourceGenerators] + /// + /// + public async Task<{config.ReturnType}> {config.OperationName}({apiParamDecl}) + {{ + var result = await FetchAsync($""{config.ServiceAPIRoute}"", authContext); + return JsonToObject<{config.ReturnType}>(result); + }} + + }} + }}", Encoding.UTF8)); + } + + + if (config.OperationMethod == "HttpPost") + { + context.AddSource($"{config.PublicAPIController}.{config.OperationName}.ICertifyInternalApiClient.g.cs", SourceText.From($@" +using Certify.Models; +using Certify.Models.Config.AccessControl; +using System.Collections.Generic; +using System.Threading.Tasks; + + namespace Certify.Client + {{ + public partial interface ICertifyInternalApiClient + {{ + /// + /// {config.Comment} [Generated by Certify.SourceGenerators] + /// + /// + Task<{config.ReturnType}> {config.OperationName}({apiParamDecl}); + + }} + + + public partial class CertifyApiClient + {{ + /// + /// {config.Comment} [Generated by Certify.SourceGenerators] + /// + /// + public async Task<{config.ReturnType}> {config.OperationName}({apiParamDecl}) + {{ + var result = await PostAsync($""{config.ServiceAPIRoute}"", {apiParamCall}); + return JsonToObject<{config.ReturnType}>(await result.Content.ReadAsStringAsync()); + }} + + }} + }}", Encoding.UTF8)); + } + + + if (config.OperationMethod == "HttpDelete") + { + context.AddSource($"{config.PublicAPIController}.{config.OperationName}.ICertifyInternalApiClient.g.cs", SourceText.From($@" +using Certify.Models; +using Certify.Models.Config.AccessControl; +using System.Collections.Generic; +using System.Threading.Tasks; + + namespace Certify.Client + {{ + public partial interface ICertifyInternalApiClient + {{ + /// + /// {config.Comment} [Generated by Certify.SourceGenerators] + /// + /// + Task<{config.ReturnType}> {config.OperationName}({apiParamDecl}); + + }} + + + public partial class CertifyApiClient + {{ + /// + /// {config.Comment} [Generated by Certify.SourceGenerators] + /// + /// + public async Task<{config.ReturnType}> {config.OperationName}({apiParamDecl}) + {{ + + var route = $""{config.ServiceAPIRoute}""; + + var result = await DeleteAsync(route, authContext); + return JsonToObject<{config.ReturnType}>(await result.Content.ReadAsStringAsync()); + + }} + + }} + }}", Encoding.UTF8)); + } + } + + if (context.Compilation.AssemblyName.EndsWith("Certify.UI.Blazor")) + { + context.AddSource($"AppModel.{config.OperationName}.g.cs", SourceText.From($@" +using System.Collections.Generic; +using System.Threading.Tasks; +using Certify.Models; +using Certify.Models.Config.AccessControl; + + namespace Certify.UI.Client.Core + {{ + public partial class AppModel + {{ + public async Task<{config.ReturnType}> {config.OperationName}({apiParamDeclWithoutAuthContext}) + {{ + return await _api.{config.OperationName}Async({apiParamCallWithoutAuthContext}); + }} + }} + }}", Encoding.UTF8)); + } + + } + } + + public void Initialize(GeneratorInitializationContext context) + { +#if DEBUG + // uncomment this to launch a debug session which code generation runs + // then add a watch on + if (!Debugger.IsAttached) + { + // Debugger.Launch(); + } +#endif + } + } +} \ No newline at end of file From e3c2f439c30d5c4039b5f166d37765f2cf8dc622 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 15 Feb 2024 14:14:04 +0800 Subject: [PATCH 135/328] Cleanup warnings --- .../Controllers/internal/ApiControllerBase.cs | 1 - .../Controllers/v1/AuthController.cs | 11 ++++++++--- .../Certify.Server.Api.Public/StatusHub.cs | 2 +- src/Certify.UI.Desktop/App.xaml | 12 ++++-------- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/ApiControllerBase.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/ApiControllerBase.cs index 4b46ef317..a83816503 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/ApiControllerBase.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/ApiControllerBase.cs @@ -12,7 +12,6 @@ public partial class ApiControllerBase : ControllerBase /// /// Get the corresponding auth context to pass to the backend service /// - /// /// internal AuthContext CurrentAuthContext { diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/AuthController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/AuthController.cs index f71fb8a79..b66ccbc98 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/AuthController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/AuthController.cs @@ -67,13 +67,13 @@ public async Task Login(AuthRequest login) var authResponse = new AuthResponse { Detail = "OK", - AccessToken = jwt.GenerateSecurityToken(login.Username, double.Parse(_config["JwtSettings:authTokenExpirationInMinutes"])), + AccessToken = jwt.GenerateSecurityToken(login.Username, double.Parse(_config["JwtSettings:authTokenExpirationInMinutes"] ?? "20")), RefreshToken = jwt.GenerateRefreshToken(), SecurityPrinciple = validation.SecurityPrinciple, RoleStatus = await _client.GetSecurityPrincipleRoleStatus(validation.SecurityPrinciple.Id, CurrentAuthContext) }; - + // TODO: Refresh token should be stored or hashed for later use return Ok(authResponse); @@ -105,7 +105,12 @@ public IActionResult Refresh(string refreshToken) var claimsIdentity = jwt.ClaimsIdentityFromToken(authToken, false); var username = claimsIdentity.Name; - var newJwtToken = jwt.GenerateSecurityToken(username, double.Parse(_config["JwtSettings:authTokenExpirationInMinutes"])); + if (username == null) + { + return Unauthorized(); + } + + var newJwtToken = jwt.GenerateSecurityToken(username, double.Parse(_config["JwtSettings:authTokenExpirationInMinutes"] ?? "20")); var newRefreshToken = jwt.GenerateRefreshToken(); // invalidate old refresh token and store new one diff --git a/src/Certify.Server/Certify.Server.Api.Public/StatusHub.cs b/src/Certify.Server/Certify.Server.Api.Public/StatusHub.cs index 2c2ad987a..28a405764 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/StatusHub.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/StatusHub.cs @@ -85,7 +85,7 @@ public override Task OnConnectedAsync() /// /// /// - public override Task OnDisconnectedAsync(Exception exception) + public override Task OnDisconnectedAsync(Exception? exception) { Debug.WriteLine("StatusHub: Client disconnected from status stream.."); return base.OnDisconnectedAsync(exception); diff --git a/src/Certify.UI.Desktop/App.xaml b/src/Certify.UI.Desktop/App.xaml index a0a6511ac..644a1a01f 100644 --- a/src/Certify.UI.Desktop/App.xaml +++ b/src/Certify.UI.Desktop/App.xaml @@ -10,13 +10,10 @@ - - + + - + + From d0d7237fc557b5b89ebec5be774946daa9fe8a89 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Tue, 6 Aug 2024 12:35:47 +0800 Subject: [PATCH 222/328] Update DotNetCore_Unit_Tests_Win.yaml Update DotNetCore_Unit_Tests_Linux.yaml Update DotNetCore_Unit_Tests_Win.yaml Update DotNetCore_Unit_Tests_Linux.yaml Update DotNetCore_Unit_Tests_Linux.yaml Update DotNetCore_Unit_Tests_Linux.yaml --- .github/workflows/DotNetCore_Unit_Tests_Linux.yaml | 6 ++++-- .github/workflows/DotNetCore_Unit_Tests_Win.yaml | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/DotNetCore_Unit_Tests_Linux.yaml b/.github/workflows/DotNetCore_Unit_Tests_Linux.yaml index deee70381..94acd9076 100644 --- a/.github/workflows/DotNetCore_Unit_Tests_Linux.yaml +++ b/.github/workflows/DotNetCore_Unit_Tests_Linux.yaml @@ -77,10 +77,12 @@ jobs: - name: Install Dependencies & Build Certify.Core.Tests.Unit run: | - dotnet tool install --global dotnet-reportgenerator-globaltool --version 5.2.0 + dotnet tool install --global dotnet-reportgenerator-globaltool --version 5.3.8 dotnet add package GitHubActionsTestLogger dotnet restore -f net9.0 - dotnet build ./certify/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj -c Debug -f net9.0 --property WarningLevel=0 /clp:ErrorsOnly + pwd + ls + dotnet build -c Debug -f net9.0 --property WarningLevel=0 /clp:ErrorsOnly working-directory: ./certify/src/Certify.Tests/Certify.Core.Tests.Unit - name: Run Certify.Core.Tests.Unit Tests diff --git a/.github/workflows/DotNetCore_Unit_Tests_Win.yaml b/.github/workflows/DotNetCore_Unit_Tests_Win.yaml index 572b306c0..1094fbe39 100644 --- a/.github/workflows/DotNetCore_Unit_Tests_Win.yaml +++ b/.github/workflows/DotNetCore_Unit_Tests_Win.yaml @@ -65,7 +65,7 @@ jobs: echo "C:\Program Files\step_0.24.4\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append - name: Pull step-ca Docker Image - run: docker pull jrnelson90/step-ca-win + run: docker pull webprofusion/step-ca-win - name: Cache NuGet Dependencies uses: actions/cache@v3 @@ -81,7 +81,7 @@ jobs: dotnet tool install --global dotnet-reportgenerator-globaltool --version 5.2.0 dotnet add package GitHubActionsTestLogger dotnet restore -f net9.0 - dotnet build ./certify/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj -c Debug -f net9.0 --property WarningLevel=0 /clp:ErrorsOnly + dotnet build Certify.Core.Tests.Unit.csproj -c Debug -f net9.0 --property WarningLevel=0 /clp:ErrorsOnly working-directory: ./certify/src/Certify.Tests/Certify.Core.Tests.Unit - name: Run Certify.Core.Tests.Unit Tests From 6bd3140ddec00b6970212568534864c4ad52fe7d Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Fri, 9 Aug 2024 16:12:54 +0800 Subject: [PATCH 223/328] Port changes from latest release --- .../CertifyManager/CertifyManager.CertificateRequest.cs | 1 - .../Certify.Core.Tests.Unit/Tests/BindingMatchTests.cs | 7 ++++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.CertificateRequest.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.CertificateRequest.cs index 5d566a762..cb3eb504a 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.CertificateRequest.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.CertificateRequest.cs @@ -2,7 +2,6 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/BindingMatchTests.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/BindingMatchTests.cs index ce4be89b3..e6bb6b4cb 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/BindingMatchTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/BindingMatchTests.cs @@ -726,8 +726,8 @@ public async Task MixedIPBindingChecksRequestConfigUpdateOnly() public async Task HttpsIPBindingChecks() { var bindings = new List { - new BindingInfo{ Host="test.com", IP="127.0.0.1", Port=80, Protocol="https" }, - new BindingInfo{ Host="www.test.com", IP="127.0.0.1", Port=80, Protocol="https" }, + new BindingInfo{ Host="test.com", IP="127.0.0.1", Port=443, Protocol="https" }, + new BindingInfo{ Host="www.test.com", IP="127.0.0.1", Port=443, Protocol="https" }, }; var deployment = new BindingDeploymentManager(); var testManagedCert = new ManagedCertificate @@ -764,9 +764,10 @@ public async Task HttpsIPBindingChecks() Assert.IsTrue(results[0].Description.Contains("Certificate will be stored in the computer certificate store"), $"Unexpected description: '{results[0].Description}'"); Assert.AreEqual("Certificate Storage", results[0].Title); + // because the existing binding uses an IP address with non-SNI the resulting update should also use the IP address and no SNI. Assert.IsFalse(results[1].HasError, "This call to StoreAndDeploy() should not have an error adding binding while deploying certificate"); Assert.AreEqual("Deployment.UpdateBinding", results[1].Category); - Assert.IsTrue(results[1].Description.Contains("Update https binding | | **127.0.0.1:80:test.com Non-SNI**"), $"Unexpected description: '{results[1].Description}'"); + Assert.IsTrue(results[1].Description.Contains("Update https binding | | **127.0.0.1:443:test.com Non-SNI**"), $"Unexpected description: '{results[1].Description}'"); Assert.AreEqual("Install Certificate For Binding", results[1].Title); } From 758b976f769821567f5bd0d5128a0250551dadda Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Fri, 9 Aug 2024 17:05:28 +0800 Subject: [PATCH 224/328] Cleanup --- src/Certify.Core/Management/BindingDeploymentManager.cs | 6 ++---- .../Management/CertifyManager/CertifyManager.cs | 1 - src/Certify.Providers/ACME/Anvil/LoggingHandler.cs | 2 +- .../Certify.Core.Tests.Unit/Tests/BindingMatchTests.cs | 4 ++-- .../Certify.Core.Tests.Unit/Tests/MiscAcmeTests.cs | 3 +-- 5 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/Certify.Core/Management/BindingDeploymentManager.cs b/src/Certify.Core/Management/BindingDeploymentManager.cs index 96593849c..8a7f92ec0 100644 --- a/src/Certify.Core/Management/BindingDeploymentManager.cs +++ b/src/Certify.Core/Management/BindingDeploymentManager.cs @@ -1,13 +1,11 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Net; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; using Certify.Management; using Certify.Models; using Certify.Models.Providers; -using Microsoft.Web.Administration; namespace Certify.Core.Management { @@ -348,7 +346,7 @@ private async Task> DeployToAllTargetBindings(IBindingDeploymen { sslPort = int.Parse(requestConfig.BindingPort); - if (sslPort!=443) + if (sslPort != 443) { var step = new ActionStep(category, "Binding Port", $"A non-standard http port has been requested ({sslPort}) ."); bindingExplanationSteps.Add(step); @@ -408,7 +406,7 @@ private async Task> DeployToAllTargetBindings(IBindingDeploymen ); stepActions.First().Substeps = bindingExplanationSteps; - + actions.AddRange(stepActions); } else diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.cs index f61c1ff7f..01da9df20 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.cs @@ -13,7 +13,6 @@ using Certify.Providers; using Microsoft.Extensions.Logging; using Serilog; -using Serilog.Core; namespace Certify.Management { diff --git a/src/Certify.Providers/ACME/Anvil/LoggingHandler.cs b/src/Certify.Providers/ACME/Anvil/LoggingHandler.cs index ff7ad26a0..e75bf1465 100644 --- a/src/Certify.Providers/ACME/Anvil/LoggingHandler.cs +++ b/src/Certify.Providers/ACME/Anvil/LoggingHandler.cs @@ -66,7 +66,7 @@ protected override async Task SendAsync(HttpRequestMessage if (response?.Content != null) { try - { + { _log.Debug("Content: {content}", await response.Content.ReadAsStringAsync()); } catch diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/BindingMatchTests.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/BindingMatchTests.cs index e6bb6b4cb..21a3c4207 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/BindingMatchTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/BindingMatchTests.cs @@ -1240,7 +1240,7 @@ public async Task TestIPSpecific_ExistingHttpsBinding() SubjectAlternativeNames = new string[] { "ipspecific.test.com", "ipspecific2.test.com", "nonipspecific.test.com", "nonipspecific2.test.com", "nonipspecific3.test.com" }, PerformAutomatedCertBinding = true, DeploymentSiteOption = DeploymentOption.SingleSite, - + DeploymentBindingBlankHostname = true, BindingIPAddress = "127.0.0.1", BindingPort = "443", @@ -1261,7 +1261,7 @@ public async Task TestIPSpecific_ExistingHttpsBinding() var results = await deployment.StoreAndDeploy(mockTarget, testManagedCert, "test.pfx", pfxPwd: "", true, Certify.Management.CertificateManager.WEBHOSTING_STORE_NAME); - Assert.AreEqual(6, results.Count(r=>r.ObjectResult is BindingInfo)); + Assert.AreEqual(6, results.Count(r => r.ObjectResult is BindingInfo)); // existing IP specific https binding should be preserved var bindingInfo = results.Last(r => (r.ObjectResult as BindingInfo)?.Host == "ipspecific.test.com")?.ObjectResult as BindingInfo; diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/MiscAcmeTests.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/MiscAcmeTests.cs index f413c10d1..7cec545f7 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/MiscAcmeTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/MiscAcmeTests.cs @@ -1,5 +1,4 @@ -using System; -using System.Net; +using System.Net; using System.Net.Http; using System.Text; using System.Threading; From 848d29149fbe481f9b45eff84a9f7e6bc9d9f004 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Mon, 12 Aug 2024 12:40:20 +0800 Subject: [PATCH 225/328] Update dev build props --- Directory.Build.props | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index b47498fba..5a4901c12 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,10 +1,10 @@ - 6.1.0 - 6.1.0 + 7.0.0 + 7.0.0 Webprofusion Pty Ltd Webprofusion Pty Ltd - Certify The Web - Certify Certificate Manager + Certify Certificate Manager - Community Edition [dev version via github] https://certifytheweb.com https://github.com/webprofusion/certify false From 174cbb397dbedddac867dc051a77628d34178eb7 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Mon, 12 Aug 2024 14:10:32 +0800 Subject: [PATCH 226/328] Cleanup rebase from release branch --- src/Certify.Client/CertifyServiceClient.cs | 2 +- .../CertifyManager.Maintenance.cs | 23 ------------------- .../Config/CertRequestConfig.cs | 2 -- src/Certify.Models/Util/EnvironmentUtil.cs | 3 +++ .../Management/CertificateManager.cs | 3 --- .../AppViewModel.ManagedCerticates.cs | 1 + 6 files changed, 5 insertions(+), 29 deletions(-) diff --git a/src/Certify.Client/CertifyServiceClient.cs b/src/Certify.Client/CertifyServiceClient.cs index d09128a3e..2f743fc06 100644 --- a/src/Certify.Client/CertifyServiceClient.cs +++ b/src/Certify.Client/CertifyServiceClient.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Threading.Tasks; using Certify.Models; diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.Maintenance.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.Maintenance.cs index df01cbdef..0dfb52078 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.Maintenance.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.Maintenance.cs @@ -15,29 +15,6 @@ namespace Certify.Management { public partial class CertifyManager { - /// - /// Upgrade/migrate settings from previous version if applicable - /// - /// - private async Task UpgradeSettings() - { - var systemVersion = Util.GetAppVersion().ToString(); - var previousVersion = CoreAppSettings.Current.CurrentServiceVersion; - - if (CoreAppSettings.Current.CurrentServiceVersion != systemVersion) - { - _tc?.TrackEvent("ServiceUpgrade", new Dictionary { - { "previousVersion", previousVersion }, - { "currentVersion", systemVersion } - }); - - // service has been updated, run any required migrations - await PerformServiceUpgrades(); - - CoreAppSettings.Current.CurrentServiceVersion = systemVersion; - SettingsManager.SaveAppSettings(); - } - } /// /// Upgrade/migrate settings from previous version if applicable diff --git a/src/Certify.Models/Config/CertRequestConfig.cs b/src/Certify.Models/Config/CertRequestConfig.cs index 8492dc4cc..d57cb32c4 100644 --- a/src/Certify.Models/Config/CertRequestConfig.cs +++ b/src/Certify.Models/Config/CertRequestConfig.cs @@ -343,8 +343,6 @@ internal List GetCertificateDomains() private static JsonSerializerOptions _defaultJsonSerializerOptions = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; - private static JsonSerializerOptions _defaultJsonSerializerOptions = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; - public List GetCertificateIdentifiers() { var identifiers = new List(); diff --git a/src/Certify.Models/Util/EnvironmentUtil.cs b/src/Certify.Models/Util/EnvironmentUtil.cs index c3d0cd38e..7051f35bb 100644 --- a/src/Certify.Models/Util/EnvironmentUtil.cs +++ b/src/Certify.Models/Util/EnvironmentUtil.cs @@ -1,6 +1,9 @@ using System; using System.Collections.Generic; using System.IO; +using System.Runtime.InteropServices; +using System.Security.AccessControl; +using System.Security.Principal; namespace Certify.Models { diff --git a/src/Certify.Shared/Management/CertificateManager.cs b/src/Certify.Shared/Management/CertificateManager.cs index 85eb83bc0..13bcc8b3c 100644 --- a/src/Certify.Shared/Management/CertificateManager.cs +++ b/src/Certify.Shared/Management/CertificateManager.cs @@ -752,9 +752,6 @@ private static string GetWindowsPrivateKeyLocation(string keyFileName) // if EC/CNG key may be under /keys machineKeyPath = Path.Combine(new string[] { appDataPath, "Microsoft", "Crypto", "Keys" }); - // if EC/CNG key may be under /keys - machineKeyPath = Path.Combine(new string[] { appDataPath, "Microsoft", "Crypto", "Keys" }); - fileList = Directory.GetFiles(machineKeyPath, keyFileName); if (fileList.Any()) diff --git a/src/Certify.UI.Shared/ViewModel/AppViewModel/AppViewModel.ManagedCerticates.cs b/src/Certify.UI.Shared/ViewModel/AppViewModel/AppViewModel.ManagedCerticates.cs index 3206829e9..49d7f8f38 100644 --- a/src/Certify.UI.Shared/ViewModel/AppViewModel/AppViewModel.ManagedCerticates.cs +++ b/src/Certify.UI.Shared/ViewModel/AppViewModel/AppViewModel.ManagedCerticates.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; From a9a2386f0cd848df8e725b4d9d0da81586e381ca Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Wed, 4 Sep 2024 12:04:35 +0800 Subject: [PATCH 227/328] Package updates Package updates Package updates Package updates Package updates --- .../Certify.Aspire.AppHost.csproj | 2 +- .../Certify.Aspire.ServiceDefaults.csproj | 4 ++-- src/Certify.Client/Certify.Client.csproj | 6 +++--- .../CertifyManager.ManagedCertificates.cs | 2 +- src/Certify.Models/Certify.Models.csproj | 4 ++-- .../DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj | 2 +- .../DNS/Azure/Plugin.DNS.Azure.csproj | 2 +- .../Certify.Server.Api.Public.Tests.csproj | 10 +++++----- .../Certify.Server.Api.Public.csproj | 4 ++-- .../Certify.Server.Core/Certify.Server.Core.csproj | 10 +++++----- src/Certify.Service/App.config | 4 ++-- src/Certify.Service/Certify.Service.csproj | 4 ++-- .../Certify.Shared.Extensions.csproj | 2 +- .../Certify.SourceGenerators.csproj | 2 +- .../Certify.Core.Tests.Integration.csproj | 8 ++++---- .../Certify.Core.Tests.Unit.csproj | 10 +++++----- .../Certify.Service.Tests.Integration.csproj | 8 ++++---- .../Certify.UI.Tests.Integration.csproj | 8 ++++---- 18 files changed, 46 insertions(+), 46 deletions(-) diff --git a/src/Certify.Aspire/Certify.Aspire.AppHost/Certify.Aspire.AppHost.csproj b/src/Certify.Aspire/Certify.Aspire.AppHost/Certify.Aspire.AppHost.csproj index f4b1d96e4..2eb5f1c9d 100644 --- a/src/Certify.Aspire/Certify.Aspire.AppHost/Certify.Aspire.AppHost.csproj +++ b/src/Certify.Aspire/Certify.Aspire.AppHost/Certify.Aspire.AppHost.csproj @@ -9,7 +9,7 @@ - + diff --git a/src/Certify.Aspire/Certify.Aspire.ServiceDefaults/Certify.Aspire.ServiceDefaults.csproj b/src/Certify.Aspire/Certify.Aspire.ServiceDefaults/Certify.Aspire.ServiceDefaults.csproj index 32cfa7b8e..6e74a002b 100644 --- a/src/Certify.Aspire/Certify.Aspire.ServiceDefaults/Certify.Aspire.ServiceDefaults.csproj +++ b/src/Certify.Aspire/Certify.Aspire.ServiceDefaults/Certify.Aspire.ServiceDefaults.csproj @@ -11,8 +11,8 @@ - - + + diff --git a/src/Certify.Client/Certify.Client.csproj b/src/Certify.Client/Certify.Client.csproj index 60c695454..45d311d69 100644 --- a/src/Certify.Client/Certify.Client.csproj +++ b/src/Certify.Client/Certify.Client.csproj @@ -16,9 +16,9 @@ - - - + + + diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagedCertificates.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagedCertificates.cs index 9bcea4a6d..29b00a230 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagedCertificates.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagedCertificates.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; diff --git a/src/Certify.Models/Certify.Models.csproj b/src/Certify.Models/Certify.Models.csproj index 290f326d6..da5b812ee 100644 --- a/src/Certify.Models/Certify.Models.csproj +++ b/src/Certify.Models/Certify.Models.csproj @@ -21,8 +21,8 @@ all runtime; build; native; contentfiles; analyzers - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj index b94ee00d3..9556efb0b 100644 --- a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj +++ b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj b/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj index 8e9b1ccc8..6a92aed39 100644 --- a/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj +++ b/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj b/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj index 6d93da5aa..4bf8339a7 100644 --- a/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj +++ b/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj @@ -10,11 +10,11 @@ - - - - - + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj index cae528e5b..cb6283502 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj +++ b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj @@ -18,9 +18,9 @@ - + - + diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj index 95563355f..4def1ce34 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj @@ -11,12 +11,12 @@ - + - - - - + + + + diff --git a/src/Certify.Service/App.config b/src/Certify.Service/App.config index 69a44a551..e7c7e79b2 100644 --- a/src/Certify.Service/App.config +++ b/src/Certify.Service/App.config @@ -14,7 +14,7 @@ - + @@ -34,7 +34,7 @@ - + diff --git a/src/Certify.Service/Certify.Service.csproj b/src/Certify.Service/Certify.Service.csproj index 537b23b2b..a2555e579 100644 --- a/src/Certify.Service/Certify.Service.csproj +++ b/src/Certify.Service/Certify.Service.csproj @@ -37,7 +37,7 @@ - + @@ -55,7 +55,7 @@ - + diff --git a/src/Certify.Shared.Extensions/Certify.Shared.Extensions.csproj b/src/Certify.Shared.Extensions/Certify.Shared.Extensions.csproj index a67cf4149..f55b256c6 100644 --- a/src/Certify.Shared.Extensions/Certify.Shared.Extensions.csproj +++ b/src/Certify.Shared.Extensions/Certify.Shared.Extensions.csproj @@ -23,7 +23,7 @@ - + diff --git a/src/Certify.SourceGenerators/Certify.SourceGenerators.csproj b/src/Certify.SourceGenerators/Certify.SourceGenerators.csproj index ae7640c78..b3ef958b5 100644 --- a/src/Certify.SourceGenerators/Certify.SourceGenerators.csproj +++ b/src/Certify.SourceGenerators/Certify.SourceGenerators.csproj @@ -5,7 +5,7 @@ true - + diff --git a/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj b/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj index abb2a5fa4..92edb8c64 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj @@ -71,13 +71,13 @@ - + - - - + + + diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj index e00341169..41e3db49b 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj @@ -105,17 +105,17 @@ - + - - - + + + - + diff --git a/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj b/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj index aef71dfbb..2c33df410 100644 --- a/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj @@ -59,11 +59,11 @@ - + - - - + + + diff --git a/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj b/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj index 0c0b361b4..1a1ee27f9 100644 --- a/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj @@ -55,11 +55,11 @@ - + - - - + + + From 26e3efbafde07484f8c8cf6bb950ecda27c52a14 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Mon, 9 Sep 2024 18:57:42 +0800 Subject: [PATCH 228/328] Cert managers: Get provider info --- src/Certify.Models/Providers/ICertificateManager.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Certify.Models/Providers/ICertificateManager.cs b/src/Certify.Models/Providers/ICertificateManager.cs index 8bf779b5d..194547972 100644 --- a/src/Certify.Models/Providers/ICertificateManager.cs +++ b/src/Certify.Models/Providers/ICertificateManager.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using Certify.Models; +using Certify.Models.Config; using Certify.Models.Providers; #nullable disable @@ -27,5 +28,7 @@ public interface ICertificateManager Task> PerformRenewalAllManagedCertificates(RenewalSettings settings, Dictionary> progressTrackers = null); Task PerformCertificateCleanup(); + + ProviderDefinition GetProviderDefinition(); } } From f00ef953e48ee69a227228a97d53c0114e82c17d Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Tue, 10 Sep 2024 10:56:50 +0800 Subject: [PATCH 229/328] Test: fix renewal hold to account for items exceeding max failures --- .../Tests/RenewalRequiredTests.cs | 43 ++++++++++++++++++- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/RenewalRequiredTests.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/RenewalRequiredTests.cs index bf8514764..d4fc47fa7 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/RenewalRequiredTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/RenewalRequiredTests.cs @@ -63,7 +63,7 @@ public void TestCheckAutoRenewalPeriodRequiredWithFailuresHold() DateExpiry = DateTimeOffset.UtcNow.AddDays(60), DateLastRenewalAttempt = DateTimeOffset.UtcNow.AddHours(-12), LastRenewalStatus = RequestState.Error, - RenewalFailureCount = 2000, // high number of failures + RenewalFailureCount = 100, // high number of failures DateNextScheduledRenewalAttempt = DateTimeOffset.UtcNow.AddHours(-0.1) // scheduled renewal set to become due }; @@ -77,12 +77,51 @@ var renewalDueCheck Assert.AreEqual(renewalDueCheck.HoldHrs, 48, "Hold should be for 48 Hrs"); managedCertificate.DateLastRenewalAttempt = DateTimeOffset.UtcNow.AddHours(-49); + + // perform check as if last attempt was over 48rs ago, item should require renewal and not be on hold + renewalDueCheck = ManagedCertificate.CalculateNextRenewalAttempt(managedCertificate, renewalPeriodDays, renewalIntervalMode, true); + + // assert result + Assert.IsTrue(renewalDueCheck.IsRenewalDue, "Renewal should be required"); + Assert.IsFalse(renewalDueCheck.IsRenewalOnHold, "Renewal should not be on hold"); + } + + [TestMethod, Description("Ensure renewal hold when item has failed more than 100 times")] + public void TestCheckAutoRenewalWithTooManyFailuresHold() + { + // setup + var renewalPeriodDays = 14; + var renewalIntervalMode = RenewalIntervalModes.DaysAfterLastRenewal; + + var managedCertificate = new ManagedCertificate + { + IncludeInAutoRenew = true, + DateRenewed = DateTimeOffset.UtcNow.AddDays(-15), + DateStart = DateTimeOffset.UtcNow.AddDays(-15), + DateExpiry = DateTimeOffset.UtcNow.AddDays(60), + DateLastRenewalAttempt = DateTimeOffset.UtcNow.AddHours(-12), + LastRenewalStatus = RequestState.Error, + RenewalFailureCount = 1001, // too many failures + DateNextScheduledRenewalAttempt = DateTimeOffset.UtcNow.AddHours(-0.1) // scheduled renewal set to become due + }; + // perform check + var renewalDueCheck + = ManagedCertificate.CalculateNextRenewalAttempt(managedCertificate, renewalPeriodDays, renewalIntervalMode, true); + + // assert result + Assert.IsTrue(renewalDueCheck.IsRenewalDue, "Renewal should be required"); + Assert.IsTrue(renewalDueCheck.IsRenewalOnHold, "Renewal should be on hold"); + Assert.AreEqual(renewalDueCheck.HoldHrs, 48, "Hold should be for 48 Hrs"); + + managedCertificate.DateLastRenewalAttempt = DateTimeOffset.UtcNow.AddHours(-49); + + // perform check as if last attempt was over 48rs ago, item should require renewal and not be on hold renewalDueCheck = ManagedCertificate.CalculateNextRenewalAttempt(managedCertificate, renewalPeriodDays, renewalIntervalMode, true); // assert result Assert.IsTrue(renewalDueCheck.IsRenewalDue, "Renewal should be required"); - Assert.IsFalse(renewalDueCheck.IsRenewalOnHold, "Renewal should be required"); + Assert.IsTrue(renewalDueCheck.IsRenewalOnHold, "Renewal should permanently be on hol, too many failures."); } [TestMethod, Description("Ensure a site which should be renewed correctly requires renewal")] From e3a5cd22140c53f101823c0b0425dd800538eb32 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Wed, 11 Sep 2024 17:20:36 +0800 Subject: [PATCH 230/328] Package updates --- .../DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj | 2 +- .../Certify.Service.Tests.Integration.csproj | 8 ++++---- .../Certify.UI.Tests.Integration.csproj | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj index 9556efb0b..8d06160b2 100644 --- a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj +++ b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj b/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj index 2c33df410..7e5627d87 100644 --- a/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj @@ -59,11 +59,11 @@ - + - - - + + + diff --git a/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj b/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj index 1a1ee27f9..8a77486a2 100644 --- a/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj @@ -55,11 +55,11 @@ - + - - - + + + From 5191fab3939f55140662da8f12a61cba6f00fa3f Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Wed, 11 Sep 2024 17:20:50 +0800 Subject: [PATCH 231/328] Misc warnings for platform specific code --- src/Certify.Models/Util/EnvironmentUtil.cs | 61 ++++++++++++---------- 1 file changed, 33 insertions(+), 28 deletions(-) diff --git a/src/Certify.Models/Util/EnvironmentUtil.cs b/src/Certify.Models/Util/EnvironmentUtil.cs index 7051f35bb..033e497bf 100644 --- a/src/Certify.Models/Util/EnvironmentUtil.cs +++ b/src/Certify.Models/Util/EnvironmentUtil.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; @@ -12,45 +12,50 @@ public class EnvironmentUtil private static void ApplyRestrictedACL(DirectoryInfo dir) { - // disable default inheritance for the existing path ACL - var acl = dir.GetAccessControl(); - acl.SetAccessRuleProtection(true, true); - dir.SetAccessControl(acl); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { - // removing any existing ACL entries for users group - acl = dir.GetAccessControl(); - var currentRules = acl.GetAccessRules(true, true, typeof(SecurityIdentifier)); + // disable default inheritance for the existing path ACL + var acl = dir.GetAccessControl(); + acl.SetAccessRuleProtection(true, true); + dir.SetAccessControl(acl); - var allUsersSid = new SecurityIdentifier(WellKnownSidType.BuiltinUsersSid, null); + // removing any existing ACL entries for users group + acl = dir.GetAccessControl(); + var currentRules = acl.GetAccessRules(true, true, typeof(SecurityIdentifier)); - var processUserSid = WindowsIdentity.GetCurrent().User; + var allUsersSid = new SecurityIdentifier(WellKnownSidType.BuiltinUsersSid, null); - if (processUserSid != null) - { - foreach (FileSystemAccessRule rule in currentRules) + var processUserSid = WindowsIdentity.GetCurrent().User; + + if (processUserSid != null) { - if (rule.IdentityReference == allUsersSid || rule.IdentityReference == processUserSid) + foreach (FileSystemAccessRule rule in currentRules) { - acl.RemoveAccessRuleAll(rule); + if (rule.IdentityReference == allUsersSid || rule.IdentityReference == processUserSid) + { + acl.RemoveAccessRuleAll(rule); + } } + + // add full control for the current (process) user + var currentUserRights = new FileSystemAccessRule(processUserSid, FileSystemRights.FullControl, InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit, PropagationFlags.None, AccessControlType.Allow); + acl.AddAccessRule(currentUserRights); } - // add full control for the current (process) user - var currentUserRights = new FileSystemAccessRule(processUserSid, FileSystemRights.FullControl, InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit, PropagationFlags.None, AccessControlType.Allow); - acl.AddAccessRule(currentUserRights); - } + // add full control for administrators + var adminSid = new SecurityIdentifier(WellKnownSidType.BuiltinAdministratorsSid, null); + var adminRights = new FileSystemAccessRule(adminSid, FileSystemRights.FullControl, AccessControlType.Allow); + acl.AddAccessRule(adminRights); - // add full control for administrators - var adminSid = new SecurityIdentifier(WellKnownSidType.BuiltinAdministratorsSid, null); - var adminRights = new FileSystemAccessRule(adminSid, FileSystemRights.FullControl, AccessControlType.Allow); - acl.AddAccessRule(adminRights); + // add full control for system + var systemUserSid = new SecurityIdentifier(WellKnownSidType.LocalSystemSid, null); + var systemUserRights = new FileSystemAccessRule(systemUserSid, FileSystemRights.FullControl, AccessControlType.Allow); + acl.AddAccessRule(systemUserRights); - // add full control for system - var systemUserSid = new SecurityIdentifier(WellKnownSidType.LocalSystemSid, null); - var systemUserRights = new FileSystemAccessRule(systemUserSid, FileSystemRights.FullControl, AccessControlType.Allow); - acl.AddAccessRule(systemUserRights); + dir.SetAccessControl(acl); - dir.SetAccessControl(acl); + } } /// From 9b918d6c7f3a8bdb6a1fe1fccd00450cf71f95a5 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Wed, 11 Sep 2024 17:21:05 +0800 Subject: [PATCH 232/328] Optionally store certificate pem in config --- src/Certify.Models/Config/ManagedCertificate.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Certify.Models/Config/ManagedCertificate.cs b/src/Certify.Models/Config/ManagedCertificate.cs index fda9d6f33..97f68dfc7 100644 --- a/src/Certify.Models/Config/ManagedCertificate.cs +++ b/src/Certify.Models/Config/ManagedCertificate.cs @@ -302,6 +302,11 @@ public ManagedCertificate() /// public string? CustomRenewalIntervalMode { get; set; } + /// + /// PEM encoded version of public certificate + /// + public string? CertificatePEM { get; set; } + public override string ToString() => $"[{Id ?? "null"}]: \"{Name}\""; [JsonIgnore] From f6d2004e08ca4dddb48441bdc668a853f1cfb54e Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Mon, 16 Sep 2024 10:03:02 +0800 Subject: [PATCH 233/328] Package updates and target net9 --- .../Certify.Aspire.ServiceDefaults.csproj | 2 +- src/Certify.Models/Certify.Models.csproj | 2 +- .../AWSRoute53/Plugin.DNS.AWSRoute53.csproj | 2 +- .../DNS/Azure/Plugin.DNS.Azure.csproj | 2 +- .../Certify.API.Public.cs | 49 ++++++++++--------- .../Certify.Server.Api.Public.Client.csproj | 2 +- .../Certify.Server.Api.Public.Tests.csproj | 6 +-- .../Certify.Server.Api.Public.csproj | 3 +- .../Properties/launchSettings.json | 8 +-- .../Certify.Server.Api.Public/Startup.cs | 6 +-- .../Properties/launchSettings.json | 3 +- src/Certify.Service/App.config | 2 +- src/Certify.Service/Certify.Service.csproj | 2 +- .../Certify.Core.Tests.Integration.csproj | 8 +-- .../Certify.Core.Tests.Unit.csproj | 8 +-- .../Certify.UI.Shared.csproj | 2 +- src/Certify.UI/Certify.UI.csproj | 3 +- 17 files changed, 59 insertions(+), 51 deletions(-) diff --git a/src/Certify.Aspire/Certify.Aspire.ServiceDefaults/Certify.Aspire.ServiceDefaults.csproj b/src/Certify.Aspire/Certify.Aspire.ServiceDefaults/Certify.Aspire.ServiceDefaults.csproj index 6e74a002b..416e1484d 100644 --- a/src/Certify.Aspire/Certify.Aspire.ServiceDefaults/Certify.Aspire.ServiceDefaults.csproj +++ b/src/Certify.Aspire/Certify.Aspire.ServiceDefaults/Certify.Aspire.ServiceDefaults.csproj @@ -11,7 +11,7 @@ - + diff --git a/src/Certify.Models/Certify.Models.csproj b/src/Certify.Models/Certify.Models.csproj index da5b812ee..c195a7d94 100644 --- a/src/Certify.Models/Certify.Models.csproj +++ b/src/Certify.Models/Certify.Models.csproj @@ -17,7 +17,7 @@ AnyCPU - + all runtime; build; native; contentfiles; analyzers diff --git a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj index 8d06160b2..e39cf0a32 100644 --- a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj +++ b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj b/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj index 6a92aed39..d5a35224b 100644 --- a/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj +++ b/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs b/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs index 28418eced..8dfaf9c6d 100644 --- a/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs +++ b/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs @@ -1,6 +1,6 @@ //---------------------- // -// Generated using the NSwag toolchain v14.0.8.0 (NJsonSchema v11.0.1.0 (Newtonsoft.Json v13.0.0.0)) (http://NSwag.org) +// Generated using the NSwag toolchain v14.1.0.0 (NJsonSchema v11.0.2.0 (Newtonsoft.Json v13.0.0.0)) (http://NSwag.org) // //---------------------- @@ -17,6 +17,7 @@ #pragma warning disable 114 // Disable "CS0114 '{derivedDto}.RaisePropertyChanged(String)' hides inherited member 'dtoBase.RaisePropertyChanged(String)'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword." #pragma warning disable 472 // Disable "CS0472 The result of the expression is always 'false' since a value of type 'Int32' is never equal to 'null' of type 'Int32?' #pragma warning disable 612 // Disable "CS0612 '...' is obsolete" +#pragma warning disable 649 // Disable "CS0649 Field is never assigned to, and will always have its default value null" #pragma warning disable 1573 // Disable "CS1573 Parameter '...' has no matching param tag in the XML comment for ... #pragma warning disable 1591 // Disable "CS1591 Missing XML comment for publicly visible type or member ..." #pragma warning disable 8073 // Disable "CS8073 The result of the expression is always 'false' since a value of type 'T' is never equal to 'null' of type 'T?'" @@ -24,13 +25,13 @@ #pragma warning disable 8603 // Disable "CS8603 Possible null reference return" #pragma warning disable 8604 // Disable "CS8604 Possible null reference argument for parameter" #pragma warning disable 8625 // Disable "CS8625 Cannot convert null literal to non-nullable reference type" -#pragma warning disable CS8765 // Nullability of type of parameter doesn't match overridden member (possibly because of nullability attributes). +#pragma warning disable 8765 // Disable "CS8765 Nullability of type of parameter doesn't match overridden member (possibly because of nullability attributes)." namespace Certify.API.Public { using System = global::System; - [System.CodeDom.Compiler.GeneratedCode("NSwag", "14.0.8.0 (NJsonSchema v11.0.1.0 (Newtonsoft.Json v13.0.0.0))")] + [System.CodeDom.Compiler.GeneratedCode("NSwag", "14.1.0.0 (NJsonSchema v11.0.2.0 (Newtonsoft.Json v13.0.0.0))")] public partial class Client { #pragma warning disable 8618 @@ -39,6 +40,7 @@ public partial class Client private System.Net.Http.HttpClient _httpClient; private static System.Lazy _settings = new System.Lazy(CreateSerializerSettings, true); + private Newtonsoft.Json.JsonSerializerSettings _instanceSettings; #pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. public Client(string baseUrl, System.Net.Http.HttpClient httpClient) @@ -46,6 +48,7 @@ public Client(string baseUrl, System.Net.Http.HttpClient httpClient) { BaseUrl = baseUrl; _httpClient = httpClient; + Initialize(); } private static Newtonsoft.Json.JsonSerializerSettings CreateSerializerSettings() @@ -66,10 +69,12 @@ public string BaseUrl } } - protected Newtonsoft.Json.JsonSerializerSettings JsonSerializerSettings { get { return _settings.Value; } } + protected Newtonsoft.Json.JsonSerializerSettings JsonSerializerSettings { get { return _instanceSettings ?? _settings.Value; } } static partial void UpdateJsonSerializerSettings(Newtonsoft.Json.JsonSerializerSettings settings); + partial void Initialize(); + partial void PrepareRequest(System.Net.Http.HttpClient client, System.Net.Http.HttpRequestMessage request, string url); partial void PrepareRequest(System.Net.Http.HttpClient client, System.Net.Http.HttpRequestMessage request, System.Text.StringBuilder urlBuilder); partial void ProcessResponse(System.Net.Http.HttpClient client, System.Net.Http.HttpResponseMessage response); @@ -444,7 +449,7 @@ public virtual async System.Threading.Tasks.Task { using (var request_ = new System.Net.Http.HttpRequestMessage()) { - var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value); + var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(body, JsonSerializerSettings); var content_ = new System.Net.Http.StringContent(json_); content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); request_.Content = content_; @@ -532,7 +537,7 @@ public virtual async System.Threading.Tasks.Task UpdateSecurityPri { using (var request_ = new System.Net.Http.HttpRequestMessage()) { - var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value); + var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(body, JsonSerializerSettings); var content_ = new System.Net.Http.StringContent(json_); content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); request_.Content = content_; @@ -620,7 +625,7 @@ public virtual async System.Threading.Tasks.Task AddSecurityPrinci { using (var request_ = new System.Net.Http.HttpRequestMessage()) { - var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value); + var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(body, JsonSerializerSettings); var content_ = new System.Net.Http.StringContent(json_); content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); request_.Content = content_; @@ -798,7 +803,7 @@ public virtual async System.Threading.Tasks.Task UpdateSecurityPri { using (var request_ = new System.Net.Http.HttpRequestMessage()) { - var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value); + var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(body, JsonSerializerSettings); var content_ = new System.Net.Http.StringContent(json_); content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); request_.Content = content_; @@ -886,7 +891,7 @@ public virtual async System.Threading.Tasks.Task UpdateSecurityPri { using (var request_ = new System.Net.Http.HttpRequestMessage()) { - var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value); + var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(body, JsonSerializerSettings); var content_ = new System.Net.Http.StringContent(json_); content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); request_.Content = content_; @@ -1054,7 +1059,7 @@ public virtual async System.Threading.Tasks.Task LoginAsync(AuthRe { using (var request_ = new System.Net.Http.HttpRequestMessage()) { - var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value); + var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(body, JsonSerializerSettings); var content_ = new System.Net.Http.StringContent(json_); content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); request_.Content = content_; @@ -1805,7 +1810,7 @@ public virtual async System.Threading.Tasks.Task UpdateManag { using (var request_ = new System.Net.Http.HttpRequestMessage()) { - var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value); + var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(body, JsonSerializerSettings); var content_ = new System.Net.Http.StringContent(json_); content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); request_.Content = content_; @@ -1984,7 +1989,7 @@ public virtual async System.Threading.Tasks.Task PerformRene { using (var request_ = new System.Net.Http.HttpRequestMessage()) { - var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value); + var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(body, JsonSerializerSettings); var content_ = new System.Net.Http.StringContent(json_); content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); request_.Content = content_; @@ -2078,7 +2083,7 @@ public virtual async System.Threading.Tasks.Task PerformRene { using (var request_ = new System.Net.Http.HttpRequestMessage()) { - var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value); + var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(body, JsonSerializerSettings); var content_ = new System.Net.Http.StringContent(json_); content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); request_.Content = content_; @@ -2340,7 +2345,7 @@ public virtual async System.Threading.Tasks.Task AddAcmeAccountAsy { using (var request_ = new System.Net.Http.HttpRequestMessage()) { - var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value); + var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(body, JsonSerializerSettings); var content_ = new System.Net.Http.StringContent(json_); content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); request_.Content = content_; @@ -2428,7 +2433,7 @@ public virtual async System.Threading.Tasks.Task AddCertificateAut { using (var request_ = new System.Net.Http.HttpRequestMessage()) { - var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value); + var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(body, JsonSerializerSettings); var content_ = new System.Net.Http.StringContent(json_); content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); request_.Content = content_; @@ -3213,7 +3218,7 @@ public virtual async System.Threading.Tasks.Task UpdateStoredC { using (var request_ = new System.Net.Http.HttpRequestMessage()) { - var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value); + var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(body, JsonSerializerSettings); var content_ = new System.Net.Http.StringContent(json_); content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); request_.Content = content_; @@ -3729,7 +3734,7 @@ public virtual async System.Threading.Tasks.Task PerformExp { using (var request_ = new System.Net.Http.HttpRequestMessage()) { - var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value); + var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(body, JsonSerializerSettings); var content_ = new System.Net.Http.StringContent(json_); content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); request_.Content = content_; @@ -3817,7 +3822,7 @@ public virtual async System.Threading.Tasks.Task PerformExp { using (var request_ = new System.Net.Http.HttpRequestMessage()) { - var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value); + var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(body, JsonSerializerSettings); var content_ = new System.Net.Http.StringContent(json_); content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); request_.Content = content_; @@ -4448,7 +4453,7 @@ private string ConvertToString(object value, System.Globalization.CultureInfo cu - [System.CodeDom.Compiler.GeneratedCode("NSwag", "14.0.8.0 (NJsonSchema v11.0.1.0 (Newtonsoft.Json v13.0.0.0))")] + [System.CodeDom.Compiler.GeneratedCode("NSwag", "14.1.0.0 (NJsonSchema v11.0.2.0 (Newtonsoft.Json v13.0.0.0))")] public partial class FileResponse : System.IDisposable { private System.IDisposable _client; @@ -4485,7 +4490,7 @@ public void Dispose() } - [System.CodeDom.Compiler.GeneratedCode("NSwag", "14.0.8.0 (NJsonSchema v11.0.1.0 (Newtonsoft.Json v13.0.0.0))")] + [System.CodeDom.Compiler.GeneratedCode("NSwag", "14.1.0.0 (NJsonSchema v11.0.2.0 (Newtonsoft.Json v13.0.0.0))")] public partial class ApiException : System.Exception { public int StatusCode { get; private set; } @@ -4508,7 +4513,7 @@ public override string ToString() } } - [System.CodeDom.Compiler.GeneratedCode("NSwag", "14.0.8.0 (NJsonSchema v11.0.1.0 (Newtonsoft.Json v13.0.0.0))")] + [System.CodeDom.Compiler.GeneratedCode("NSwag", "14.1.0.0 (NJsonSchema v11.0.2.0 (Newtonsoft.Json v13.0.0.0))")] public partial class ApiException : ApiException { public TResult Result { get; private set; } diff --git a/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.Server.Api.Public.Client.csproj b/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.Server.Api.Public.Client.csproj index b52f9423a..302e7dfd0 100644 --- a/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.Server.Api.Public.Client.csproj +++ b/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.Server.Api.Public.Client.csproj @@ -1,7 +1,7 @@  - net8.0;net9.0 + net9.0 enable enable diff --git a/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj b/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj index 4bf8339a7..c00f1687c 100644 --- a/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj +++ b/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj @@ -12,9 +12,9 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj index cb6283502..cf9412482 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj +++ b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj @@ -18,7 +18,8 @@ - + + diff --git a/src/Certify.Server/Certify.Server.Api.Public/Properties/launchSettings.json b/src/Certify.Server/Certify.Server.Api.Public/Properties/launchSettings.json index 38d1d0174..4da6856e9 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Properties/launchSettings.json +++ b/src/Certify.Server/Certify.Server.Api.Public/Properties/launchSettings.json @@ -4,7 +4,7 @@ "commandName": "IISExpress", "launchUrl": "https://localhost:44361/docs", "environmentVariables": { - "ASPNETCORE_URLS": "https://localhost:44361;http://localhost:44360", + "ASPNETCORE_URLS": "https://localhost:44361;", "CERTIFY_SERVICE_HOST": "127.0.0.2", // note: aspire hosted uses this profile "CERTIFY_SERVICE_PORT": "9695" } @@ -16,14 +16,14 @@ "CERTIFY_SERVICE_HOST": "127.0.0.2", "CERTIFY_SERVICE_PORT": "9695" }, - "applicationUrl": "https://0.0.0.0:44361;http://0.0.0.0:44360" + "applicationUrl": "https://0.0.0.0:44361;" }, "WSL": { "commandName": "WSL2", "launchUrl": "https://localhost:44361/docs", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development", - "ASPNETCORE_URLS": "https://localhost:44361;http://localhost:44360", + "ASPNETCORE_URLS": "https://localhost:44361;", "CERTIFY_SERVICE_HOST": "localhost", "CERTIFY_SERVICE_PORT": "9695" } @@ -32,7 +32,7 @@ "commandName": "Docker", "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/docs", "environmentVariables": { - "ASPNETCORE_URLS": "https://0.0.0.0:44361;http://0.0.0.0:44360", + "ASPNETCORE_URLS": "https://0.0.0.0:44361;", "CERTIFY_SERVICE_HOST": "localhost", "CERTIFY_SERVICE_PORT": "9695" }, diff --git a/src/Certify.Server/Certify.Server.Api.Public/Startup.cs b/src/Certify.Server/Certify.Server.Api.Public/Startup.cs index 42f80ae92..7553214d3 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Startup.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Startup.cs @@ -66,14 +66,14 @@ public void ConfigureServices(IServiceCollection services) .AddSignalR(opt => opt.MaximumReceiveMessageSize = null) .AddMessagePackProtocol(); - + services.AddResponseCompression(opts => { opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat( new[] { "application/octet-stream", "application/json" }); }); -#if DEBUG + services.AddOpenApi(); // required in net9 to resolve warning "Unable to find service type 'Microsoft.Extensions.ApiDescriptions.IDocumentProvider' in dependency injection container." services.AddEndpointsApiExplorer(); @@ -139,7 +139,7 @@ public void ConfigureServices(IServiceCollection services) }); }); -#endif + // connect to certify service var configManager = new ServiceConfigManager(); var serviceConfig = configManager.GetServiceConfig(); diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Properties/launchSettings.json b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Properties/launchSettings.json index 84203e0a9..31fcdf982 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Properties/launchSettings.json +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Properties/launchSettings.json @@ -15,7 +15,8 @@ "launchBrowser": false, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development", - "ASPNETCORE_URLS": "http://127.0.0.2:9695" + "ASPNETCORE_URLS": "http://127.0.0.2:9695", + "CERTIFY_MANAGEMENT_HUB": "https://localhost:44361/api/internal/managementhub" } }, "IIS Express": { diff --git a/src/Certify.Service/App.config b/src/Certify.Service/App.config index e7c7e79b2..c9ed81aaf 100644 --- a/src/Certify.Service/App.config +++ b/src/Certify.Service/App.config @@ -14,7 +14,7 @@ - + diff --git a/src/Certify.Service/Certify.Service.csproj b/src/Certify.Service/Certify.Service.csproj index a2555e579..b36688bd9 100644 --- a/src/Certify.Service/Certify.Service.csproj +++ b/src/Certify.Service/Certify.Service.csproj @@ -37,7 +37,7 @@ - + diff --git a/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj b/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj index 92edb8c64..041e364c0 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj @@ -71,13 +71,13 @@ - + - - - + + + diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj index 41e3db49b..f1a8eea1c 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj @@ -105,11 +105,11 @@ - + - - - + + + diff --git a/src/Certify.UI.Shared/Certify.UI.Shared.csproj b/src/Certify.UI.Shared/Certify.UI.Shared.csproj index e2586a16e..db8575a0f 100644 --- a/src/Certify.UI.Shared/Certify.UI.Shared.csproj +++ b/src/Certify.UI.Shared/Certify.UI.Shared.csproj @@ -34,7 +34,7 @@ - + NU1701 diff --git a/src/Certify.UI/Certify.UI.csproj b/src/Certify.UI/Certify.UI.csproj index 68f45af6b..17e5bee05 100644 --- a/src/Certify.UI/Certify.UI.csproj +++ b/src/Certify.UI/Certify.UI.csproj @@ -45,6 +45,7 @@ AnyCPU + win-x64;win true bin\Release\ TRACE;ALPHA @@ -132,7 +133,7 @@ - 6.8.1 + 6.8.2 runtime; build; native; contentfiles; analyzers all From 15723ae78de0a68b7a3c7950cfae2ba755b91c2d Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Mon, 30 Sep 2024 12:37:18 +0800 Subject: [PATCH 234/328] Package updates --- .../Certify.Aspire.AppHost/Certify.Aspire.AppHost.csproj | 2 +- .../Certify.Aspire.ServiceDefaults.csproj | 2 +- src/Certify.Client/Certify.Client.csproj | 4 ++-- src/Certify.Core/Certify.Core.csproj | 2 +- src/Certify.Models/Certify.Models.csproj | 2 +- .../DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj | 2 +- src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj | 2 +- .../Certify.Server.Api.Public.csproj | 4 ++-- .../Certify.Server.Core/Certify.Server.Core.csproj | 4 ++-- src/Certify.Service/App.config | 2 +- src/Certify.Service/Certify.Service.csproj | 2 +- .../Certify.Core.Tests.Integration.csproj | 2 +- .../Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj | 2 +- .../Certify.Service.Tests.Integration.csproj | 2 +- 14 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/Certify.Aspire/Certify.Aspire.AppHost/Certify.Aspire.AppHost.csproj b/src/Certify.Aspire/Certify.Aspire.AppHost/Certify.Aspire.AppHost.csproj index 2eb5f1c9d..eee4af4ac 100644 --- a/src/Certify.Aspire/Certify.Aspire.AppHost/Certify.Aspire.AppHost.csproj +++ b/src/Certify.Aspire/Certify.Aspire.AppHost/Certify.Aspire.AppHost.csproj @@ -9,7 +9,7 @@ - + diff --git a/src/Certify.Aspire/Certify.Aspire.ServiceDefaults/Certify.Aspire.ServiceDefaults.csproj b/src/Certify.Aspire/Certify.Aspire.ServiceDefaults/Certify.Aspire.ServiceDefaults.csproj index 416e1484d..0ce46c3a1 100644 --- a/src/Certify.Aspire/Certify.Aspire.ServiceDefaults/Certify.Aspire.ServiceDefaults.csproj +++ b/src/Certify.Aspire/Certify.Aspire.ServiceDefaults/Certify.Aspire.ServiceDefaults.csproj @@ -12,7 +12,7 @@ - + diff --git a/src/Certify.Client/Certify.Client.csproj b/src/Certify.Client/Certify.Client.csproj index 45d311d69..2ebe62f60 100644 --- a/src/Certify.Client/Certify.Client.csproj +++ b/src/Certify.Client/Certify.Client.csproj @@ -18,9 +18,9 @@ - + - + diff --git a/src/Certify.Core/Certify.Core.csproj b/src/Certify.Core/Certify.Core.csproj index 36d40e890..3ec352508 100644 --- a/src/Certify.Core/Certify.Core.csproj +++ b/src/Certify.Core/Certify.Core.csproj @@ -52,7 +52,7 @@ 1701;1702;CA1068 - + diff --git a/src/Certify.Models/Certify.Models.csproj b/src/Certify.Models/Certify.Models.csproj index c195a7d94..251cbcc8c 100644 --- a/src/Certify.Models/Certify.Models.csproj +++ b/src/Certify.Models/Certify.Models.csproj @@ -21,7 +21,7 @@ all runtime; build; native; contentfiles; analyzers - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj index e39cf0a32..1f56f0187 100644 --- a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj +++ b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj b/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj index d5a35224b..2f701d329 100644 --- a/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj +++ b/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj index cf9412482..f4c06c237 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj +++ b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj @@ -18,10 +18,10 @@ - + - + diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj index 4def1ce34..e02c65f83 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj @@ -10,10 +10,10 @@ - + - + diff --git a/src/Certify.Service/App.config b/src/Certify.Service/App.config index c9ed81aaf..ffa3db271 100644 --- a/src/Certify.Service/App.config +++ b/src/Certify.Service/App.config @@ -34,7 +34,7 @@ - + diff --git a/src/Certify.Service/Certify.Service.csproj b/src/Certify.Service/Certify.Service.csproj index b36688bd9..7f942e6fa 100644 --- a/src/Certify.Service/Certify.Service.csproj +++ b/src/Certify.Service/Certify.Service.csproj @@ -37,7 +37,7 @@ - + diff --git a/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj b/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj index 041e364c0..60ca22006 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj @@ -79,7 +79,7 @@ - + diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj index f1a8eea1c..977c3949d 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj @@ -110,7 +110,7 @@ - + diff --git a/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj b/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj index 7e5627d87..deda2939c 100644 --- a/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj @@ -64,7 +64,7 @@ - + From 83ff888fb93723b6495bed3faa8b63742c56ba37 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Wed, 2 Oct 2024 16:04:21 +0800 Subject: [PATCH 235/328] Implement per instance get/add CA account --- .../CertifyManager.ManagementHub.cs | 15 ++++++- .../API/Management/ManagementHubMessages.cs | 2 + src/Certify.Models/Util/EnvironmentUtil.cs | 41 +++++++++++++++++ .../Certify.API.Public.cs | 28 +++++++----- .../CertificateAuthorityController.cs | 5 ++- .../Services/ManagementAPI.cs | 44 ++++++++++++++++++- .../Certify.Server.Api.Public/Startup.cs | 4 +- src/Certify.Shared/Utils/Util.cs | 2 +- src/Certify.SourceGenerators/ApiMethods.cs | 39 ++++++++-------- .../PublicAPISourceGenerator.cs | 18 ++++++-- 10 files changed, 160 insertions(+), 38 deletions(-) diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs index a9890800d..01e8a8c47 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs @@ -48,7 +48,7 @@ private async Task StartManagementHubConnection(string hubUri) var instanceInfo = new ManagedInstanceInfo { InstanceId = $"{this.InstanceId}", - Title = Environment.MachineName + Title = $"{Environment.MachineName} [{EnvironmentUtil.GetFriendlyOSName()}]", }; if (_managementServerClient != null) @@ -162,6 +162,19 @@ private async Task _managementServerClient_OnGetCommandRe val = true; } + else if (arg.CommandType == ManagementHubCommands.GetAcmeAccounts) + { + val = await GetAccountRegistrations(); + } + else if (arg.CommandType == ManagementHubCommands.AddAcmeAccount) + { + + var args = JsonSerializer.Deserialize[]>(arg.Value); + var registrationArg = args.FirstOrDefault(a => a.Key == "registration"); + var registration = JsonSerializer.Deserialize(registrationArg.Value); + + val = await AddAccount(registration); + } else if (arg.CommandType == ManagementHubCommands.Reconnect) { await _managementServerClient.Disconnect(); diff --git a/src/Certify.Models/API/Management/ManagementHubMessages.cs b/src/Certify.Models/API/Management/ManagementHubMessages.cs index 62dc9ebff..aa05b8106 100644 --- a/src/Certify.Models/API/Management/ManagementHubMessages.cs +++ b/src/Certify.Models/API/Management/ManagementHubMessages.cs @@ -24,6 +24,8 @@ public class ManagementHubCommands public const string TestManagedItemConfiguration = "TestManagedItemConfiguration"; public const string PerformManagedItemRequest = "PerformManagedItemRequest"; + public const string GetAcmeAccounts = "GetAcmeAccounts"; + public const string AddAcmeAccount = "AddAcmeAccount"; public const string Reconnect = "Reconnect"; } diff --git a/src/Certify.Models/Util/EnvironmentUtil.cs b/src/Certify.Models/Util/EnvironmentUtil.cs index 033e497bf..41217f3f2 100644 --- a/src/Certify.Models/Util/EnvironmentUtil.cs +++ b/src/Certify.Models/Util/EnvironmentUtil.cs @@ -114,5 +114,46 @@ public static string CreateAppDataPath(string? subDirectory = null) return path; } + + public static string GetFriendlyOSName() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return $"{RuntimeInformation.OSDescription}"; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + var osName = string.Empty; + + string filePath = "/etc/os-release"; + + try + { + using (FileStream fileStream = new FileStream(filePath, FileMode.Open)) + { + using (StreamReader reader = new StreamReader(fileStream)) + { + string line; + + while ((line = reader.ReadLine()) != null) + { + + if (line.StartsWith("NAME")) + { + osName = line.Split('\"')[1]; //split the line string by " and get the second slice + return osName; + } + } + } + } + } + catch + { + return $"Linux - {RuntimeInformation.OSDescription}"; + } + } + + return $"{RuntimeInformation.OSDescription}"; + } } } diff --git a/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs b/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs index 8dfaf9c6d..5fb22bcbe 100644 --- a/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs +++ b/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs @@ -2242,9 +2242,9 @@ public virtual async System.Threading.Tasks.Task PerformRene /// /// OK /// A server side error occurred. - public virtual System.Threading.Tasks.Task> GetAcmeAccountsAsync() + public virtual System.Threading.Tasks.Task> GetAcmeAccountsAsync(string instanceId) { - return GetAcmeAccountsAsync(System.Threading.CancellationToken.None); + return GetAcmeAccountsAsync(instanceId, System.Threading.CancellationToken.None); } /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. @@ -2253,8 +2253,11 @@ public virtual async System.Threading.Tasks.Task PerformRene /// /// OK /// A server side error occurred. - public virtual async System.Threading.Tasks.Task> GetAcmeAccountsAsync(System.Threading.CancellationToken cancellationToken) + public virtual async System.Threading.Tasks.Task> GetAcmeAccountsAsync(string instanceId, System.Threading.CancellationToken cancellationToken) { + if (instanceId == null) + throw new System.ArgumentNullException("instanceId"); + var client_ = _httpClient; var disposeClient_ = false; try @@ -2266,8 +2269,9 @@ public virtual async System.Threading.Tasks.Task PerformRene var urlBuilder_ = new System.Text.StringBuilder(); if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); - // Operation Path: "internal/v1/certificateauthority/accounts" - urlBuilder_.Append("internal/v1/certificateauthority/accounts"); + // Operation Path: "internal/v1/certificateauthority/accounts/{instanceId}" + urlBuilder_.Append("internal/v1/certificateauthority/accounts/"); + urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(instanceId, System.Globalization.CultureInfo.InvariantCulture))); PrepareRequest(client_, request_, urlBuilder_); @@ -2326,9 +2330,9 @@ public virtual async System.Threading.Tasks.Task PerformRene /// /// OK /// A server side error occurred. - public virtual System.Threading.Tasks.Task AddAcmeAccountAsync(ContactRegistration body) + public virtual System.Threading.Tasks.Task AddAcmeAccountAsync(string instanceId, ContactRegistration body) { - return AddAcmeAccountAsync(body, System.Threading.CancellationToken.None); + return AddAcmeAccountAsync(instanceId, body, System.Threading.CancellationToken.None); } /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. @@ -2337,8 +2341,11 @@ public virtual System.Threading.Tasks.Task AddAcmeAccountAsync(Con /// /// OK /// A server side error occurred. - public virtual async System.Threading.Tasks.Task AddAcmeAccountAsync(ContactRegistration body, System.Threading.CancellationToken cancellationToken) + public virtual async System.Threading.Tasks.Task AddAcmeAccountAsync(string instanceId, ContactRegistration body, System.Threading.CancellationToken cancellationToken) { + if (instanceId == null) + throw new System.ArgumentNullException("instanceId"); + var client_ = _httpClient; var disposeClient_ = false; try @@ -2354,8 +2361,9 @@ public virtual async System.Threading.Tasks.Task AddAcmeAccountAsy var urlBuilder_ = new System.Text.StringBuilder(); if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); - // Operation Path: "internal/v1/certificateauthority/account" - urlBuilder_.Append("internal/v1/certificateauthority/account"); + // Operation Path: "internal/v1/certificateauthority/account/{instanceId}" + urlBuilder_.Append("internal/v1/certificateauthority/account/"); + urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(instanceId, System.Globalization.CultureInfo.InvariantCulture))); PrepareRequest(client_, request_, urlBuilder_); diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/CertificateAuthorityController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/CertificateAuthorityController.cs index 42964187c..f6687833b 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/CertificateAuthorityController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/CertificateAuthorityController.cs @@ -1,4 +1,5 @@ using Certify.Client; +using Certify.Server.Api.Public.Services; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -16,16 +17,18 @@ public partial class CertificateAuthorityController : ApiControllerBase private readonly ILogger _logger; private readonly ICertifyInternalApiClient _client; + private readonly ManagementAPI _mgmtAPI; /// /// Constructor /// /// /// - public CertificateAuthorityController(ILogger logger, ICertifyInternalApiClient client) + public CertificateAuthorityController(ILogger logger, ICertifyInternalApiClient client, ManagementAPI mgmtApi) { _logger = logger; _client = client; + _mgmtAPI = mgmtApi; } /// diff --git a/src/Certify.Server/Certify.Server.Api.Public/Services/ManagementAPI.cs b/src/Certify.Server/Certify.Server.Api.Public/Services/ManagementAPI.cs index 3fd9a9de1..5d0462a2c 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Services/ManagementAPI.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Services/ManagementAPI.cs @@ -3,13 +3,14 @@ using Certify.Client; using Certify.Models; using Certify.Models.API; +using Certify.Models.Config; using Certify.Models.Reporting; using Certify.Server.Api.Public.SignalR.ManagementHub; using Microsoft.AspNetCore.SignalR; namespace Certify.Server.Api.Public.Services { - public class ManagementAPI + public partial class ManagementAPI { IInstanceManagementStateProvider _mgmtStateProvider; IHubContext _mgmtHubContext; @@ -163,6 +164,47 @@ public async Task GetManagedCertificateSummary(AuthContext? curre return await Task.FromResult(sum); } + public async Task?> GetAcmeAccounts(string instanceId, AuthContext? currentAuthContext) + { + var args = new KeyValuePair[] { + new("instanceId", instanceId) + }; + + var cmd = new InstanceCommandRequest(ManagementHubCommands.GetAcmeAccounts, args); + + var result = await GetCommandResult(instanceId, cmd); + + if (result?.Value != null) + { + return JsonSerializer.Deserialize>(result.Value); + } + else + { + return null; + } + } + + public async Task AddAcmeAccount(string instanceId, ContactRegistration registration, AuthContext? currentAuthContext) + { + var args = new KeyValuePair[] { + new("instanceId", instanceId) , + new("registration", JsonSerializer.Serialize(registration)) + }; + + var cmd = new InstanceCommandRequest(ManagementHubCommands.AddAcmeAccount, args); + + var result = await GetCommandResult(instanceId, cmd); + + if (result?.Value != null) + { + return JsonSerializer.Deserialize(result.Value); + } + else + { + return null; + } + } + public async Task GetItemLog(string instanceId, string managedCertId, int maxLines, AuthContext? currentAuthContext) { var args = new KeyValuePair[] { diff --git a/src/Certify.Server/Certify.Server.Api.Public/Startup.cs b/src/Certify.Server/Certify.Server.Api.Public/Startup.cs index 7553214d3..1504c467f 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Startup.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Startup.cs @@ -140,7 +140,7 @@ public void ConfigureServices(IServiceCollection services) }); - // connect to certify service + // connect to primary certify service var configManager = new ServiceConfigManager(); var serviceConfig = configManager.GetServiceConfig(); @@ -245,7 +245,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) } /// - /// Connect to status stream of backend service + /// Connect to status stream of primary service /// /// /// diff --git a/src/Certify.Shared/Utils/Util.cs b/src/Certify.Shared/Utils/Util.cs index c388e40d7..f8dd77318 100644 --- a/src/Certify.Shared/Utils/Util.cs +++ b/src/Certify.Shared/Utils/Util.cs @@ -176,7 +176,7 @@ public static void SetSupportedTLSVersions() public static string GetUserAgent() { var versionName = "Certify/" + GetAppVersion().ToString(); - return $"{versionName} (Windows; {Environment.OSVersion}) "; + return $"{versionName} ({RuntimeInformation.OSDescription}; {Environment.OSVersion}) "; } public static Version GetAppVersion() diff --git a/src/Certify.SourceGenerators/ApiMethods.cs b/src/Certify.SourceGenerators/ApiMethods.cs index 0ba2896f9..fb0d93abd 100644 --- a/src/Certify.SourceGenerators/ApiMethods.cs +++ b/src/Certify.SourceGenerators/ApiMethods.cs @@ -125,26 +125,28 @@ public static List GetApiDefinitions() ReturnType = "Models.Config.ActionResult", Params = new Dictionary{{"id","string"}} }, + /* per instance API, via management hub */ new GeneratedAPI { - OperationName = "GetAcmeAccounts", OperationMethod = "HttpGet", Comment = "Get All Acme Accounts", + UseManagementAPI = true, PublicAPIController = "CertificateAuthority", - PublicAPIRoute = "accounts", + PublicAPIRoute = "accounts/{instanceId}", ServiceAPIRoute = "accounts", - ReturnType = "ICollection" + ReturnType = "ICollection", + Params =new Dictionary{ { "instanceId", "string" } } }, new GeneratedAPI { - OperationName = "AddAcmeAccount", OperationMethod = "HttpPost", Comment = "Add New Acme Account", + UseManagementAPI = true, PublicAPIController = "CertificateAuthority", - PublicAPIRoute = "account", + PublicAPIRoute = "account/{instanceId}", ServiceAPIRoute = "accounts", ReturnType = "Models.Config.ActionResult", - Params =new Dictionary{{"registration", "Certify.Models.ContactRegistration" } } + Params =new Dictionary{ { "instanceId", "string" },{ "registration", "Certify.Models.ContactRegistration" } } }, new GeneratedAPI { @@ -157,18 +159,7 @@ public static List GetApiDefinitions() ReturnType = "Models.Config.ActionResult", Params =new Dictionary{{ "certificateAuthority", "Certify.Models.CertificateAuthority" } } }, - new GeneratedAPI { - OperationName = "RemoveManagedCertificate", - OperationMethod = "HttpDelete", - Comment = "Remove Managed Certificate", - PublicAPIController = "Certificate", - PublicAPIRoute = "settings/{instanceId}/{managedCertId}", - UseManagementAPI = true, - ServiceAPIRoute = "managedcertificates/delete/{managedCertId}", - ReturnType = "bool", - Params =new Dictionary{ { "instanceId", "string" },{ "managedCertId", "string" } } - }, new GeneratedAPI { OperationName = "RemoveCertificateAuthority", @@ -219,7 +210,19 @@ public static List GetApiDefinitions() ServiceAPIRoute = "system/migration/import", ReturnType = "ICollection", Params =new Dictionary{{ "importRequest", "Certify.Models.Config.Migration.ImportRequest" } } - } + }, + new GeneratedAPI { + + OperationName = "RemoveManagedCertificate", + OperationMethod = "HttpDelete", + Comment = "Remove Managed Certificate", + PublicAPIController = "Certificate", + PublicAPIRoute = "settings/{instanceId}/{managedCertId}", + UseManagementAPI = true, + ServiceAPIRoute = "managedcertificates/delete/{managedCertId}", + ReturnType = "bool", + Params =new Dictionary{ { "instanceId", "string" },{ "managedCertId", "string" } } + }, }; } } diff --git a/src/Certify.SourceGenerators/PublicAPISourceGenerator.cs b/src/Certify.SourceGenerators/PublicAPISourceGenerator.cs index 44ab3f3b3..e2e7dbf7e 100644 --- a/src/Certify.SourceGenerators/PublicAPISourceGenerator.cs +++ b/src/Certify.SourceGenerators/PublicAPISourceGenerator.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; @@ -122,6 +122,16 @@ public partial class CertifyApiClient if (config.OperationMethod == "HttpPost") { + var postAPIRoute = config.ServiceAPIRoute; + var postApiCall = apiParamCall; + var postApiParamDecl = apiParamDecl; + + if (config.UseManagementAPI) + { + postApiCall = apiParamCall.Replace("instanceId,", ""); + postApiParamDecl = apiParamDecl.Replace("string instanceId,", ""); + } + context.AddSource($"{config.PublicAPIController}.{config.OperationName}.ICertifyInternalApiClient.g.cs", SourceText.From($@" using Certify.Models; using Certify.Models.Config.AccessControl; @@ -136,7 +146,7 @@ public partial interface ICertifyInternalApiClient /// {config.Comment} [Generated by Certify.SourceGenerators] /// /// - Task<{config.ReturnType}> {config.OperationName}({apiParamDecl}); + Task<{config.ReturnType}> {config.OperationName}({postApiParamDecl}); }} @@ -147,9 +157,9 @@ public partial class CertifyApiClient /// {config.Comment} [Generated by Certify.SourceGenerators] /// /// - public async Task<{config.ReturnType}> {config.OperationName}({apiParamDecl}) + public async Task<{config.ReturnType}> {config.OperationName}({postApiParamDecl}) {{ - var result = await PostAsync($""{config.ServiceAPIRoute}"", {apiParamCall}); + var result = await PostAsync($""{postAPIRoute}"", {postApiCall}); return JsonToObject<{config.ReturnType}>(await result.Content.ReadAsStringAsync()); }} From 579dbbb420d69a713ba0ac867f0c7ee2d1836ca0 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Wed, 9 Oct 2024 13:30:37 +0800 Subject: [PATCH 236/328] Package updates Package Updates --- .../Certify.Aspire.ServiceDefaults.csproj | 2 +- src/Certify.Client/Certify.Client.csproj | 6 +++--- src/Certify.Models/Certify.Models.csproj | 4 ++-- .../DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj | 2 +- .../DNS/Azure/Plugin.DNS.Azure.csproj | 2 +- .../Certify.Server.Api.Public.Tests.csproj | 8 ++++---- .../Certify.Server.Api.Public.csproj | 6 +++--- .../Certify.Server.Core/Certify.Server.Core.csproj | 10 +++++----- src/Certify.Service/App.config | 2 +- src/Certify.Service/Certify.Service.csproj | 2 +- src/Certify.Shared/Certify.Shared.Core.csproj | 4 ++-- .../Certify.SourceGenerators.csproj | 1 + .../Certify.Core.Tests.Integration.csproj | 4 ++-- .../Certify.Core.Tests.Unit.csproj | 8 ++++---- .../Certify.Service.Tests.Integration.csproj | 4 ++-- .../Certify.UI.Tests.Integration.csproj | 4 ++-- src/Certify.UI.Shared/Certify.UI.Shared.csproj | 2 +- src/Certify.UI/App.config | 2 +- 18 files changed, 37 insertions(+), 36 deletions(-) diff --git a/src/Certify.Aspire/Certify.Aspire.ServiceDefaults/Certify.Aspire.ServiceDefaults.csproj b/src/Certify.Aspire/Certify.Aspire.ServiceDefaults/Certify.Aspire.ServiceDefaults.csproj index 0ce46c3a1..93819c71f 100644 --- a/src/Certify.Aspire/Certify.Aspire.ServiceDefaults/Certify.Aspire.ServiceDefaults.csproj +++ b/src/Certify.Aspire/Certify.Aspire.ServiceDefaults/Certify.Aspire.ServiceDefaults.csproj @@ -11,7 +11,7 @@ - + diff --git a/src/Certify.Client/Certify.Client.csproj b/src/Certify.Client/Certify.Client.csproj index 2ebe62f60..cf59f7061 100644 --- a/src/Certify.Client/Certify.Client.csproj +++ b/src/Certify.Client/Certify.Client.csproj @@ -16,9 +16,9 @@ - - - + + + diff --git a/src/Certify.Models/Certify.Models.csproj b/src/Certify.Models/Certify.Models.csproj index 251cbcc8c..197f014ad 100644 --- a/src/Certify.Models/Certify.Models.csproj +++ b/src/Certify.Models/Certify.Models.csproj @@ -21,7 +21,7 @@ all runtime; build; native; contentfiles; analyzers - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -30,7 +30,7 @@ all - + diff --git a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj index 1f56f0187..62b189e6a 100644 --- a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj +++ b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj b/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj index 2f701d329..605a86ab1 100644 --- a/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj +++ b/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj b/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj index c00f1687c..ed075a0ce 100644 --- a/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj +++ b/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj @@ -10,11 +10,11 @@ - - + + - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj index f4c06c237..c413dcf48 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj +++ b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj @@ -18,10 +18,10 @@ - - + + - + diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj index e02c65f83..514cc3070 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj @@ -11,12 +11,12 @@ - + - - - - + + + + diff --git a/src/Certify.Service/App.config b/src/Certify.Service/App.config index ffa3db271..467e9f0ba 100644 --- a/src/Certify.Service/App.config +++ b/src/Certify.Service/App.config @@ -14,7 +14,7 @@ - + diff --git a/src/Certify.Service/Certify.Service.csproj b/src/Certify.Service/Certify.Service.csproj index 7f942e6fa..9b9391816 100644 --- a/src/Certify.Service/Certify.Service.csproj +++ b/src/Certify.Service/Certify.Service.csproj @@ -50,7 +50,7 @@ - + diff --git a/src/Certify.Shared/Certify.Shared.Core.csproj b/src/Certify.Shared/Certify.Shared.Core.csproj index ad5121fa6..2ec67bbe0 100644 --- a/src/Certify.Shared/Certify.Shared.Core.csproj +++ b/src/Certify.Shared/Certify.Shared.Core.csproj @@ -6,8 +6,8 @@ - - + + diff --git a/src/Certify.SourceGenerators/Certify.SourceGenerators.csproj b/src/Certify.SourceGenerators/Certify.SourceGenerators.csproj index b3ef958b5..3b3302ea8 100644 --- a/src/Certify.SourceGenerators/Certify.SourceGenerators.csproj +++ b/src/Certify.SourceGenerators/Certify.SourceGenerators.csproj @@ -3,6 +3,7 @@ netstandard2.0 true + latest diff --git a/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj b/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj index 60ca22006..4f4015d9f 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj @@ -76,8 +76,8 @@ - - + + diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj index 977c3949d..15dec1d7b 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj @@ -104,17 +104,17 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + - - + + - + diff --git a/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj b/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj index deda2939c..312e3915a 100644 --- a/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj @@ -62,8 +62,8 @@ - - + + diff --git a/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj b/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj index 8a77486a2..a937fe7b2 100644 --- a/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj @@ -58,8 +58,8 @@ - - + + diff --git a/src/Certify.UI.Shared/Certify.UI.Shared.csproj b/src/Certify.UI.Shared/Certify.UI.Shared.csproj index db8575a0f..521c23eb7 100644 --- a/src/Certify.UI.Shared/Certify.UI.Shared.csproj +++ b/src/Certify.UI.Shared/Certify.UI.Shared.csproj @@ -42,7 +42,7 @@ - + NU1701 diff --git a/src/Certify.UI/App.config b/src/Certify.UI/App.config index 6b8d63af8..919459795 100644 --- a/src/Certify.UI/App.config +++ b/src/Certify.UI/App.config @@ -8,7 +8,7 @@ - + From b302e8e33a6cefee99e5d394048fe86c29453bd4 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Mon, 14 Oct 2024 10:19:59 +0800 Subject: [PATCH 237/328] Package Updates --- .../DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj | 2 +- src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj | 2 +- src/Certify.Service/APIHost.cs | 3 +-- src/Certify.Service/App.config | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj index 62b189e6a..5bd506be2 100644 --- a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj +++ b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj b/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj index 605a86ab1..1ba420553 100644 --- a/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj +++ b/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/Certify.Service/APIHost.cs b/src/Certify.Service/APIHost.cs index d20a47e96..d43b67f78 100644 --- a/src/Certify.Service/APIHost.cs +++ b/src/Certify.Service/APIHost.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; using System.Net; using System.Net.Http; using System.Net.Http.Formatting; @@ -81,7 +81,6 @@ public void Configuration(IAppBuilder appBuilder) // inject single CertifyManager for service to use _container.Register(new PerContainerLifetime()); - var currentCertifyManager = _container.GetInstance(); var sw = Stopwatch.StartNew(); diff --git a/src/Certify.Service/App.config b/src/Certify.Service/App.config index 467e9f0ba..5c0610812 100644 --- a/src/Certify.Service/App.config +++ b/src/Certify.Service/App.config @@ -14,7 +14,7 @@ - + From e497c226d68a935a277a7046790065e16369bb75 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Wed, 16 Oct 2024 12:56:00 +0800 Subject: [PATCH 238/328] Tasks: implement task failed trigger Run task if any preceding task failed --- .../Management/CertifyManager/CertifyManager.DeploymentTasks.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.DeploymentTasks.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.DeploymentTasks.cs index 6ade7a97f..b276df72c 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.DeploymentTasks.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.DeploymentTasks.cs @@ -252,7 +252,7 @@ private async Task> PerformTaskList(ILog log, bool isPreviewOnl task.TaskConfig.DateLastExecuted = DateTimeOffset.UtcNow; wasTaskExecuted = true; - taskResults = await task.Execute(log, _credentialsManager, result, cancellationToken: CancellationToken.None, new DeploymentContext { PowershellExecutionPolicy = _serverConfig.PowershellExecutionPolicy }, isPreviewOnly: isPreviewOnly); + taskResults = await task.Execute(log, _credentialsManager, result, new DeploymentContext { PowershellExecutionPolicy = _serverConfig.PowershellExecutionPolicy }, isPreviewOnly: isPreviewOnly, cancellationToken: CancellationToken.None); if (!isPreviewOnly) { From 42039c78a4faa4b50a269bc6bd4b5d264082ecf5 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Wed, 16 Oct 2024 13:04:13 +0800 Subject: [PATCH 239/328] Tests: cleanup --- .../CertRequestTests.cs | 6 ++--- .../CertificateStoreCleanup.cs | 8 +++---- .../CertifyManagerServerTypeTests.cs | 4 ++-- .../DeploymentPreviewTests.cs | 8 +++---- .../IntegrationTestBase.cs | 23 +++++++++++-------- .../ServerManagers/IISManagerTests.cs | 20 ++++++++-------- 6 files changed, 37 insertions(+), 32 deletions(-) diff --git a/src/Certify.Tests/Certify.Core.Tests.Integration/CertRequestTests.cs b/src/Certify.Tests/Certify.Core.Tests.Integration/CertRequestTests.cs index facd4422e..6fc3ef24a 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Integration/CertRequestTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Integration/CertRequestTests.cs @@ -44,7 +44,7 @@ public CertRequestTests() PrimaryTestDomain = ConfigSettings["Cloudflare_TestDomain"]; testSiteDomain = "integration1." + PrimaryTestDomain; - testSitePath = _primaryWebRoot; + testSitePath = PrimaryWebRootPath; _testCredStorageKey = ConfigSettings["TestCredentialsKey_Cloudflare"]; @@ -69,7 +69,7 @@ public async Task SetupIIS() await iisManager.DeleteSite(testSiteName); } - var site = await iisManager.CreateSite(testSiteName, testSiteDomain, _primaryWebRoot, "DefaultAppPool", port: testSiteHttpPort); + var site = await iisManager.CreateSite(testSiteName, testSiteDomain, PrimaryWebRootPath, "DefaultAppPool", port: testSiteHttpPort); Assert.IsTrue(await iisManager.SiteExists(testSiteName)); _siteId = site.Id.ToString(); } @@ -527,7 +527,7 @@ public async Task TestChallengeRequestDNSWildcard() await iisManager.DeleteSite(testWildcardSiteName); } - var site = await iisManager.CreateSite(testWildcardSiteName, "test" + testStr + "." + PrimaryTestDomain, _primaryWebRoot, "DefaultAppPool", port: testSiteHttpPort); + var site = await iisManager.CreateSite(testWildcardSiteName, "test" + testStr + "." + PrimaryTestDomain, PrimaryWebRootPath, "DefaultAppPool", port: testSiteHttpPort); ManagedCertificate managedCertificate = null; X509Certificate2 certInfo = null; diff --git a/src/Certify.Tests/Certify.Core.Tests.Integration/CertificateStoreCleanup.cs b/src/Certify.Tests/Certify.Core.Tests.Integration/CertificateStoreCleanup.cs index 5cf46006b..4f359b428 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Integration/CertificateStoreCleanup.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Integration/CertificateStoreCleanup.cs @@ -29,7 +29,7 @@ public async Task TestCertCleanupAtExpiry() await iisManager.DeleteSite(testSiteDomain); } - var site = await iisManager.CreateSite(testSiteDomain, testSiteDomain, _primaryWebRoot, "DefaultAppPool"); + var site = await iisManager.CreateSite(testSiteDomain, testSiteDomain, PrimaryWebRootPath, "DefaultAppPool"); await iisManager.AddOrUpdateSiteBinding( new Models.BindingInfo @@ -92,7 +92,7 @@ public async Task TestCertCleanupByThumbprint() await iisManager.DeleteSite(testSiteDomain); } - var site = await iisManager.CreateSite(testSiteDomain, testSiteDomain, _primaryWebRoot, "DefaultAppPool"); + var site = await iisManager.CreateSite(testSiteDomain, testSiteDomain, PrimaryWebRootPath, "DefaultAppPool"); await iisManager.AddOrUpdateSiteBinding( new Models.BindingInfo @@ -156,7 +156,7 @@ public async Task TestCertCleanupFull() await iisManager.DeleteSite(testSiteDomain); } - var site = await iisManager.CreateSite(testSiteDomain, testSiteDomain, _primaryWebRoot, "DefaultAppPool"); + var site = await iisManager.CreateSite(testSiteDomain, testSiteDomain, PrimaryWebRootPath, "DefaultAppPool"); await iisManager.AddOrUpdateSiteBinding( new Models.BindingInfo @@ -223,7 +223,7 @@ public async Task TestCertCleanupAfterRenewal() await iisManager.DeleteSite(testSiteDomain); } - var site = await iisManager.CreateSite(testSiteDomain, testSiteDomain, _primaryWebRoot, "DefaultAppPool"); + var site = await iisManager.CreateSite(testSiteDomain, testSiteDomain, PrimaryWebRootPath, "DefaultAppPool"); await iisManager.AddOrUpdateSiteBinding( new Models.BindingInfo diff --git a/src/Certify.Tests/Certify.Core.Tests.Integration/CertifyManagerServerTypeTests.cs b/src/Certify.Tests/Certify.Core.Tests.Integration/CertifyManagerServerTypeTests.cs index 8fcc9840e..08f4fd600 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Integration/CertifyManagerServerTypeTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Integration/CertifyManagerServerTypeTests.cs @@ -37,7 +37,7 @@ public async Task SetupIIS() await _iisManager.DeleteSite(_testSiteName); } - var site = await _iisManager.CreateSite(_testSiteName, _testSiteDomain, _primaryWebRoot, "DefaultAppPool", ipAddress: _testSiteIp, port: _testSiteHttpPort); + var site = await _iisManager.CreateSite(_testSiteName, _testSiteDomain, PrimaryWebRootPath, "DefaultAppPool", ipAddress: _testSiteIp, port: _testSiteHttpPort); Assert.IsTrue(await _iisManager.SiteExists(_testSiteName)); _testSiteId = site.Id.ToString(); } @@ -158,7 +158,7 @@ public async Task TestCertifyManagerGetDomainOptionsFromSiteNoDomain() } // Add no domain site - var noDomainSite = await _iisManager.CreateSite(noDomainSiteName, "", _primaryWebRoot, "DefaultAppPool", port: 81); + var noDomainSite = await _iisManager.CreateSite(noDomainSiteName, "", PrimaryWebRootPath, "DefaultAppPool", port: 81); Assert.IsTrue(await _iisManager.SiteExists(_testSiteName), "Expected no domain site to be created"); var noDomainSiteId = noDomainSite.Id.ToString(); diff --git a/src/Certify.Tests/Certify.Core.Tests.Integration/DeploymentPreviewTests.cs b/src/Certify.Tests/Certify.Core.Tests.Integration/DeploymentPreviewTests.cs index 00750073a..504be11c8 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Integration/DeploymentPreviewTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Integration/DeploymentPreviewTests.cs @@ -35,7 +35,7 @@ public DeploymentPreviewTests() PrimaryTestDomain = ConfigSettings["AWS_TestDomain"]; testSiteDomain = "integration1." + PrimaryTestDomain; - testSitePath = _primaryWebRoot; + testSitePath = PrimaryWebRootPath; _awsCredStorageKey = ConfigSettings["TestCredentialsKey_Route53"]; @@ -60,7 +60,7 @@ public async Task SetupIIS() await iisManager.DeleteSite(testSiteName); } - var site = await iisManager.CreateSite(testSiteName, testSiteDomain, _primaryWebRoot, "DefaultAppPool", port: testSiteHttpPort); + var site = await iisManager.CreateSite(testSiteName, testSiteDomain, PrimaryWebRootPath, "DefaultAppPool", port: testSiteHttpPort); Assert.IsTrue(await iisManager.SiteExists(testSiteName)); _siteId = site.Id.ToString(); } @@ -85,7 +85,7 @@ public async Task TestPreviewWildcard() await iisManager.DeleteSite(testPreviewSiteName); } - var site = await iisManager.CreateSite(testPreviewSiteName, hostname, _primaryWebRoot, "DefaultAppPool", port: testSiteHttpPort); + var site = await iisManager.CreateSite(testPreviewSiteName, hostname, PrimaryWebRootPath, "DefaultAppPool", port: testSiteHttpPort); ManagedCertificate managedCertificate = null; X509Certificate2 certInfo = null; @@ -166,7 +166,7 @@ public async Task TestPreviewStaticIPBindings() var ipAddress = GetTestStaticIP(); - var site = await iisManager.CreateSite(testPreviewSiteName, hostname, _primaryWebRoot, "DefaultAppPool", "http", ipAddress, testSiteHttpPort); + var site = await iisManager.CreateSite(testPreviewSiteName, hostname, PrimaryWebRootPath, "DefaultAppPool", "http", ipAddress, testSiteHttpPort); ManagedCertificate managedCertificate = null; X509Certificate2 certInfo = null; diff --git a/src/Certify.Tests/Certify.Core.Tests.Integration/IntegrationTestBase.cs b/src/Certify.Tests/Certify.Core.Tests.Integration/IntegrationTestBase.cs index 3234d9ae9..fbe5e29ff 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Integration/IntegrationTestBase.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Integration/IntegrationTestBase.cs @@ -11,9 +11,13 @@ namespace Certify.Core.Tests { public class IntegrationTestBase { - public string PrimaryTestDomain = "test.certifytheweb.com"; // TODO: get this from debug config as it changes per dev machine - public string _primaryWebRoot = @"c:\inetpub\wwwroot\"; public Dictionary ConfigSettings = new Dictionary(); + + public string PrimaryTestDomain = "test.certifytheweb.com"; // TODO: get this from debug config as it changes per dev machine + public string PrimaryWebRootPath = @"c:\inetpub\wwwroot\"; + + private string _testConfigPath = @"c:\temp\Certify\TestConfigSettings.json"; + internal ILog _log; public IntegrationTestBase() @@ -23,13 +27,14 @@ public IntegrationTestBase() PrimaryTestDomain = Environment.GetEnvironmentVariable("CERTIFY_TESTDOMAIN"); } - /* ConfigSettings.Add("AWS_ZoneId", "example"); - ConfigSettings.Add("Azure_ZoneId", "example"); - ConfigSettings.Add("Cloudflare_ZoneId", "example"); - System.IO.File.WriteAllText("C:\\temp\\TestConfigSettings.json", JsonConvert.SerializeObject(ConfigSettings)); - */ - - ConfigSettings = JsonConvert.DeserializeObject>(System.IO.File.ReadAllText("C:\\temp\\Certify\\TestConfigSettings.json")); + if (File.Exists("C:\\temp\\Certify\\TestConfigSettings.json")) + { + ConfigSettings = JsonConvert.DeserializeObject>(System.IO.File.ReadAllText(_testConfigPath)); + } + else + { + System.Diagnostics.Debug.WriteLine("Test config file not found: " + _testConfigPath); + } _log = new Loggy(LoggerFactory.Create(builder => builder.AddDebug()).CreateLogger()); } diff --git a/src/Certify.Tests/Certify.Core.Tests.Integration/ServerManagers/IISManagerTests.cs b/src/Certify.Tests/Certify.Core.Tests.Integration/ServerManagers/IISManagerTests.cs index 6dc42066b..57fa02f6b 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Integration/ServerManagers/IISManagerTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Integration/ServerManagers/IISManagerTests.cs @@ -51,7 +51,7 @@ public async Task SetupIIS() await iisManager.DeleteSite(testSiteName); } - var site = await iisManager.CreateSite(testSiteName, testSiteDomain, _primaryWebRoot, "DefaultAppPool"); + var site = await iisManager.CreateSite(testSiteName, testSiteDomain, PrimaryWebRootPath, "DefaultAppPool"); _siteId = site.Id.ToString(); Assert.IsTrue(await iisManager.SiteExists(testSiteName)); } @@ -111,7 +111,7 @@ public async Task TestCreateUnusualBindings() try { // create net.msmq://localhost binding, no port or ip - await iisManager.CreateSite(siteName, "localhost", _primaryWebRoot, null, protocol: "net.msmq", ipAddress: null, port: null); + await iisManager.CreateSite(siteName, "localhost", PrimaryWebRootPath, null, protocol: "net.msmq", ipAddress: null, port: null); var sites = iisManager.GetSiteBindingList(false); } @@ -134,7 +134,7 @@ public async Task TestCreateFixedIPBindings() try { var ipAddress = Dns.GetHostEntry(Dns.GetHostName()).AddressList[0].ToString(); - var site = await iisManager.CreateSite(testName, testDomainName, _primaryWebRoot, "DefaultAppPool", "http", ipAddress); + var site = await iisManager.CreateSite(testName, testDomainName, PrimaryWebRootPath, "DefaultAppPool", "http", ipAddress); Assert.IsTrue(await iisManager.SiteExists(testSiteName)); @@ -165,7 +165,7 @@ public async Task TestManySiteBindingUpdates() await iisManager.DeleteSite(testSiteName); } - await iisManager.CreateSite(testSiteName, "site_" + i + "_toomany.com", _primaryWebRoot, null, protocol: "http"); + await iisManager.CreateSite(testSiteName, "site_" + i + "_toomany.com", PrimaryWebRootPath, null, protocol: "http"); var site = await iisManager.GetSiteBindingByDomain(domain); for (var d = 0; d < 2; d++) { @@ -175,7 +175,7 @@ public async Task TestManySiteBindingUpdates() { SiteId = site.SiteId, Host = testDomain, - PhysicalPath = _primaryWebRoot + PhysicalPath = PrimaryWebRootPath }, addNew: true)); } } @@ -200,7 +200,7 @@ public async Task TestManySiteBindingUpdates() { SiteId = site.SiteId, Host = testDomain, - PhysicalPath = _primaryWebRoot + PhysicalPath = PrimaryWebRootPath }, addNew: true)); } else @@ -209,7 +209,7 @@ public async Task TestManySiteBindingUpdates() { SiteId = site.SiteId, Host = testDomain, - PhysicalPath = _primaryWebRoot + PhysicalPath = PrimaryWebRootPath }, addNew: true)); } } @@ -261,7 +261,7 @@ public async Task TestTooManyBindings() try { // create net.msmq://localhost binding, no port or ip - await iisManager.CreateSite("ManyBindings", "toomany.com", _primaryWebRoot, null, protocol: "http"); + await iisManager.CreateSite("ManyBindings", "toomany.com", PrimaryWebRootPath, null, protocol: "http"); var site = await iisManager.GetSiteBindingByDomain("toomany.com"); var domains = new List(); for (var i = 0; i < 101; i++) @@ -287,7 +287,7 @@ public async Task TestLongBinding() await iisManager.DeleteSite(testName); } - var site = await iisManager.CreateSite(testName, testDomainName, _primaryWebRoot, null); + var site = await iisManager.CreateSite(testName, testDomainName, PrimaryWebRootPath, null); try { @@ -344,7 +344,7 @@ public async Task TestBindingMatch() } // create site with IP all unassigned, no hostname - var site = await iisManager.CreateSite(testBindingSiteName, "", _primaryWebRoot, "DefaultAppPool", port: testSiteHttpPort); + var site = await iisManager.CreateSite(testBindingSiteName, "", PrimaryWebRootPath, "DefaultAppPool", port: testSiteHttpPort); // add another hostname binding (matching cert and not matching cert) var testDomains = new List { testSiteDomain, "label1." + testSiteDomain, "nested.label." + testSiteDomain }; From 464f16d548b0b1d223d4bb31ba1713eabde264e6 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Thu, 24 Oct 2024 11:23:29 +0800 Subject: [PATCH 240/328] Tidy up linux os name check --- src/Certify.Models/Util/EnvironmentUtil.cs | 25 ++++++++-------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/src/Certify.Models/Util/EnvironmentUtil.cs b/src/Certify.Models/Util/EnvironmentUtil.cs index 41217f3f2..4ac127970 100644 --- a/src/Certify.Models/Util/EnvironmentUtil.cs +++ b/src/Certify.Models/Util/EnvironmentUtil.cs @@ -123,27 +123,20 @@ public static string GetFriendlyOSName() } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { - var osName = string.Empty; - - string filePath = "/etc/os-release"; + var filePath = "/etc/os-release"; try { - using (FileStream fileStream = new FileStream(filePath, FileMode.Open)) + using var fileStream = new FileStream(filePath, FileMode.Open); + using var reader = new StreamReader(fileStream); + string? line; + + while ((line = reader.ReadLine()) != null) { - using (StreamReader reader = new StreamReader(fileStream)) + if (line.StartsWith("NAME", StringComparison.InvariantCultureIgnoreCase)) { - string line; - - while ((line = reader.ReadLine()) != null) - { - - if (line.StartsWith("NAME")) - { - osName = line.Split('\"')[1]; //split the line string by " and get the second slice - return osName; - } - } + var osName = line.Split('\"')[1]; + return osName; } } } From 5d9cf9db596ba19069405451b250d61e20c2d5eb Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Thu, 24 Oct 2024 13:39:38 +0800 Subject: [PATCH 241/328] Package updates --- .../DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj | 2 +- .../DNS/Azure/Plugin.DNS.Azure.csproj | 2 +- .../Certify.Server.Core/Certify.Server.Core.csproj | 2 +- src/Certify.Service/Certify.Service.csproj | 2 +- .../Certify.Shared.Extensions.csproj | 2 +- .../Certify.SourceGenerators.csproj | 10 +++++++++- 6 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj index 5bd506be2..974736712 100644 --- a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj +++ b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj b/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj index 1ba420553..86e2093e3 100644 --- a/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj +++ b/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj index 514cc3070..002d1f8f0 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj @@ -13,7 +13,7 @@ - + diff --git a/src/Certify.Service/Certify.Service.csproj b/src/Certify.Service/Certify.Service.csproj index 9b9391816..011729e81 100644 --- a/src/Certify.Service/Certify.Service.csproj +++ b/src/Certify.Service/Certify.Service.csproj @@ -37,7 +37,7 @@ - + diff --git a/src/Certify.Shared.Extensions/Certify.Shared.Extensions.csproj b/src/Certify.Shared.Extensions/Certify.Shared.Extensions.csproj index f55b256c6..3729bf4a9 100644 --- a/src/Certify.Shared.Extensions/Certify.Shared.Extensions.csproj +++ b/src/Certify.Shared.Extensions/Certify.Shared.Extensions.csproj @@ -23,7 +23,7 @@ - + diff --git a/src/Certify.SourceGenerators/Certify.SourceGenerators.csproj b/src/Certify.SourceGenerators/Certify.SourceGenerators.csproj index 3b3302ea8..351012dee 100644 --- a/src/Certify.SourceGenerators/Certify.SourceGenerators.csproj +++ b/src/Certify.SourceGenerators/Certify.SourceGenerators.csproj @@ -5,9 +5,17 @@ true latest + + + 1701;1702;RS1035 + + + + 1701;1702;RS1035 + - + From e7d26346c771189d8af1ea6088e500e96d2cd0b4 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Tue, 29 Oct 2024 11:01:27 +0800 Subject: [PATCH 242/328] Package Updates --- .../Certify.Aspire.AppHost/Certify.Aspire.AppHost.csproj | 2 +- .../Certify.Aspire.ServiceDefaults.csproj | 2 +- .../DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj | 2 +- src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Certify.Aspire/Certify.Aspire.AppHost/Certify.Aspire.AppHost.csproj b/src/Certify.Aspire/Certify.Aspire.AppHost/Certify.Aspire.AppHost.csproj index eee4af4ac..5ddd04426 100644 --- a/src/Certify.Aspire/Certify.Aspire.AppHost/Certify.Aspire.AppHost.csproj +++ b/src/Certify.Aspire/Certify.Aspire.AppHost/Certify.Aspire.AppHost.csproj @@ -9,7 +9,7 @@ - + diff --git a/src/Certify.Aspire/Certify.Aspire.ServiceDefaults/Certify.Aspire.ServiceDefaults.csproj b/src/Certify.Aspire/Certify.Aspire.ServiceDefaults/Certify.Aspire.ServiceDefaults.csproj index 93819c71f..268956d7d 100644 --- a/src/Certify.Aspire/Certify.Aspire.ServiceDefaults/Certify.Aspire.ServiceDefaults.csproj +++ b/src/Certify.Aspire/Certify.Aspire.ServiceDefaults/Certify.Aspire.ServiceDefaults.csproj @@ -12,7 +12,7 @@ - + diff --git a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj index 974736712..241d7a789 100644 --- a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj +++ b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj b/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj index 86e2093e3..3c56bc20d 100644 --- a/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj +++ b/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj @@ -8,7 +8,7 @@ - + From e7ad9d845b83ac85bde191d879060ae7b8fcd160 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Tue, 29 Oct 2024 19:44:42 +0800 Subject: [PATCH 243/328] Cleanup --- src/Certify.Models/Config/ManagedCertificate.cs | 2 +- .../Shared/Validation/CertificateEditorService.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Certify.Models/Config/ManagedCertificate.cs b/src/Certify.Models/Config/ManagedCertificate.cs index 97f68dfc7..492f29e84 100644 --- a/src/Certify.Models/Config/ManagedCertificate.cs +++ b/src/Certify.Models/Config/ManagedCertificate.cs @@ -493,7 +493,7 @@ public CertRequestChallengeConfig GetChallengeConfig(CertIdentifierItem identifi { c.DomainMatch = c.DomainMatch.Replace(",", ";"); // if user has entered comma seperators instead of semicolons, convert now. - if (!c.DomainMatch.Contains(";")) + if (!c.DomainMatch.Contains(';')) { var domainMatchKey = c.DomainMatch.Trim(); diff --git a/src/Certify.Models/Shared/Validation/CertificateEditorService.cs b/src/Certify.Models/Shared/Validation/CertificateEditorService.cs index 7c5c1df70..9a33933b2 100644 --- a/src/Certify.Models/Shared/Validation/CertificateEditorService.cs +++ b/src/Certify.Models/Shared/Validation/CertificateEditorService.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Certify.Locales; @@ -318,7 +318,7 @@ public static ValidationResult Validate(ManagedCertificate item, SiteInfo? selec if (!(preferredCA != null && preferredCA.AllowInternalHostnames)) { // validate hostnames - if (item.DomainOptions?.Any(d => d.IsSelected && d.Type == "dns" && d.Domain != null && (!d.Domain.Contains(".") || d.Domain.ToLowerInvariant().EndsWith(".local", StringComparison.InvariantCultureIgnoreCase))) == true) + if (item.DomainOptions?.Any(d => d.IsSelected && d.Type == "dns" && d.Domain != null && (!d.Domain.Contains('.') || d.Domain.ToLowerInvariant().EndsWith(".local", StringComparison.InvariantCultureIgnoreCase))) == true) { // one or more selected domains does not include a label separator (is an internal host name) or end in .local From d5252eea9720a5b0e2effeeb71baf7ad832a0e98 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Tue, 29 Oct 2024 19:46:39 +0800 Subject: [PATCH 244/328] Hub: implement per instance stored credentials --- .../CertifyManager.ManagementHub.cs | 21 ++++- .../API/Management/ManagementHubMessages.cs | 5 ++ .../Certify.API.Public.cs | 63 +++++++++------ .../internal/StoredCredentialController.cs | 41 ++-------- .../Services/ManagementAPI.cs | 77 ++++++++++++++++++- src/Certify.SourceGenerators/ApiMethods.cs | 29 ++++++- 6 files changed, 171 insertions(+), 65 deletions(-) diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs index 01e8a8c47..f7e1fa0ba 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs @@ -6,6 +6,7 @@ using Certify.API.Management; using Certify.Client; using Certify.Models; +using Certify.Models.Config; using Certify.Shared.Core.Utils; namespace Certify.Management @@ -168,13 +169,31 @@ private async Task _managementServerClient_OnGetCommandRe } else if (arg.CommandType == ManagementHubCommands.AddAcmeAccount) { - var args = JsonSerializer.Deserialize[]>(arg.Value); var registrationArg = args.FirstOrDefault(a => a.Key == "registration"); var registration = JsonSerializer.Deserialize(registrationArg.Value); val = await AddAccount(registration); } + else if (arg.CommandType == ManagementHubCommands.GetStoredCredentials) + { + val = await _credentialsManager.GetCredentials(); + } + else if (arg.CommandType == ManagementHubCommands.UpdateStoredCredential) + { + var args = JsonSerializer.Deserialize[]>(arg.Value); + var itemArg = args.FirstOrDefault(a => a.Key == "item"); + var storedCredential = JsonSerializer.Deserialize(itemArg.Value); + + val = await _credentialsManager.Update(storedCredential); + } + else if (arg.CommandType == ManagementHubCommands.DeleteStoredCredential) + { + var args = JsonSerializer.Deserialize[]>(arg.Value); + var itemArg = args.FirstOrDefault(a => a.Key == "storageKey"); + var storedCredential = JsonSerializer.Deserialize(itemArg.Value); + val = await _credentialsManager.Delete(_itemManager, itemArg.Value); + } else if (arg.CommandType == ManagementHubCommands.Reconnect) { await _managementServerClient.Disconnect(); diff --git a/src/Certify.Models/API/Management/ManagementHubMessages.cs b/src/Certify.Models/API/Management/ManagementHubMessages.cs index aa05b8106..7b00ef97b 100644 --- a/src/Certify.Models/API/Management/ManagementHubMessages.cs +++ b/src/Certify.Models/API/Management/ManagementHubMessages.cs @@ -26,6 +26,11 @@ public class ManagementHubCommands public const string GetAcmeAccounts = "GetAcmeAccounts"; public const string AddAcmeAccount = "AddAcmeAccount"; + + public const string GetStoredCredentials = "GetStoredCredentials"; + public const string UpdateStoredCredential = "UpdateStoredCredential"; + public const string DeleteStoredCredential = "DeleteStoredCredential"; + public const string Reconnect = "Reconnect"; } diff --git a/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs b/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs index 5fb22bcbe..f74d1eca5 100644 --- a/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs +++ b/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs @@ -2953,9 +2953,9 @@ public virtual async System.Threading.Tasks.Task RemoveAcmeAccount /// /// OK /// A server side error occurred. - public virtual System.Threading.Tasks.Task GetHubManagedItemsAsync(string keyword, int? page, int? pageSize) + public virtual System.Threading.Tasks.Task GetHubManagedItemsAsync(string instanceId, string keyword, int? page, int? pageSize) { - return GetHubManagedItemsAsync(keyword, page, pageSize, System.Threading.CancellationToken.None); + return GetHubManagedItemsAsync(instanceId, keyword, page, pageSize, System.Threading.CancellationToken.None); } /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. @@ -2964,7 +2964,7 @@ public virtual System.Threading.Tasks.Task GetH /// /// OK /// A server side error occurred. - public virtual async System.Threading.Tasks.Task GetHubManagedItemsAsync(string keyword, int? page, int? pageSize, System.Threading.CancellationToken cancellationToken) + public virtual async System.Threading.Tasks.Task GetHubManagedItemsAsync(string instanceId, string keyword, int? page, int? pageSize, System.Threading.CancellationToken cancellationToken) { var client_ = _httpClient; var disposeClient_ = false; @@ -2980,6 +2980,10 @@ public virtual async System.Threading.Tasks.Task - /// Get List of stored credentials + /// Get List of Stored Credentials [Generated by Certify.SourceGenerators] /// /// OK /// A server side error occurred. - public virtual System.Threading.Tasks.Task> GetStoredCredentialsAsync() + public virtual System.Threading.Tasks.Task> GetStoredCredentialsAsync(string instanceId) { - return GetStoredCredentialsAsync(System.Threading.CancellationToken.None); + return GetStoredCredentialsAsync(instanceId, System.Threading.CancellationToken.None); } /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Get List of stored credentials + /// Get List of Stored Credentials [Generated by Certify.SourceGenerators] /// /// OK /// A server side error occurred. - public virtual async System.Threading.Tasks.Task> GetStoredCredentialsAsync(System.Threading.CancellationToken cancellationToken) + public virtual async System.Threading.Tasks.Task> GetStoredCredentialsAsync(string instanceId, System.Threading.CancellationToken cancellationToken) { + if (instanceId == null) + throw new System.ArgumentNullException("instanceId"); + var client_ = _httpClient; var disposeClient_ = false; try @@ -3319,8 +3326,9 @@ public virtual async System.Threading.Tasks.Task - /// Add/Update a stored credential + /// Add/Update Stored Credential [Generated by Certify.SourceGenerators] /// /// OK /// A server side error occurred. - public virtual System.Threading.Tasks.Task UpdateStoredCredentialAsync(StoredCredential body) + public virtual System.Threading.Tasks.Task UpdateStoredCredentialAsync(string instanceId, StoredCredential body) { - return UpdateStoredCredentialAsync(body, System.Threading.CancellationToken.None); + return UpdateStoredCredentialAsync(instanceId, body, System.Threading.CancellationToken.None); } /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Add/Update a stored credential + /// Add/Update Stored Credential [Generated by Certify.SourceGenerators] /// /// OK /// A server side error occurred. - public virtual async System.Threading.Tasks.Task UpdateStoredCredentialAsync(StoredCredential body, System.Threading.CancellationToken cancellationToken) + public virtual async System.Threading.Tasks.Task UpdateStoredCredentialAsync(string instanceId, StoredCredential body, System.Threading.CancellationToken cancellationToken) { + if (instanceId == null) + throw new System.ArgumentNullException("instanceId"); + var client_ = _httpClient; var disposeClient_ = false; try @@ -3407,8 +3418,9 @@ public virtual async System.Threading.Tasks.Task UpdateStoredC var urlBuilder_ = new System.Text.StringBuilder(); if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); - // Operation Path: "internal/v1/storedcredential" - urlBuilder_.Append("internal/v1/storedcredential"); + // Operation Path: "internal/v1/storedcredential/credentials/{instanceId}" + urlBuilder_.Append("internal/v1/storedcredential/credentials/"); + urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(instanceId, System.Globalization.CultureInfo.InvariantCulture))); PrepareRequest(client_, request_, urlBuilder_); @@ -3435,7 +3447,7 @@ public virtual async System.Threading.Tasks.Task UpdateStoredC var status_ = (int)response_.StatusCode; if (status_ == 200) { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); @@ -3467,9 +3479,9 @@ public virtual async System.Threading.Tasks.Task UpdateStoredC /// /// OK /// A server side error occurred. - public virtual System.Threading.Tasks.Task RemoveStoredCredentialAsync(string storageKey) + public virtual System.Threading.Tasks.Task DeleteStoredCredentialAsync(string instanceId, string storageKey) { - return RemoveStoredCredentialAsync(storageKey, System.Threading.CancellationToken.None); + return DeleteStoredCredentialAsync(instanceId, storageKey, System.Threading.CancellationToken.None); } /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. @@ -3478,8 +3490,11 @@ public virtual System.Threading.Tasks.Task RemoveStoredCredentialA /// /// OK /// A server side error occurred. - public virtual async System.Threading.Tasks.Task RemoveStoredCredentialAsync(string storageKey, System.Threading.CancellationToken cancellationToken) + public virtual async System.Threading.Tasks.Task DeleteStoredCredentialAsync(string instanceId, string storageKey, System.Threading.CancellationToken cancellationToken) { + if (instanceId == null) + throw new System.ArgumentNullException("instanceId"); + if (storageKey == null) throw new System.ArgumentNullException("storageKey"); @@ -3494,8 +3509,10 @@ public virtual async System.Threading.Tasks.Task RemoveStoredCrede var urlBuilder_ = new System.Text.StringBuilder(); if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); - // Operation Path: "internal/v1/storedcredential/storedcredential/{storageKey}" - urlBuilder_.Append("internal/v1/storedcredential/storedcredential/"); + // Operation Path: "internal/v1/storedcredential/credentials/{instanceId}/{storageKey}" + urlBuilder_.Append("internal/v1/storedcredential/credentials/"); + urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(instanceId, System.Globalization.CultureInfo.InvariantCulture))); + urlBuilder_.Append('/'); urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(storageKey, System.Globalization.CultureInfo.InvariantCulture))); PrepareRequest(client_, request_, urlBuilder_); diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/StoredCredentialController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/StoredCredentialController.cs index 54f31f8c7..b90043620 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/StoredCredentialController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/StoredCredentialController.cs @@ -1,4 +1,5 @@ using Certify.Client; +using Certify.Server.Api.Public.Services; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -17,50 +18,18 @@ public partial class StoredCredentialController : ApiControllerBase private readonly ICertifyInternalApiClient _client; + private readonly ManagementAPI _mgmtAPI; + /// /// Constructor /// /// /// - public StoredCredentialController(ILogger logger, ICertifyInternalApiClient client) + public StoredCredentialController(ILogger logger, ICertifyInternalApiClient client, ManagementAPI mgmtApi) { _logger = logger; _client = client; - } - - /// - /// Get List of stored credentials - /// - /// - [HttpGet] - [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] - - [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(List))] - - public async Task GetStoredCredentials() - { - var list = await _client.GetCredentials(); - return new OkObjectResult(list); - } - - /// - /// Add/Update a stored credential - /// - /// - [HttpPost] - [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] - [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Models.Config.StoredCredential))] - public async Task UpdateStoredCredential(Models.Config.StoredCredential credential) - { - var update = await _client.UpdateCredentials(credential); - if (update != null) - { - return new OkObjectResult(update); - } - else - { - return new BadRequestResult(); - } + _mgmtAPI = mgmtApi; } } } diff --git a/src/Certify.Server/Certify.Server.Api.Public/Services/ManagementAPI.cs b/src/Certify.Server/Certify.Server.Api.Public/Services/ManagementAPI.cs index 5d0462a2c..e344bd97d 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Services/ManagementAPI.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Services/ManagementAPI.cs @@ -10,12 +10,21 @@ namespace Certify.Server.Api.Public.Services { + /// + /// Management Hub API + /// public partial class ManagementAPI { IInstanceManagementStateProvider _mgmtStateProvider; IHubContext _mgmtHubContext; ICertifyInternalApiClient _backendAPIClient; + /// + /// Constructor for Management Hub API + /// + /// + /// + /// public ManagementAPI(IInstanceManagementStateProvider mgmtStateProvider, IHubContext mgmtHubContext, ICertifyInternalApiClient backendAPIClient) { _mgmtStateProvider = mgmtStateProvider; @@ -54,9 +63,16 @@ public ManagementAPI(IInstanceManagementStateProvider mgmtStateProvider, IHubCon } } + /// + /// Add or Update Managed Certificate for target instance + /// + /// + /// + /// + /// public async Task UpdateManagedCertificate(string instanceId, ManagedCertificate managedCert, AuthContext authContext) { - // get managed cert via local api or via management hub + // update managed cert via management hub var args = new KeyValuePair[] { new("instanceId", instanceId) , @@ -167,7 +183,7 @@ public async Task GetManagedCertificateSummary(AuthContext? curre public async Task?> GetAcmeAccounts(string instanceId, AuthContext? currentAuthContext) { var args = new KeyValuePair[] { - new("instanceId", instanceId) + new("instanceId", instanceId) }; var cmd = new InstanceCommandRequest(ManagementHubCommands.GetAcmeAccounts, args); @@ -205,6 +221,63 @@ public async Task GetManagedCertificateSummary(AuthContext? curre } } + public async Task?> GetStoredCredentials(string instanceId, AuthContext? currentAuthContext) + { + var args = new KeyValuePair[] { + new("instanceId", instanceId) + }; + + var cmd = new InstanceCommandRequest(ManagementHubCommands.GetStoredCredentials, args); + + var result = await GetCommandResult(instanceId, cmd); + + if (result?.Value != null) + { + return JsonSerializer.Deserialize>(result.Value); + } + else + { + return null; + } + } + + public async Task UpdateStoredCredential(string instanceId, StoredCredential item, AuthContext? currentAuthContext) + { + var args = new KeyValuePair[] { + new("instanceId", instanceId) , + new("item", JsonSerializer.Serialize(item)) + }; + + var cmd = new InstanceCommandRequest(ManagementHubCommands.UpdateStoredCredential, args); + + var result = await GetCommandResult(instanceId, cmd); + + if (result?.Value != null) + { + return JsonSerializer.Deserialize(result.Value); + } + else + { + return null; + } + } + + public async Task DeleteStoredCredential(string instanceId, string storageKey, AuthContext authContext) + { + // delete stored credential via management hub + + var args = new KeyValuePair[] { + new("instanceId", instanceId) , + new("storageKey",storageKey) + }; + + var cmd = new InstanceCommandRequest(ManagementHubCommands.DeleteStoredCredential, args); + + var result = await GetCommandResult(instanceId, cmd); + + return result?.ObjectValue as bool? ?? false; + } + public async Task GetItemLog(string instanceId, string managedCertId, int maxLines, AuthContext? currentAuthContext) { var args = new KeyValuePair[] { diff --git a/src/Certify.SourceGenerators/ApiMethods.cs b/src/Certify.SourceGenerators/ApiMethods.cs index fb0d93abd..42979ae36 100644 --- a/src/Certify.SourceGenerators/ApiMethods.cs +++ b/src/Certify.SourceGenerators/ApiMethods.cs @@ -180,16 +180,39 @@ public static List GetApiDefinitions() ServiceAPIRoute = "accounts/remove/{storageKey}/{deactivate}", ReturnType = "Models.Config.ActionResult", Params =new Dictionary{{ "storageKey", "string" }, { "deactivate", "bool" } } + }, + new GeneratedAPI { + OperationName = "GetStoredCredentials", + OperationMethod = "HttpGet", + Comment = "Get List of Stored Credentials", + PublicAPIController = "StoredCredential", + PublicAPIRoute = "credentials/{instanceId}", + ServiceAPIRoute = "credentials", + ReturnType = "ICollection", + UseManagementAPI = true, + Params =new Dictionary{ { "instanceId", "string" } } + }, + new GeneratedAPI { + OperationName = "UpdateStoredCredential", + OperationMethod = "HttpPost", + Comment = "Add/Update Stored Credential", + PublicAPIController = "StoredCredential", + PublicAPIRoute = "credentials/{instanceId}", + ServiceAPIRoute = "credentials", + ReturnType = "Models.Config.ActionResult", + UseManagementAPI = true, + Params =new Dictionary{ { "instanceId", "string" }, { "item", "Models.Config.StoredCredential" } } }, new GeneratedAPI { - OperationName = "RemoveStoredCredential", + OperationName = "DeleteStoredCredential", OperationMethod = "HttpDelete", Comment = "Remove Stored Credential", PublicAPIController = "StoredCredential", - PublicAPIRoute = "storedcredential/{storageKey}", + PublicAPIRoute = "credentials/{instanceId}/{storageKey}", ServiceAPIRoute = "credentials", ReturnType = "Models.Config.ActionResult", - Params =new Dictionary{{ "storageKey", "string" } } + UseManagementAPI = true, + Params =new Dictionary{ { "instanceId", "string" },{ "storageKey", "string" } } }, new GeneratedAPI { OperationName = "PerformExport", From 280fd750faebd83100251ef96b87bb6d41446ba3 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Tue, 29 Oct 2024 19:47:03 +0800 Subject: [PATCH 245/328] Hub: implement instance and keyword filter of managed items --- .../Controllers/internal/HubController.cs | 36 +++++++++++-------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/HubController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/HubController.cs index 5b4782428..1bf48d2c7 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/HubController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/HubController.cs @@ -48,9 +48,8 @@ public HubController(ILogger logger, ICertifyInternalApiC [Route("items")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(ManagedCertificateSummaryResult))] - public async Task GetHubManagedItems(string? keyword, int? page = null, int? pageSize = null) + public async Task GetHubManagedItems(string? instanceId, string? keyword, int? page = null, int? pageSize = null) { - var result = new ManagedCertificateSummaryResult(); var managedItems = _mgmtStateProvider.GetManagedInstanceItems(); @@ -61,20 +60,27 @@ public async Task GetHubManagedItems(string? keyword, int? page = var list = new List(); foreach (var remote in managedItems.Values) { - list.AddRange(remote.Items.Select(i => new ManagedCertificateSummary + if (string.IsNullOrEmpty(instanceId) || (instanceId == remote.InstanceId)) { - InstanceId = remote.InstanceId, - InstanceTitle = instances.FirstOrDefault(i => i.InstanceId == remote.InstanceId)?.Title, - Id = i.Id ?? "", - Title = $"[remote] {i.Name}" ?? "", - PrimaryIdentifier = i.GetCertificateIdentifiers().FirstOrDefault(p => p.Value == i.RequestConfig.PrimaryDomain) ?? i.GetCertificateIdentifiers().FirstOrDefault(), - Identifiers = i.GetCertificateIdentifiers(), - DateRenewed = i.DateRenewed, - DateExpiry = i.DateExpiry, - Comments = i.Comments ?? "", - Status = i.LastRenewalStatus?.ToString() ?? "", - HasCertificate = !string.IsNullOrEmpty(i.CertificatePath) - })); + list.AddRange( + remote.Items + .Where(i => string.IsNullOrWhiteSpace(keyword) || (!string.IsNullOrWhiteSpace(keyword) && i.Name?.Contains(keyword) == true)) + .Select(i => new ManagedCertificateSummary + { + InstanceId = remote.InstanceId, + InstanceTitle = instances.FirstOrDefault(i => i.InstanceId == remote.InstanceId)?.Title, + Id = i.Id ?? "", + Title = $"[remote] {i.Name}" ?? "", + PrimaryIdentifier = i.GetCertificateIdentifiers().FirstOrDefault(p => p.Value == i.RequestConfig.PrimaryDomain) ?? i.GetCertificateIdentifiers().FirstOrDefault(), + Identifiers = i.GetCertificateIdentifiers(), + DateRenewed = i.DateRenewed, + DateExpiry = i.DateExpiry, + Comments = i.Comments ?? "", + Status = i.LastRenewalStatus?.ToString() ?? "", + HasCertificate = !string.IsNullOrEmpty(i.CertificatePath) + }) + ); + } } result.Results = list.OrderBy(l => l.Title); From 41a1f4ad0a6263137e014fc4f491a62dddd007b3 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Tue, 29 Oct 2024 19:47:14 +0800 Subject: [PATCH 246/328] Package updates --- .../Certify.Server.Api.Public/Certify.Server.Api.Public.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj index c413dcf48..7b3ab2fe9 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj +++ b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj @@ -21,7 +21,7 @@ - + From 8e09794b28f05ae5af23e9be12d5cb248778a25e Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Tue, 29 Oct 2024 19:47:29 +0800 Subject: [PATCH 247/328] API: dev listen all interfaces --- .../Certify.Server.Api.Public/Properties/launchSettings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Certify.Server/Certify.Server.Api.Public/Properties/launchSettings.json b/src/Certify.Server/Certify.Server.Api.Public/Properties/launchSettings.json index 4da6856e9..e6a49d2e6 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Properties/launchSettings.json +++ b/src/Certify.Server/Certify.Server.Api.Public/Properties/launchSettings.json @@ -4,7 +4,7 @@ "commandName": "IISExpress", "launchUrl": "https://localhost:44361/docs", "environmentVariables": { - "ASPNETCORE_URLS": "https://localhost:44361;", + "ASPNETCORE_URLS": "https://0.0.0.0:44361;", "CERTIFY_SERVICE_HOST": "127.0.0.2", // note: aspire hosted uses this profile "CERTIFY_SERVICE_PORT": "9695" } From a0c425c53fc6b35daa2564ef560ce338a1deeaab Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Wed, 30 Oct 2024 10:13:25 +0800 Subject: [PATCH 248/328] Package Updates --- .../DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj index 241d7a789..144fc8756 100644 --- a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj +++ b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj @@ -7,7 +7,7 @@ - + From 2d57c3f92c8c4476e410b444ce51fe10d43ae40b Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Wed, 30 Oct 2024 15:48:24 +0800 Subject: [PATCH 249/328] Implement per instance credential delete, dns zone lookup --- .../CertifyManager/CertifyManager.Account.cs | 8 +-- .../CertifyManager.ManagementHub.cs | 9 +++- .../API/Management/ManagementHubMessages.cs | 2 + .../Providers/ICredentialsManager.cs | 2 +- .../Certify.API.Public.cs | 38 +++++++------- .../internal/ChallengeProviderController.cs | 21 ++------ .../Controllers/internal/HubController.cs | 2 +- .../Services/ManagementAPI.cs | 33 ++++++++++++- .../Controllers/CredentialsController.cs | 4 +- .../Controllers/CredentialsController.cs | 4 +- src/Certify.SourceGenerators/ApiMethods.cs | 40 ++++++++++++++- .../PublicAPISourceGenerator.cs | 49 +++++++++---------- 12 files changed, 137 insertions(+), 75 deletions(-) diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.Account.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.Account.cs index a0baf0e06..fa25508eb 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.Account.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.Account.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -417,7 +417,7 @@ public async Task RemoveAccount(string storageKey, bool includeAcc { _serviceLog?.Information($"Deleting account {storageKey}: " + account.AccountURI); - var resultOk = await _credentialsManager.Delete(_itemManager, storageKey); + var result = await _credentialsManager.Delete(_itemManager, storageKey); // invalidate accounts cache lock (_accountsLock) @@ -426,7 +426,7 @@ public async Task RemoveAccount(string storageKey, bool includeAcc } // attempt acme account deactivation - if (resultOk && includeAccountDeactivation && acmeProvider != null) + if (result.IsSuccess && includeAccountDeactivation && acmeProvider != null) { try { @@ -443,7 +443,7 @@ public async Task RemoveAccount(string storageKey, bool includeAcc } } - return new ActionResult("RemoveAccount", resultOk); + return new ActionResult("RemoveAccount", result.IsSuccess); } else { diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs index f7e1fa0ba..8f640b199 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs @@ -191,9 +191,16 @@ private async Task _managementServerClient_OnGetCommandRe { var args = JsonSerializer.Deserialize[]>(arg.Value); var itemArg = args.FirstOrDefault(a => a.Key == "storageKey"); - var storedCredential = JsonSerializer.Deserialize(itemArg.Value); val = await _credentialsManager.Delete(_itemManager, itemArg.Value); } + else if (arg.CommandType == ManagementHubCommands.GetDnsZones) + { + var args = JsonSerializer.Deserialize[]>(arg.Value); + var providerTypeArg = args.FirstOrDefault(a => a.Key == "providerTypeId"); + var credentialsIdArg = args.FirstOrDefault(a => a.Key == "credentialsId"); + + val = await GetDnsProviderZones(providerTypeArg.Value, credentialsIdArg.Value); + } else if (arg.CommandType == ManagementHubCommands.Reconnect) { await _managementServerClient.Disconnect(); diff --git a/src/Certify.Models/API/Management/ManagementHubMessages.cs b/src/Certify.Models/API/Management/ManagementHubMessages.cs index 7b00ef97b..2d0d482c5 100644 --- a/src/Certify.Models/API/Management/ManagementHubMessages.cs +++ b/src/Certify.Models/API/Management/ManagementHubMessages.cs @@ -31,6 +31,8 @@ public class ManagementHubCommands public const string UpdateStoredCredential = "UpdateStoredCredential"; public const string DeleteStoredCredential = "DeleteStoredCredential"; + public const string GetDnsZones = "GetDnsZones"; + public const string Reconnect = "Reconnect"; } diff --git a/src/Certify.Models/Providers/ICredentialsManager.cs b/src/Certify.Models/Providers/ICredentialsManager.cs index fd3917649..1888c28bc 100644 --- a/src/Certify.Models/Providers/ICredentialsManager.cs +++ b/src/Certify.Models/Providers/ICredentialsManager.cs @@ -12,7 +12,7 @@ public interface ICredentialsManager bool Init(string connectionString, ILog log); Task IsInitialised(); - Task Delete(IManagedItemStore itemStore, string storageKey); + Task Delete(IManagedItemStore itemStore, string storageKey); Task> GetCredentials(string type = null, string storageKey = null); Task GetCredential(string storageKey); Task GetUnlockedCredential(string storageKey); diff --git a/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs b/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs index f74d1eca5..b590ed498 100644 --- a/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs +++ b/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs @@ -2771,23 +2771,32 @@ public virtual async System.Threading.Tasks.Task RemoveAcmeAccount } /// - /// Fetch list of DNS zones for a given DNS provider and credential + /// Get List of Zones with the current DNS provider and credential [Generated by Certify.SourceGenerators] /// /// OK /// A server side error occurred. - public virtual System.Threading.Tasks.Task> GetDnsZonesAsync(string providerTypeId, string credentialsId) + public virtual System.Threading.Tasks.Task> GetDnsZonesAsync(string instanceId, string providerTypeId, string credentialsId) { - return GetDnsZonesAsync(providerTypeId, credentialsId, System.Threading.CancellationToken.None); + return GetDnsZonesAsync(instanceId, providerTypeId, credentialsId, System.Threading.CancellationToken.None); } /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Fetch list of DNS zones for a given DNS provider and credential + /// Get List of Zones with the current DNS provider and credential [Generated by Certify.SourceGenerators] /// /// OK /// A server side error occurred. - public virtual async System.Threading.Tasks.Task> GetDnsZonesAsync(string providerTypeId, string credentialsId, System.Threading.CancellationToken cancellationToken) + public virtual async System.Threading.Tasks.Task> GetDnsZonesAsync(string instanceId, string providerTypeId, string credentialsId, System.Threading.CancellationToken cancellationToken) { + if (instanceId == null) + throw new System.ArgumentNullException("instanceId"); + + if (providerTypeId == null) + throw new System.ArgumentNullException("providerTypeId"); + + if (credentialsId == null) + throw new System.ArgumentNullException("credentialsId"); + var client_ = _httpClient; var disposeClient_ = false; try @@ -2799,18 +2808,13 @@ public virtual async System.Threading.Tasks.Task RemoveAcmeAccount var urlBuilder_ = new System.Text.StringBuilder(); if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); - // Operation Path: "internal/v1/challengeprovider/dnszones" - urlBuilder_.Append("internal/v1/challengeprovider/dnszones"); - urlBuilder_.Append('?'); - if (providerTypeId != null) - { - urlBuilder_.Append(System.Uri.EscapeDataString("providerTypeId")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(providerTypeId, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); - } - if (credentialsId != null) - { - urlBuilder_.Append(System.Uri.EscapeDataString("credentialsId")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(credentialsId, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); - } - urlBuilder_.Length--; + // Operation Path: "internal/v1/challengeprovider/challengeprovider/dnszones/{instanceId}/{providerTypeId}/{credentialsId}" + urlBuilder_.Append("internal/v1/challengeprovider/challengeprovider/dnszones/"); + urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(instanceId, System.Globalization.CultureInfo.InvariantCulture))); + urlBuilder_.Append('/'); + urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(providerTypeId, System.Globalization.CultureInfo.InvariantCulture))); + urlBuilder_.Append('/'); + urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(credentialsId, System.Globalization.CultureInfo.InvariantCulture))); PrepareRequest(client_, request_, urlBuilder_); diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/ChallengeProviderController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/ChallengeProviderController.cs index 5b8002ab9..6c6d1fe6f 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/ChallengeProviderController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/ChallengeProviderController.cs @@ -1,5 +1,6 @@ using Certify.Client; using Certify.Models.Providers; +using Certify.Server.Api.Public.Services; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -17,16 +18,17 @@ public partial class ChallengeProviderController : ApiControllerBase private readonly ILogger _logger; private readonly ICertifyInternalApiClient _client; - + private readonly ManagementAPI _mgmtAPI; /// /// Constructor /// /// /// - public ChallengeProviderController(ILogger logger, ICertifyInternalApiClient client) + public ChallengeProviderController(ILogger logger, ICertifyInternalApiClient client, ManagementAPI mgmtAPI) { _logger = logger; _client = client; + _mgmtAPI = mgmtAPI; } /// @@ -42,20 +44,5 @@ public async Task GetChallengeProviders() var list = await _client.GetChallengeAPIList(); return new OkObjectResult(list); } - - /// - /// Fetch list of DNS zones for a given DNS provider and credential - /// - /// - /// - /// - [HttpGet] - [Route("dnszones")] - [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] - [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(List))] - public async Task> GetDnsZones(string providerTypeId, string credentialsId) - { - return await _client.GetDnsProviderZones(providerTypeId, credentialsId); - } } } diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/HubController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/HubController.cs index 1bf48d2c7..13687ac78 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/HubController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/HubController.cs @@ -70,7 +70,7 @@ public async Task GetHubManagedItems(string? instanceId, string? InstanceId = remote.InstanceId, InstanceTitle = instances.FirstOrDefault(i => i.InstanceId == remote.InstanceId)?.Title, Id = i.Id ?? "", - Title = $"[remote] {i.Name}" ?? "", + Title = $"{i.Name}" ?? "", PrimaryIdentifier = i.GetCertificateIdentifiers().FirstOrDefault(p => p.Value == i.RequestConfig.PrimaryDomain) ?? i.GetCertificateIdentifiers().FirstOrDefault(), Identifiers = i.GetCertificateIdentifiers(), DateRenewed = i.DateRenewed, diff --git a/src/Certify.Server/Certify.Server.Api.Public/Services/ManagementAPI.cs b/src/Certify.Server/Certify.Server.Api.Public/Services/ManagementAPI.cs index e344bd97d..985875d63 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Services/ManagementAPI.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Services/ManagementAPI.cs @@ -221,6 +221,28 @@ public async Task GetManagedCertificateSummary(AuthContext? curre } } + public async Task?> GetDnsZones(string instanceId, string providerTypeId, string credentialsId, AuthContext? currentAuthContext) + { + var args = new KeyValuePair[] { + new("instanceId", instanceId), + new("providerTypeId", providerTypeId), + new("credentialsId", credentialsId) + }; + + var cmd = new InstanceCommandRequest(ManagementHubCommands.GetDnsZones, args); + + var result = await GetCommandResult(instanceId, cmd); + + if (result?.Value != null) + { + return JsonSerializer.Deserialize>(result.Value); + } + else + { + return null; + } + } + public async Task?> GetStoredCredentials(string instanceId, AuthContext? currentAuthContext) { var args = new KeyValuePair[] { @@ -262,7 +284,7 @@ public async Task GetManagedCertificateSummary(AuthContext? curre } } - public async Task DeleteStoredCredential(string instanceId, string storageKey, AuthContext authContext) + public async Task DeleteStoredCredential(string instanceId, string storageKey, AuthContext authContext) { // delete stored credential via management hub @@ -275,7 +297,14 @@ public async Task DeleteStoredCredential(string instanceId, string storage var result = await GetCommandResult(instanceId, cmd); - return result?.ObjectValue as bool? ?? false; + if (result?.Value != null) + { + return JsonSerializer.Deserialize(result.Value); + } + else + { + return null; + } } public async Task GetItemLog(string instanceId, string managedCertId, int maxLines, AuthContext? currentAuthContext) diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/CredentialsController.cs b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/CredentialsController.cs index 90e53068c..c39f6c821 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/CredentialsController.cs +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/CredentialsController.cs @@ -1,4 +1,4 @@ -using Certify.Management; +using Certify.Management; using Certify.Models.Config; using Microsoft.AspNetCore.Mvc; @@ -33,7 +33,7 @@ public async Task UpdateCredentials(StoredCredential credentia } [HttpDelete, Route("{storageKey}")] - public async Task DeleteCredential(string storageKey) + public async Task DeleteCredential(string storageKey) { DebugLog(); diff --git a/src/Certify.Service/Controllers/CredentialsController.cs b/src/Certify.Service/Controllers/CredentialsController.cs index eb4fe32bc..98e481aed 100644 --- a/src/Certify.Service/Controllers/CredentialsController.cs +++ b/src/Certify.Service/Controllers/CredentialsController.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Threading.Tasks; using System.Web.Http; using Certify.Management; @@ -34,7 +34,7 @@ public async Task UpdateCredentials(StoredCredential credentia } [HttpDelete, Route("{storageKey}")] - public async Task DeleteCredential(string storageKey) + public async Task DeleteCredential(string storageKey) { DebugLog(); diff --git a/src/Certify.SourceGenerators/ApiMethods.cs b/src/Certify.SourceGenerators/ApiMethods.cs index 42979ae36..77ca3c668 100644 --- a/src/Certify.SourceGenerators/ApiMethods.cs +++ b/src/Certify.SourceGenerators/ApiMethods.cs @@ -1,10 +1,31 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; + using SourceGenerator; namespace Certify.SourceGenerators { internal class ApiMethods { + public static string HttpGet = "HttpGet"; + public static string HttpPost = "HttpPost"; + public static string HttpDelete = "HttpDelete"; + + public static string GetFormattedTypeName(Type type) + { + if (type.IsGenericType) + { + var genericArguments = type.GetGenericArguments() + .Select(x => x.FullName) + .Aggregate((x1, x2) => $"{x1}, {x2}"); + return $"{type.FullName.Substring(0, type.FullName.IndexOf("`"))}" + + $"<{genericArguments}>"; + } + + return type.FullName; + } public static List GetApiDefinitions() { // declaring an API definition here is then used by the source generators to: @@ -189,7 +210,7 @@ public static List GetApiDefinitions() PublicAPIRoute = "credentials/{instanceId}", ServiceAPIRoute = "credentials", ReturnType = "ICollection", - UseManagementAPI = true, + UseManagementAPI = true, Params =new Dictionary{ { "instanceId", "string" } } }, new GeneratedAPI { @@ -213,6 +234,21 @@ public static List GetApiDefinitions() ReturnType = "Models.Config.ActionResult", UseManagementAPI = true, Params =new Dictionary{ { "instanceId", "string" },{ "storageKey", "string" } } + }, + new GeneratedAPI { + OperationName = "GetDnsZones", + OperationMethod = HttpGet, + Comment = "Get List of Zones with the current DNS provider and credential", + PublicAPIController = "ChallengeProvider", + PublicAPIRoute = "challengeprovider/dnszones/{instanceId}/{providerTypeId}/{credentialsId}", + ServiceAPIRoute = "managedcertificates/dnszones/{providerTypeId}/{credentialsId}", + ReturnType = "ICollection", + UseManagementAPI = true, + Params =new Dictionary{ + { "instanceId", "string" } , + { "providerTypeId", "string" }, + { "credentialsId", "string" } + } }, new GeneratedAPI { OperationName = "PerformExport", diff --git a/src/Certify.SourceGenerators/PublicAPISourceGenerator.cs b/src/Certify.SourceGenerators/PublicAPISourceGenerator.cs index e2e7dbf7e..f3f3f99e4 100644 --- a/src/Certify.SourceGenerators/PublicAPISourceGenerator.cs +++ b/src/Certify.SourceGenerators/PublicAPISourceGenerator.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; @@ -83,17 +83,24 @@ public partial class {config.PublicAPIController}Controller if (context.Compilation.AssemblyName.EndsWith("Certify.Client")) { - - if (config.OperationMethod == "HttpGet") - { - context.AddSource($"{config.PublicAPIController}.{config.OperationName}.ICertifyInternalApiClient.g.cs", SourceText.From($@" + var template = @" using Certify.Models; +using Certify.Models.Config.Migration; +using Certify.Models.Providers; using Certify.Models.Config.AccessControl; using System.Collections.Generic; using System.Threading.Tasks; - namespace Certify.Client - {{ +namespace Certify.Client +{ + MethodTemplate +} +"; + + if (config.OperationMethod == "HttpGet") + { + var source = SourceText.From(template.Replace("MethodTemplate", $@" + public partial interface ICertifyInternalApiClient {{ /// @@ -117,7 +124,8 @@ public partial class CertifyApiClient }} }} - }}", Encoding.UTF8)); + "), Encoding.UTF8); + context.AddSource($"{config.PublicAPIController}.{config.OperationName}.ICertifyInternalApiClient.g.cs", source); } if (config.OperationMethod == "HttpPost") @@ -131,15 +139,9 @@ public partial class CertifyApiClient postApiCall = apiParamCall.Replace("instanceId,", ""); postApiParamDecl = apiParamDecl.Replace("string instanceId,", ""); } - - context.AddSource($"{config.PublicAPIController}.{config.OperationName}.ICertifyInternalApiClient.g.cs", SourceText.From($@" -using Certify.Models; -using Certify.Models.Config.AccessControl; -using System.Collections.Generic; -using System.Threading.Tasks; - namespace Certify.Client - {{ + context.AddSource($"{config.PublicAPIController}.{config.OperationName}.ICertifyInternalApiClient.g.cs", SourceText.From(template.Replace("MethodTemplate", $@" + public partial interface ICertifyInternalApiClient {{ /// @@ -164,19 +166,13 @@ public partial class CertifyApiClient }} }} - }}", Encoding.UTF8)); + "), Encoding.UTF8)); } if (config.OperationMethod == "HttpDelete") { - context.AddSource($"{config.PublicAPIController}.{config.OperationName}.ICertifyInternalApiClient.g.cs", SourceText.From($@" -using Certify.Models; -using Certify.Models.Config.AccessControl; -using System.Collections.Generic; -using System.Threading.Tasks; + context.AddSource($"{config.PublicAPIController}.{config.OperationName}.ICertifyInternalApiClient.g.cs", SourceText.From(template.Replace("MethodTemplate", $@" - namespace Certify.Client - {{ public partial interface ICertifyInternalApiClient {{ /// @@ -205,7 +201,7 @@ public partial class CertifyApiClient }} }} - }}", Encoding.UTF8)); + "), Encoding.UTF8)); } } @@ -215,6 +211,7 @@ public partial class CertifyApiClient using System.Collections.Generic; using System.Threading.Tasks; using Certify.Models; +using Certify.Models.Providers; using Certify.Models.Config.AccessControl; namespace Certify.UI.Client.Core @@ -237,7 +234,7 @@ public void Initialize(GeneratorInitializationContext context) // then add a watch on if (!Debugger.IsAttached) { - //Debugger.Launch(); + // Debugger.Launch(); } #endif } From 408b2a0e3ae0eda25427ad7ae30dff3f680cfab2 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Fri, 1 Nov 2024 16:10:51 +0800 Subject: [PATCH 250/328] Hub: use stateful reconnect to preserve connection info --- src/Certify.Client/ManagementServerClient.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Certify.Client/ManagementServerClient.cs b/src/Certify.Client/ManagementServerClient.cs index ed9cc01cf..543c3464c 100644 --- a/src/Certify.Client/ManagementServerClient.cs +++ b/src/Certify.Client/ManagementServerClient.cs @@ -65,6 +65,9 @@ public async Task ConnectAsync() return message; }; + + opts.UseStatefulReconnect = true; + }) .WithAutomaticReconnect() .AddMessagePackProtocol() From cbf6257acba94099d2297143447b81ba80a1e1c7 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Fri, 1 Nov 2024 16:11:48 +0800 Subject: [PATCH 251/328] Hub: reinstate instance heartbeat message --- .../Management/CertifyManager/CertifyManager.ManagementHub.cs | 4 ++-- src/Certify.Core/Management/CertifyManager/CertifyManager.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs index 8f640b199..fc6c089f8 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text.Json; @@ -38,7 +38,7 @@ private async Task EnsureMgmtHubConnection() private void SendHeartbeatToManagementHub() { - //_managementServerClient.SendInstanceInfo(Guid.NewGuid(), false); + _managementServerClient.SendInstanceInfo(Guid.NewGuid(), false); } private async Task StartManagementHubConnection(string hubUri) diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.cs index 01da9df20..7d9b57bd9 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.cs @@ -205,8 +205,8 @@ public async Task Init() /// private void SetupJobs() { - // 60 second job timer (reporting etc) - _heartbeatTimer = new System.Timers.Timer(60 * 1000); // every n seconds + // n second job timer (reporting etc) + _heartbeatTimer = new System.Timers.Timer(30 * 1000); // every n seconds _heartbeatTimer.Elapsed += _heartbeatTimer_Elapsed; _heartbeatTimer.Start(); From 805893a2b8d8327c7a69d86120d5f2a4fc574c05 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Fri, 1 Nov 2024 16:13:58 +0800 Subject: [PATCH 252/328] Implement per instance dns challenge providers --- .../CertifyManager.ManagementHub.cs | 6 +- .../API/Management/ManagementHubMessages.cs | 1 + .../Certify.API.Public.cs | 18 +++--- .../internal/ChallengeProviderController.cs | 14 ----- src/Certify.SourceGenerators/ApiMethods.cs | 61 +++++++++++-------- .../Certify.SourceGenerators.csproj | 1 - .../Certify.Core.Tests.Unit.csproj | 1 + .../Tests/MiscTests.cs | 17 ++++++ 8 files changed, 71 insertions(+), 48 deletions(-) diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs index fc6c089f8..7c0c09842 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text.Json; @@ -193,6 +193,10 @@ private async Task _managementServerClient_OnGetCommandRe var itemArg = args.FirstOrDefault(a => a.Key == "storageKey"); val = await _credentialsManager.Delete(_itemManager, itemArg.Value); } + else if (arg.CommandType == ManagementHubCommands.GetChallengeProviders) + { + val = await Core.Management.Challenges.ChallengeProviders.GetChallengeAPIProviders(); + } else if (arg.CommandType == ManagementHubCommands.GetDnsZones) { var args = JsonSerializer.Deserialize[]>(arg.Value); diff --git a/src/Certify.Models/API/Management/ManagementHubMessages.cs b/src/Certify.Models/API/Management/ManagementHubMessages.cs index 2d0d482c5..9aa6232fa 100644 --- a/src/Certify.Models/API/Management/ManagementHubMessages.cs +++ b/src/Certify.Models/API/Management/ManagementHubMessages.cs @@ -31,6 +31,7 @@ public class ManagementHubCommands public const string UpdateStoredCredential = "UpdateStoredCredential"; public const string DeleteStoredCredential = "DeleteStoredCredential"; + public const string GetChallengeProviders = "GetChallengeProviders"; public const string GetDnsZones = "GetDnsZones"; public const string Reconnect = "Reconnect"; diff --git a/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs b/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs index b590ed498..4e9f6d8a4 100644 --- a/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs +++ b/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs @@ -2687,23 +2687,26 @@ public virtual async System.Threading.Tasks.Task RemoveAcmeAccount } /// - /// Get list of supported challenge providers + /// Get Dns Challenge Providers [Generated by Certify.SourceGenerators] /// /// OK /// A server side error occurred. - public virtual System.Threading.Tasks.Task> GetChallengeProvidersAsync() + public virtual System.Threading.Tasks.Task> GetChallengeProvidersAsync(string instanceId) { - return GetChallengeProvidersAsync(System.Threading.CancellationToken.None); + return GetChallengeProvidersAsync(instanceId, System.Threading.CancellationToken.None); } /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Get list of supported challenge providers + /// Get Dns Challenge Providers [Generated by Certify.SourceGenerators] /// /// OK /// A server side error occurred. - public virtual async System.Threading.Tasks.Task> GetChallengeProvidersAsync(System.Threading.CancellationToken cancellationToken) + public virtual async System.Threading.Tasks.Task> GetChallengeProvidersAsync(string instanceId, System.Threading.CancellationToken cancellationToken) { + if (instanceId == null) + throw new System.ArgumentNullException("instanceId"); + var client_ = _httpClient; var disposeClient_ = false; try @@ -2715,8 +2718,9 @@ public virtual async System.Threading.Tasks.Task RemoveAcmeAccount var urlBuilder_ = new System.Text.StringBuilder(); if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); - // Operation Path: "internal/v1/challengeprovider" - urlBuilder_.Append("internal/v1/challengeprovider"); + // Operation Path: "internal/v1/challengeprovider/challengeprovider/{instanceId}" + urlBuilder_.Append("internal/v1/challengeprovider/challengeprovider/"); + urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(instanceId, System.Globalization.CultureInfo.InvariantCulture))); PrepareRequest(client_, request_, urlBuilder_); diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/ChallengeProviderController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/ChallengeProviderController.cs index 6c6d1fe6f..1bddf16c9 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/ChallengeProviderController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/ChallengeProviderController.cs @@ -30,19 +30,5 @@ public ChallengeProviderController(ILogger logger, _client = client; _mgmtAPI = mgmtAPI; } - - /// - /// Get list of supported challenge providers - /// - /// - [HttpGet] - [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] - [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(List))] - - public async Task GetChallengeProviders() - { - var list = await _client.GetChallengeAPIList(); - return new OkObjectResult(list); - } } } diff --git a/src/Certify.SourceGenerators/ApiMethods.cs b/src/Certify.SourceGenerators/ApiMethods.cs index 77ca3c668..c6781a81b 100644 --- a/src/Certify.SourceGenerators/ApiMethods.cs +++ b/src/Certify.SourceGenerators/ApiMethods.cs @@ -1,13 +1,11 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; -using System.Net.Http; - using SourceGenerator; namespace Certify.SourceGenerators { - internal class ApiMethods + public class ApiMethods { public static string HttpGet = "HttpGet"; public static string HttpPost = "HttpPost"; @@ -38,7 +36,7 @@ public static List GetApiDefinitions() new GeneratedAPI { OperationName = "GetSecurityPrincipleAssignedRoles", - OperationMethod = "HttpGet", + OperationMethod = HttpGet, Comment = "Get list of Assigned Roles for a given security principle", PublicAPIController = "Access", PublicAPIRoute = "securityprinciple/{id}/assignedroles", @@ -49,7 +47,7 @@ public static List GetApiDefinitions() new GeneratedAPI { OperationName = "GetSecurityPrincipleRoleStatus", - OperationMethod = "HttpGet", + OperationMethod = HttpGet, Comment = "Get list of Assigned Roles etc for a given security principle", PublicAPIController = "Access", PublicAPIRoute = "securityprinciple/{id}/rolestatus", @@ -60,7 +58,7 @@ public static List GetApiDefinitions() new GeneratedAPI { OperationName = "GetAccessRoles", - OperationMethod = "HttpGet", + OperationMethod = HttpGet, Comment = "Get list of available security Roles", PublicAPIController = "Access", PublicAPIRoute = "roles", @@ -70,7 +68,7 @@ public static List GetApiDefinitions() new GeneratedAPI { OperationName = "GetSecurityPrinciples", - OperationMethod = "HttpGet", + OperationMethod = HttpGet, Comment = "Get list of available security principles", PublicAPIController = "Access", PublicAPIRoute = "securityprinciples", @@ -79,7 +77,7 @@ public static List GetApiDefinitions() }, new GeneratedAPI { OperationName = "ValidateSecurityPrinciplePassword", - OperationMethod = "HttpPost", + OperationMethod = HttpPost, Comment = "Check password valid for security principle", PublicAPIController = "Access", PublicAPIRoute = "validate", @@ -90,7 +88,7 @@ public static List GetApiDefinitions() new GeneratedAPI { OperationName = "UpdateSecurityPrinciplePassword", - OperationMethod = "HttpPost", + OperationMethod = HttpPost, Comment = "Update password for security principle", PublicAPIController = "Access", PublicAPIRoute = "updatepassword", @@ -101,7 +99,7 @@ public static List GetApiDefinitions() new GeneratedAPI { OperationName = "AddSecurityPrinciple", - OperationMethod = "HttpPost", + OperationMethod = HttpPost, Comment = "Add new security principle", PublicAPIController = "Access", PublicAPIRoute = "securityprinciple", @@ -112,7 +110,7 @@ public static List GetApiDefinitions() new GeneratedAPI { OperationName = "UpdateSecurityPrinciple", - OperationMethod = "HttpPost", + OperationMethod = HttpPost, Comment = "Update existing security principle", PublicAPIController = "Access", PublicAPIRoute = "securityprinciple/update", @@ -125,7 +123,7 @@ public static List GetApiDefinitions() new GeneratedAPI { OperationName = "UpdateSecurityPrincipleAssignedRoles", - OperationMethod = "HttpPost", + OperationMethod = HttpPost, Comment = "Update assigned roles for a security principle", PublicAPIController = "Access", PublicAPIRoute = "securityprinciple/roles/update", @@ -138,7 +136,7 @@ public static List GetApiDefinitions() new GeneratedAPI { OperationName = "DeleteSecurityPrinciple", - OperationMethod = "HttpDelete", + OperationMethod = HttpDelete, Comment = "Delete security principle", PublicAPIController = "Access", PublicAPIRoute = "securityprinciple", @@ -149,7 +147,7 @@ public static List GetApiDefinitions() /* per instance API, via management hub */ new GeneratedAPI { OperationName = "GetAcmeAccounts", - OperationMethod = "HttpGet", + OperationMethod = HttpGet, Comment = "Get All Acme Accounts", UseManagementAPI = true, PublicAPIController = "CertificateAuthority", @@ -160,7 +158,7 @@ public static List GetApiDefinitions() }, new GeneratedAPI { OperationName = "AddAcmeAccount", - OperationMethod = "HttpPost", + OperationMethod = HttpPost, Comment = "Add New Acme Account", UseManagementAPI = true, PublicAPIController = "CertificateAuthority", @@ -172,7 +170,7 @@ public static List GetApiDefinitions() new GeneratedAPI { OperationName = "AddCertificateAuthority", - OperationMethod = "HttpPost", + OperationMethod = HttpPost, Comment = "Add New Certificate Authority", PublicAPIController = "CertificateAuthority", PublicAPIRoute = "authority", @@ -184,7 +182,7 @@ public static List GetApiDefinitions() new GeneratedAPI { OperationName = "RemoveCertificateAuthority", - OperationMethod = "HttpDelete", + OperationMethod = HttpDelete, Comment = "Remove Certificate Authority", PublicAPIController = "CertificateAuthority", PublicAPIRoute = "authority/{id}", @@ -194,7 +192,7 @@ public static List GetApiDefinitions() }, new GeneratedAPI { OperationName = "RemoveAcmeAccount", - OperationMethod = "HttpDelete", + OperationMethod = HttpDelete, Comment = "Remove ACME Account", PublicAPIController = "CertificateAuthority", PublicAPIRoute = "accounts/{storageKey}/{deactivate}", @@ -204,7 +202,7 @@ public static List GetApiDefinitions() }, new GeneratedAPI { OperationName = "GetStoredCredentials", - OperationMethod = "HttpGet", + OperationMethod = HttpGet, Comment = "Get List of Stored Credentials", PublicAPIController = "StoredCredential", PublicAPIRoute = "credentials/{instanceId}", @@ -215,7 +213,7 @@ public static List GetApiDefinitions() }, new GeneratedAPI { OperationName = "UpdateStoredCredential", - OperationMethod = "HttpPost", + OperationMethod = HttpPost, Comment = "Add/Update Stored Credential", PublicAPIController = "StoredCredential", PublicAPIRoute = "credentials/{instanceId}", @@ -226,7 +224,7 @@ public static List GetApiDefinitions() }, new GeneratedAPI { OperationName = "DeleteStoredCredential", - OperationMethod = "HttpDelete", + OperationMethod = HttpDelete, Comment = "Remove Stored Credential", PublicAPIController = "StoredCredential", PublicAPIRoute = "credentials/{instanceId}/{storageKey}", @@ -234,6 +232,19 @@ public static List GetApiDefinitions() ReturnType = "Models.Config.ActionResult", UseManagementAPI = true, Params =new Dictionary{ { "instanceId", "string" },{ "storageKey", "string" } } + }, + new GeneratedAPI { + OperationName = "GetChallengeProviders", + OperationMethod = HttpGet, + Comment = "Get Dns Challenge Providers", + PublicAPIController = "ChallengeProvider", + PublicAPIRoute = "challengeprovider/{instanceId}", + ServiceAPIRoute = "managedcertificates/challengeapis/", + ReturnType = "ICollection", + UseManagementAPI = true, + Params =new Dictionary{ + { "instanceId", "string" } + } }, new GeneratedAPI { OperationName = "GetDnsZones", @@ -252,7 +263,7 @@ public static List GetApiDefinitions() }, new GeneratedAPI { OperationName = "PerformExport", - OperationMethod = "HttpPost", + OperationMethod = HttpPost, Comment = "Perform an export of all settings", PublicAPIController = "System", PublicAPIRoute = "system/migration/export", @@ -262,7 +273,7 @@ public static List GetApiDefinitions() }, new GeneratedAPI { OperationName = "PerformImport", - OperationMethod = "HttpPost", + OperationMethod = HttpPost, Comment = "Perform an import of all settings", PublicAPIController = "System", PublicAPIRoute = "system/migration/import", @@ -273,7 +284,7 @@ public static List GetApiDefinitions() new GeneratedAPI { OperationName = "RemoveManagedCertificate", - OperationMethod = "HttpDelete", + OperationMethod = HttpDelete, Comment = "Remove Managed Certificate", PublicAPIController = "Certificate", PublicAPIRoute = "settings/{instanceId}/{managedCertId}", diff --git a/src/Certify.SourceGenerators/Certify.SourceGenerators.csproj b/src/Certify.SourceGenerators/Certify.SourceGenerators.csproj index 351012dee..6c8720d2d 100644 --- a/src/Certify.SourceGenerators/Certify.SourceGenerators.csproj +++ b/src/Certify.SourceGenerators/Certify.SourceGenerators.csproj @@ -17,5 +17,4 @@ - diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj index 15dec1d7b..bfa193fe4 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj @@ -96,6 +96,7 @@ + diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/MiscTests.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/MiscTests.cs index e359e041d..3e66b640b 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/MiscTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/MiscTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -114,5 +115,21 @@ public void TestDemoDataGeneration() Assert.IsTrue(items.Any()); } + + [TestMethod, Description("Source gen test")] + public void TestSourceGen() + { + var typeName = SourceGenerators.ApiMethods.GetFormattedTypeName(typeof(string)); + + Assert.AreEqual("System.String", typeName); + + typeName = SourceGenerators.ApiMethods.GetFormattedTypeName(typeof(Certify.Models.CertificateAuthority)); + + Assert.AreEqual("Certify.Models.CertificateAuthority", typeName); + + typeName = SourceGenerators.ApiMethods.GetFormattedTypeName(typeof(ICollection)); + + Assert.AreEqual("System.Collections.Generic.ICollection", typeName); + } } } From 64d14e94859efa004160dc384364f9733938d277 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Fri, 1 Nov 2024 16:22:47 +0800 Subject: [PATCH 253/328] Hub: simplify and standardize command invocation --- .../Services/ManagementAPI.cs | 252 ++++++------------ 1 file changed, 80 insertions(+), 172 deletions(-) diff --git a/src/Certify.Server/Certify.Server.Api.Public/Services/ManagementAPI.cs b/src/Certify.Server/Certify.Server.Api.Public/Services/ManagementAPI.cs index 985875d63..c38b1c7f8 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Services/ManagementAPI.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Services/ManagementAPI.cs @@ -4,6 +4,7 @@ using Certify.Models; using Certify.Models.API; using Certify.Models.Config; +using Certify.Models.Providers; using Certify.Models.Reporting; using Certify.Server.Api.Public.SignalR.ManagementHub; using Microsoft.AspNetCore.SignalR; @@ -32,6 +33,52 @@ public ManagementAPI(IInstanceManagementStateProvider mgmtStateProvider, IHubCon _backendAPIClient = backendAPIClient; } + private async Task GetCommandResult(string instanceId, InstanceCommandRequest cmd) + { + var connectionId = _mgmtStateProvider.GetConnectionIdForInstance(instanceId); + + if (connectionId == null) + { + throw new Exception("Instance connection info not known, cannot send commands to instance."); + } + + _mgmtStateProvider.AddAwaitedCommandRequest(cmd); + + await _mgmtHubContext.Clients.Client(connectionId).SendCommandRequest(cmd); + + return await _mgmtStateProvider.ConsumeAwaitedCommandResult(cmd.CommandId); + } + + private async Task SendCommandWithNoResult(string instanceId, InstanceCommandRequest cmd) + { + var connectionId = _mgmtStateProvider.GetConnectionIdForInstance(instanceId); + + if (connectionId == null) + { + throw new Exception("Instance connection info not known, cannot send commands to instance."); + } + + _mgmtStateProvider.AddAwaitedCommandRequest(cmd); + + await _mgmtHubContext.Clients.Client(connectionId).SendCommandRequest(cmd); + } + + private async Task PerformInstanceCommandTaskWithResult(string instanceId, KeyValuePair[] args, string commandType) + { + var cmd = new InstanceCommandRequest(commandType, args); + + var result = await GetCommandResult(instanceId, cmd); + + if (result?.Value != null) + { + return JsonSerializer.Deserialize(result.Value); + } + else + { + return default; + } + } + /// /// Fetch managed cert details from the target instance /// @@ -43,24 +90,12 @@ public ManagementAPI(IInstanceManagementStateProvider mgmtStateProvider, IHubCon { // get managed cert via local api or via management hub - var args = new KeyValuePair - [] { + var args = new KeyValuePair[] { new("instanceId", instanceId) , new("managedCertId", managedCertId) }; - var cmd = new InstanceCommandRequest(ManagementHubCommands.GetManagedItem, args); - var result = await GetCommandResult(instanceId, cmd); - - if (result?.Value != null) - { - return JsonSerializer.Deserialize(result.Value); - - } - else - { - return null; - } + return await PerformInstanceCommandTaskWithResult(instanceId, args, ManagementHubCommands.GetManagedItem); } /// @@ -79,21 +114,14 @@ public ManagementAPI(IInstanceManagementStateProvider mgmtStateProvider, IHubCon new("managedCert", JsonSerializer.Serialize(managedCert)) }; - var cmd = new InstanceCommandRequest(ManagementHubCommands.UpdateManagedItem, args); + var result = await PerformInstanceCommandTaskWithResult(instanceId, args, ManagementHubCommands.UpdateManagedItem); - var result = await GetCommandResult(instanceId, cmd); - - if (result?.Value != null) + if (result != null) { - var update = JsonSerializer.Deserialize(result.Value); - - _mgmtStateProvider.UpdateCachedManagedInstanceItem(instanceId, update); - return update; - } - else - { - return null; + _mgmtStateProvider.UpdateCachedManagedInstanceItem(instanceId, result); } + + return result; } /// @@ -112,49 +140,20 @@ public async Task RemoveManagedCertificate(string instanceId, string manag new("managedCertId",managedCertId) }; - var cmd = new InstanceCommandRequest(ManagementHubCommands.DeleteManagedItem, args); - - var result = await GetCommandResult(instanceId, cmd); + var deletedOK = await PerformInstanceCommandTaskWithResult(instanceId, args, ManagementHubCommands.DeleteManagedItem); - try - { - _mgmtStateProvider.DeleteCachedManagedInstanceItem(instanceId, managedCertId); - return true; - } - catch + if (deletedOK) { - return false; - } - } - - private async Task GetCommandResult(string instanceId, InstanceCommandRequest cmd) - { - var connectionId = _mgmtStateProvider.GetConnectionIdForInstance(instanceId); - - if (connectionId == null) - { - throw new Exception("Instance connection info not known, cannot send commands to instance."); - } - - _mgmtStateProvider.AddAwaitedCommandRequest(cmd); - - await _mgmtHubContext.Clients.Client(connectionId).SendCommandRequest(cmd); - - return await _mgmtStateProvider.ConsumeAwaitedCommandResult(cmd.CommandId); - } - - private async Task SendCommandWithNoResult(string instanceId, InstanceCommandRequest cmd) - { - var connectionId = _mgmtStateProvider.GetConnectionIdForInstance(instanceId); - - if (connectionId == null) - { - throw new Exception("Instance connection info not known, cannot send commands to instance."); + try + { + _mgmtStateProvider.DeleteCachedManagedInstanceItem(instanceId, managedCertId); + } + catch + { + } } - _mgmtStateProvider.AddAwaitedCommandRequest(cmd); - - await _mgmtHubContext.Clients.Client(connectionId).SendCommandRequest(cmd); + return deletedOK; } public async Task GetManagedCertificateSummary(AuthContext? currentAuthContext) @@ -186,18 +185,7 @@ public async Task GetManagedCertificateSummary(AuthContext? curre new("instanceId", instanceId) }; - var cmd = new InstanceCommandRequest(ManagementHubCommands.GetAcmeAccounts, args); - - var result = await GetCommandResult(instanceId, cmd); - - if (result?.Value != null) - { - return JsonSerializer.Deserialize>(result.Value); - } - else - { - return null; - } + return await PerformInstanceCommandTaskWithResult>(instanceId, args, ManagementHubCommands.GetAcmeAccounts); } public async Task AddAcmeAccount(string instanceId, ContactRegistration registration, AuthContext? currentAuthContext) @@ -207,18 +195,15 @@ public async Task GetManagedCertificateSummary(AuthContext? curre new("registration", JsonSerializer.Serialize(registration)) }; - var cmd = new InstanceCommandRequest(ManagementHubCommands.AddAcmeAccount, args); - - var result = await GetCommandResult(instanceId, cmd); + return await PerformInstanceCommandTaskWithResult(instanceId, args, ManagementHubCommands.AddAcmeAccount); + } - if (result?.Value != null) - { - return JsonSerializer.Deserialize(result.Value); - } - else - { - return null; - } + public async Task?> GetChallengeProviders(string instanceId, AuthContext? currentAuthContext) + { + var args = new KeyValuePair[] { + new("instanceId", instanceId) + }; + return await PerformInstanceCommandTaskWithResult>(instanceId, args, ManagementHubCommands.GetChallengeProviders); } public async Task?> GetDnsZones(string instanceId, string providerTypeId, string credentialsId, AuthContext? currentAuthContext) @@ -229,18 +214,7 @@ public async Task GetManagedCertificateSummary(AuthContext? curre new("credentialsId", credentialsId) }; - var cmd = new InstanceCommandRequest(ManagementHubCommands.GetDnsZones, args); - - var result = await GetCommandResult(instanceId, cmd); - - if (result?.Value != null) - { - return JsonSerializer.Deserialize>(result.Value); - } - else - { - return null; - } + return await PerformInstanceCommandTaskWithResult>(instanceId, args, ManagementHubCommands.GetDnsZones); } public async Task?> GetStoredCredentials(string instanceId, AuthContext? currentAuthContext) @@ -249,18 +223,7 @@ public async Task GetManagedCertificateSummary(AuthContext? curre new("instanceId", instanceId) }; - var cmd = new InstanceCommandRequest(ManagementHubCommands.GetStoredCredentials, args); - - var result = await GetCommandResult(instanceId, cmd); - - if (result?.Value != null) - { - return JsonSerializer.Deserialize>(result.Value); - } - else - { - return null; - } + return await PerformInstanceCommandTaskWithResult>(instanceId, args, ManagementHubCommands.GetStoredCredentials); } public async Task UpdateStoredCredential(string instanceId, StoredCredential item, AuthContext? currentAuthContext) @@ -270,18 +233,7 @@ public async Task GetManagedCertificateSummary(AuthContext? curre new("item", JsonSerializer.Serialize(item)) }; - var cmd = new InstanceCommandRequest(ManagementHubCommands.UpdateStoredCredential, args); - - var result = await GetCommandResult(instanceId, cmd); - - if (result?.Value != null) - { - return JsonSerializer.Deserialize(result.Value); - } - else - { - return null; - } + return await PerformInstanceCommandTaskWithResult(instanceId, args, ManagementHubCommands.UpdateStoredCredential); } public async Task DeleteStoredCredential(string instanceId, string storageKey, AuthContext authContext) @@ -293,18 +245,7 @@ public async Task GetManagedCertificateSummary(AuthContext? curre new("storageKey",storageKey) }; - var cmd = new InstanceCommandRequest(ManagementHubCommands.DeleteStoredCredential, args); - - var result = await GetCommandResult(instanceId, cmd); - - if (result?.Value != null) - { - return JsonSerializer.Deserialize(result.Value); - } - else - { - return null; - } + return await PerformInstanceCommandTaskWithResult(instanceId, args, ManagementHubCommands.DeleteStoredCredential); } public async Task GetItemLog(string instanceId, string managedCertId, int maxLines, AuthContext? currentAuthContext) @@ -315,18 +256,7 @@ public async Task GetItemLog(string instanceId, string managedCertId, new("limit",maxLines.ToString()) }; - var cmd = new InstanceCommandRequest(ManagementHubCommands.GetManagedItemLog, args); - - var result = await GetCommandResult(instanceId, cmd); - - if (result?.Value != null) - { - return JsonSerializer.Deserialize(result.Value); - } - else - { - return []; - } + return await PerformInstanceCommandTaskWithResult(instanceId, args, ManagementHubCommands.GetManagedItemLog) ?? []; } internal async Task> TestManagedCertificateConfiguration(string instanceId, ManagedCertificate managedCert, AuthContext? currentAuthContext) @@ -336,18 +266,7 @@ internal async Task> TestManagedCertificateConfiguration(str new("managedCert",JsonSerializer.Serialize(managedCert)) }; - var cmd = new InstanceCommandRequest(ManagementHubCommands.TestManagedItemConfiguration, args); - - var result = await GetCommandResult(instanceId, cmd); - - if (result?.Value != null) - { - return JsonSerializer.Deserialize>(result.Value); - } - else - { - return []; - } + return await PerformInstanceCommandTaskWithResult>(instanceId, args, ManagementHubCommands.TestManagedItemConfiguration) ?? []; } internal async Task> GetPreviewActions(string instanceId, ManagedCertificate managedCert, AuthContext? currentAuthContext) @@ -357,18 +276,7 @@ internal async Task> GetPreviewActions(string instanceId, Manag new("managedCert",JsonSerializer.Serialize(managedCert)) }; - var cmd = new InstanceCommandRequest(ManagementHubCommands.GetManagedItemRenewalPreview, args); - - var result = await GetCommandResult(instanceId, cmd); - - if (result?.Value != null) - { - return JsonSerializer.Deserialize>(result.Value); - } - else - { - return []; - } + return await PerformInstanceCommandTaskWithResult>(instanceId, args, ManagementHubCommands.GetManagedItemRenewalPreview) ?? []; } internal async Task PerformManagedCertificateRequest(string instanceId, string managedCertId, AuthContext? currentAuthContext) From 9c313bafddcf80418c1f8a6b7d6ef8605138e84c Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Fri, 1 Nov 2024 16:23:44 +0800 Subject: [PATCH 254/328] Deployment: skip store for non-windows --- src/Certify.Core/Management/BindingDeploymentManager.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Certify.Core/Management/BindingDeploymentManager.cs b/src/Certify.Core/Management/BindingDeploymentManager.cs index 8a7f92ec0..73e23f1b8 100644 --- a/src/Certify.Core/Management/BindingDeploymentManager.cs +++ b/src/Certify.Core/Management/BindingDeploymentManager.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.InteropServices; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; using Certify.Management; @@ -54,6 +55,12 @@ public async Task> StoreAndDeploy(IBindingDeploymentTarget depl { var actions = new List(); + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + actions.Add(new ActionStep { Title = "Certificate Store and Deploy Skipped", Category = "CertificateStorage", Description = "Platform not supported for certificate store, skipping"}); + return actions; + } + var requestConfig = managedCertificate.RequestConfig; if (!isPreviewOnly) From df5960debdffc0401b0f3513ff23bf26e6130951 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Fri, 1 Nov 2024 16:24:30 +0800 Subject: [PATCH 255/328] PFX: use newer loader api on net9+ --- .../Management/CertificateManager.cs | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/src/Certify.Shared/Management/CertificateManager.cs b/src/Certify.Shared/Management/CertificateManager.cs index 13bcc8b3c..21f54afe9 100644 --- a/src/Certify.Shared/Management/CertificateManager.cs +++ b/src/Certify.Shared/Management/CertificateManager.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -372,7 +372,24 @@ public static async Task StoreCertificate( { // https://support.microsoft.com/en-gb/help/950090/installing-a-pfx-file-using-x509certificate-from-a-standard--net-appli X509Certificate2 certificate; + +#if NET9_0_OR_GREATER try + { + var pfxBytes = File.ReadAllBytes(pfxFile); + certificate = X509CertificateLoader.LoadPkcs12(pfxBytes, pwd, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable); + + } + catch (CryptographicException) + { + var pfxBytes = File.ReadAllBytes(pfxFile); + certificate = X509CertificateLoader.LoadPkcs12(pfxBytes, "", X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable); + + // success using blank pwd, continue with blank pwd + pwd = ""; + } +#else + try { certificate = new X509Certificate2(pfxFile, pwd, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable); } @@ -384,6 +401,7 @@ public static async Task StoreCertificate( // success using blank pwd, continue with blank pwd pwd = ""; } +#endif if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { @@ -790,7 +808,14 @@ private static string GetWindowsPrivateKeyLocation(string keyFileName) } public static X509Store GetMachineStore(string storeName = DEFAULT_STORE_NAME) => new X509Store(storeName, StoreLocation.LocalMachine); - public static X509Store GetUserStore(string storeName = DEFAULT_STORE_NAME) => new X509Store(storeName, StoreLocation.CurrentUser); + public static X509Store GetUserStore(string storeName = DEFAULT_STORE_NAME) + { +#if NET9_0_OR_GREATER + return new X509Store(storeName, StoreLocation.CurrentUser, OpenFlags.ReadWrite); +#else + return new X509Store(storeName, StoreLocation.CurrentUser); +#endif + } public static bool IsCertificateInStore(X509Certificate2 cert, string storeName = DEFAULT_STORE_NAME) { From 127d4b6d5e285b07c541cd0de0c171afdfcf918d Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Fri, 1 Nov 2024 16:57:03 +0800 Subject: [PATCH 256/328] API cleanup --- src/Certify.Client/CertifyApiClient.cs | 4 +-- src/Certify.Client/ICertifyClient.cs | 2 +- .../CertifyManager.ManagedCertificates.cs | 4 +-- .../CertifyManager.ManagementHub.cs | 4 +-- .../CertifyManager/ICertifyManager.cs | 4 +-- .../Challenges/DNS/DnsChallengeHelper.cs | 8 +++--- .../Certify.API.Public.cs | 26 +++++++++---------- .../Services/ManagementAPI.cs | 4 +-- src/Certify.SourceGenerators/ApiMethods.cs | 8 +++--- .../Tests/GetDnsProviderTests.cs | 8 +++--- 10 files changed, 36 insertions(+), 36 deletions(-) diff --git a/src/Certify.Client/CertifyApiClient.cs b/src/Certify.Client/CertifyApiClient.cs index e4c7dedc0..5c0dee43f 100644 --- a/src/Certify.Client/CertifyApiClient.cs +++ b/src/Certify.Client/CertifyApiClient.cs @@ -508,9 +508,9 @@ public async Task> PerformChallengeCleanup(ManagedCertificat var response = await PostAsync($"managedcertificates/challengecleanup", site, authContext); return JsonConvert.DeserializeObject>(await response.Content.ReadAsStringAsync()); } - public async Task> GetDnsProviderZones(string providerTypeId, string credentialsId, AuthContext authContext = null) + public async Task> GetDnsProviderZones(string providerTypeId, string credentialId, AuthContext authContext = null) { - var json = await FetchAsync($"managedcertificates/dnszones/{providerTypeId}/{credentialsId}", authContext); + var json = await FetchAsync($"managedcertificates/dnszones/{providerTypeId}/{credentialId}", authContext); return JsonConvert.DeserializeObject>(json); } diff --git a/src/Certify.Client/ICertifyClient.cs b/src/Certify.Client/ICertifyClient.cs index 28ac176fe..9dc04810c 100644 --- a/src/Certify.Client/ICertifyClient.cs +++ b/src/Certify.Client/ICertifyClient.cs @@ -96,7 +96,7 @@ public partial interface ICertifyInternalApiClient Task> TestChallengeConfiguration(ManagedCertificate site, AuthContext authContext = null); Task> PerformChallengeCleanup(ManagedCertificate site, AuthContext authContext = null); - Task> GetDnsProviderZones(string providerTypeId, string credentialsId, AuthContext authContext = null); + Task> GetDnsProviderZones(string providerTypeId, string credentialId, AuthContext authContext = null); Task> PreviewActions(ManagedCertificate site, AuthContext authContext = null); diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagedCertificates.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagedCertificates.cs index 29b00a230..d4ffccc97 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagedCertificates.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagedCertificates.cs @@ -527,11 +527,11 @@ public async Task> GeneratePreview(ManagedCertificate item) return await new PreviewManager().GeneratePreview(item, serverProvider, this, _credentialsManager); } - public async Task> GetDnsProviderZones(string providerTypeId, string credentialsId) + public async Task> GetDnsProviderZones(string providerTypeId, string credentialId) { var dnsHelper = new Core.Management.Challenges.DnsChallengeHelper(_credentialsManager); - var result = await dnsHelper.GetDnsProvider(providerTypeId, credentialsId, null, _credentialsManager, _serviceLog); + var result = await dnsHelper.GetDnsProvider(providerTypeId, credentialId, null, _credentialsManager, _serviceLog); if (result.Provider != null) { diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs index 7c0c09842..1c3da6b9d 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs @@ -201,9 +201,9 @@ private async Task _managementServerClient_OnGetCommandRe { var args = JsonSerializer.Deserialize[]>(arg.Value); var providerTypeArg = args.FirstOrDefault(a => a.Key == "providerTypeId"); - var credentialsIdArg = args.FirstOrDefault(a => a.Key == "credentialsId"); + var credentialIdArg = args.FirstOrDefault(a => a.Key == "credentialId"); - val = await GetDnsProviderZones(providerTypeArg.Value, credentialsIdArg.Value); + val = await GetDnsProviderZones(providerTypeArg.Value, credentialIdArg.Value); } else if (arg.CommandType == ManagementHubCommands.Reconnect) { diff --git a/src/Certify.Core/Management/CertifyManager/ICertifyManager.cs b/src/Certify.Core/Management/CertifyManager/ICertifyManager.cs index f145da2d3..d63fbb762 100644 --- a/src/Certify.Core/Management/CertifyManager/ICertifyManager.cs +++ b/src/Certify.Core/Management/CertifyManager/ICertifyManager.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading.Tasks; @@ -52,7 +52,7 @@ public interface ICertifyManager Task> TestChallenge(ILog log, ManagedCertificate managedCertificate, bool isPreviewMode, IProgress progress = null); Task> PerformChallengeCleanup(ILog log, ManagedCertificate managedCertificate, IProgress progress = null); Task> PerformServiceDiagnostics(); - Task> GetDnsProviderZones(string providerTypeId, string credentialsId); + Task> GetDnsProviderZones(string providerTypeId, string credentialId); Task UpdateCertificateAuthority(CertificateAuthority certificateAuthority); Task> GetCertificateAuthorities(); diff --git a/src/Certify.Core/Management/Challenges/DNS/DnsChallengeHelper.cs b/src/Certify.Core/Management/Challenges/DNS/DnsChallengeHelper.cs index dca4b4be0..a5417c6c5 100644 --- a/src/Certify.Core/Management/Challenges/DNS/DnsChallengeHelper.cs +++ b/src/Certify.Core/Management/Challenges/DNS/DnsChallengeHelper.cs @@ -42,10 +42,10 @@ public DnsChallengeHelper(ICredentialsManager credentialsManager) { _credentialsManager = credentialsManager; } - public async Task GetDnsProvider(string providerTypeId, string credentialsId, Dictionary parameters, ICredentialsManager credentialsManager, ILog log = null) + public async Task GetDnsProvider(string providerTypeId, string credentialId, Dictionary parameters, ICredentialsManager credentialsManager, ILog log = null) { var credentials = new Dictionary(); - if (!string.IsNullOrEmpty(credentialsId)) + if (!string.IsNullOrEmpty(credentialId)) { var failureResult = new DnsChallengeHelperResult( failureMsg: "DNS Challenge API Credentials could not be decrypted or no longer exists. The original user must be used for decryption." @@ -54,7 +54,7 @@ public async Task GetDnsProvider(string providerTypeId // decode credentials string array try { - credentials = await credentialsManager.GetUnlockedCredentialsDictionary(credentialsId); + credentials = await credentialsManager.GetUnlockedCredentialsDictionary(credentialId); if (credentials == null) { return failureResult; @@ -62,7 +62,7 @@ public async Task GetDnsProvider(string providerTypeId } catch (Exception exp) { - log?.Error(exp, $"The required stored credential {credentialsId} could not be found or could not be decrypted."); + log?.Error(exp, $"The required stored credential {credentialId} could not be found or could not be decrypted."); return failureResult; } } diff --git a/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs b/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs index 4e9f6d8a4..1fcb698d6 100644 --- a/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs +++ b/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs @@ -2718,8 +2718,8 @@ public virtual async System.Threading.Tasks.Task RemoveAcmeAccount var urlBuilder_ = new System.Text.StringBuilder(); if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); - // Operation Path: "internal/v1/challengeprovider/challengeprovider/{instanceId}" - urlBuilder_.Append("internal/v1/challengeprovider/challengeprovider/"); + // Operation Path: "internal/v1/challengeprovider/{instanceId}" + urlBuilder_.Append("internal/v1/challengeprovider/"); urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(instanceId, System.Globalization.CultureInfo.InvariantCulture))); PrepareRequest(client_, request_, urlBuilder_); @@ -2779,9 +2779,9 @@ public virtual async System.Threading.Tasks.Task RemoveAcmeAccount /// /// OK /// A server side error occurred. - public virtual System.Threading.Tasks.Task> GetDnsZonesAsync(string instanceId, string providerTypeId, string credentialsId) + public virtual System.Threading.Tasks.Task> GetDnsZonesAsync(string instanceId, string providerTypeId, string credentialId) { - return GetDnsZonesAsync(instanceId, providerTypeId, credentialsId, System.Threading.CancellationToken.None); + return GetDnsZonesAsync(instanceId, providerTypeId, credentialId, System.Threading.CancellationToken.None); } /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. @@ -2790,7 +2790,7 @@ public virtual async System.Threading.Tasks.Task RemoveAcmeAccount /// /// OK /// A server side error occurred. - public virtual async System.Threading.Tasks.Task> GetDnsZonesAsync(string instanceId, string providerTypeId, string credentialsId, System.Threading.CancellationToken cancellationToken) + public virtual async System.Threading.Tasks.Task> GetDnsZonesAsync(string instanceId, string providerTypeId, string credentialId, System.Threading.CancellationToken cancellationToken) { if (instanceId == null) throw new System.ArgumentNullException("instanceId"); @@ -2798,8 +2798,8 @@ public virtual async System.Threading.Tasks.Task RemoveAcmeAccount if (providerTypeId == null) throw new System.ArgumentNullException("providerTypeId"); - if (credentialsId == null) - throw new System.ArgumentNullException("credentialsId"); + if (credentialId == null) + throw new System.ArgumentNullException("credentialId"); var client_ = _httpClient; var disposeClient_ = false; @@ -2812,13 +2812,13 @@ public virtual async System.Threading.Tasks.Task RemoveAcmeAccount var urlBuilder_ = new System.Text.StringBuilder(); if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); - // Operation Path: "internal/v1/challengeprovider/challengeprovider/dnszones/{instanceId}/{providerTypeId}/{credentialsId}" - urlBuilder_.Append("internal/v1/challengeprovider/challengeprovider/dnszones/"); + // Operation Path: "internal/v1/challengeprovider/dnszones/{instanceId}/{providerTypeId}/{credentialId}" + urlBuilder_.Append("internal/v1/challengeprovider/dnszones/"); urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(instanceId, System.Globalization.CultureInfo.InvariantCulture))); urlBuilder_.Append('/'); urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(providerTypeId, System.Globalization.CultureInfo.InvariantCulture))); urlBuilder_.Append('/'); - urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(credentialsId, System.Globalization.CultureInfo.InvariantCulture))); + urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(credentialId, System.Globalization.CultureInfo.InvariantCulture))); PrepareRequest(client_, request_, urlBuilder_); @@ -3580,7 +3580,7 @@ public virtual async System.Threading.Tasks.Task DeleteStoredCrede /// /// OK /// A server side error occurred. - public virtual System.Threading.Tasks.Task GetSystemVersionAsync() + public virtual System.Threading.Tasks.Task GetSystemVersionAsync() { return GetSystemVersionAsync(System.Threading.CancellationToken.None); } @@ -3591,7 +3591,7 @@ public virtual System.Threading.Tasks.Task GetSystemVersionAsync() /// /// OK /// A server side error occurred. - public virtual async System.Threading.Tasks.Task GetSystemVersionAsync(System.Threading.CancellationToken cancellationToken) + public virtual async System.Threading.Tasks.Task GetSystemVersionAsync(System.Threading.CancellationToken cancellationToken) { var client_ = _httpClient; var disposeClient_ = false; @@ -3632,7 +3632,7 @@ public virtual async System.Threading.Tasks.Task GetSystemVersionAsync(S var status_ = (int)response_.StatusCode; if (status_ == 200) { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); diff --git a/src/Certify.Server/Certify.Server.Api.Public/Services/ManagementAPI.cs b/src/Certify.Server/Certify.Server.Api.Public/Services/ManagementAPI.cs index c38b1c7f8..c7e5a73ac 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Services/ManagementAPI.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Services/ManagementAPI.cs @@ -206,12 +206,12 @@ public async Task GetManagedCertificateSummary(AuthContext? curre return await PerformInstanceCommandTaskWithResult>(instanceId, args, ManagementHubCommands.GetChallengeProviders); } - public async Task?> GetDnsZones(string instanceId, string providerTypeId, string credentialsId, AuthContext? currentAuthContext) + public async Task?> GetDnsZones(string instanceId, string providerTypeId, string credentialId, AuthContext? currentAuthContext) { var args = new KeyValuePair[] { new("instanceId", instanceId), new("providerTypeId", providerTypeId), - new("credentialsId", credentialsId) + new("credentialId", credentialId) }; return await PerformInstanceCommandTaskWithResult>(instanceId, args, ManagementHubCommands.GetDnsZones); diff --git a/src/Certify.SourceGenerators/ApiMethods.cs b/src/Certify.SourceGenerators/ApiMethods.cs index c6781a81b..2901ff3c7 100644 --- a/src/Certify.SourceGenerators/ApiMethods.cs +++ b/src/Certify.SourceGenerators/ApiMethods.cs @@ -238,7 +238,7 @@ public static List GetApiDefinitions() OperationMethod = HttpGet, Comment = "Get Dns Challenge Providers", PublicAPIController = "ChallengeProvider", - PublicAPIRoute = "challengeprovider/{instanceId}", + PublicAPIRoute = "{instanceId}", ServiceAPIRoute = "managedcertificates/challengeapis/", ReturnType = "ICollection", UseManagementAPI = true, @@ -251,14 +251,14 @@ public static List GetApiDefinitions() OperationMethod = HttpGet, Comment = "Get List of Zones with the current DNS provider and credential", PublicAPIController = "ChallengeProvider", - PublicAPIRoute = "challengeprovider/dnszones/{instanceId}/{providerTypeId}/{credentialsId}", - ServiceAPIRoute = "managedcertificates/dnszones/{providerTypeId}/{credentialsId}", + PublicAPIRoute = "dnszones/{instanceId}/{providerTypeId}/{credentialId}", + ServiceAPIRoute = "managedcertificates/dnszones/{providerTypeId}/{credentialId}", ReturnType = "ICollection", UseManagementAPI = true, Params =new Dictionary{ { "instanceId", "string" } , { "providerTypeId", "string" }, - { "credentialsId", "string" } + { "credentialId", "string" } } }, new GeneratedAPI { diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/GetDnsProviderTests.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/GetDnsProviderTests.cs index 23e259469..ad8e4e1cc 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/GetDnsProviderTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/GetDnsProviderTests.cs @@ -24,12 +24,12 @@ public GetDnsProviderTests() dnsHelper = new DnsChallengeHelper(credentialsManager); } - [TestMethod, Description("Test Getting DNS Provider with empty CredentialsID")] - public async Task TestGetDnsProvidersEmptyCredentialsID() + [TestMethod, Description("Test Getting DNS Provider with empty CredentialID")] + public async Task TestGetDnsProvidersEmptyCredentialID() { var providerTypeId = "DNS01.Powershell"; - var credentialsId = ""; - var result = await dnsHelper.GetDnsProvider(providerTypeId, credentialsId, null, credentialsManager); + var credentialId = ""; + var result = await dnsHelper.GetDnsProvider(providerTypeId, credentialId, null, credentialsManager); // Assert Assert.AreEqual("DNS Challenge API Provider not set or could not load.", result.Result.Message); From 25960091ea294986a960ac68e0e48556e8adcd55 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Fri, 1 Nov 2024 16:57:17 +0800 Subject: [PATCH 257/328] Package updates --- .../DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj | 2 +- .../Certify.Core.Tests.Integration.csproj | 4 ++-- .../Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj | 4 ++-- .../Certify.Service.Tests.Integration.csproj | 4 ++-- .../Certify.UI.Tests.Integration.csproj | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj index 144fc8756..cc30c1abf 100644 --- a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj +++ b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj b/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj index 4f4015d9f..1f978d2fa 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj @@ -76,8 +76,8 @@ - - + + diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj index bfa193fe4..74f32ab94 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj @@ -109,8 +109,8 @@ - - + + diff --git a/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj b/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj index 312e3915a..1652ae8d7 100644 --- a/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj @@ -62,8 +62,8 @@ - - + + diff --git a/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj b/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj index a937fe7b2..987844c11 100644 --- a/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj @@ -58,8 +58,8 @@ - - + + From fa88b62cebe832e3424780fceaca2b03ffadc707 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Fri, 1 Nov 2024 16:57:30 +0800 Subject: [PATCH 258/328] API: update version info --- src/Certify.Models/API/VersionInfo.cs | 13 +++++++++++++ .../Controllers/v1/SystemController.cs | 6 +++--- 2 files changed, 16 insertions(+), 3 deletions(-) create mode 100644 src/Certify.Models/API/VersionInfo.cs diff --git a/src/Certify.Models/API/VersionInfo.cs b/src/Certify.Models/API/VersionInfo.cs new file mode 100644 index 000000000..cc96353d1 --- /dev/null +++ b/src/Certify.Models/API/VersionInfo.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Certify.Models.API +{ + public class VersionInfo + { + public string Product { get; set; } = string.Empty; + public string Version { get; set; } = string.Empty; + + } +} diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/SystemController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/SystemController.cs index 427c36757..b9dff4dcd 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/SystemController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/SystemController.cs @@ -32,12 +32,12 @@ public SystemController(ILogger logger, ICertifyInternalApiCli /// [HttpGet] [Route("version")] - [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Version))] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Models.API.VersionInfo))] public async Task GetSystemVersion() { var versionInfo = await _client.GetAppVersion(); - - return new OkObjectResult(Version.Parse(versionInfo)); + var result = new Models.API.VersionInfo { Version = versionInfo, Product = "Certify Management Hub" }; + return new OkObjectResult(result); } /// From ea8da2d956897045fa242769be382f62643f0973 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Fri, 1 Nov 2024 17:29:59 +0800 Subject: [PATCH 259/328] API route cleanup --- .../internal/StoredCredentialController.cs | 4 +--- src/Certify.SourceGenerators/ApiMethods.cs | 18 +++++++++--------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/StoredCredentialController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/StoredCredentialController.cs index b90043620..243eb339b 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/StoredCredentialController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/StoredCredentialController.cs @@ -1,7 +1,5 @@ using Certify.Client; using Certify.Server.Api.Public.Services; -using Microsoft.AspNetCore.Authentication.JwtBearer; -using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace Certify.Server.Api.Public.Controllers @@ -10,7 +8,7 @@ namespace Certify.Server.Api.Public.Controllers /// Internal API for extended certificate management. Not intended for general use. /// [ApiController] - [Route("internal/v1/[controller]")] + [Route("internal/v1/credentials")] public partial class StoredCredentialController : ApiControllerBase { diff --git a/src/Certify.SourceGenerators/ApiMethods.cs b/src/Certify.SourceGenerators/ApiMethods.cs index 2901ff3c7..1f4d790f2 100644 --- a/src/Certify.SourceGenerators/ApiMethods.cs +++ b/src/Certify.SourceGenerators/ApiMethods.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using SourceGenerator; @@ -151,7 +151,7 @@ public static List GetApiDefinitions() Comment = "Get All Acme Accounts", UseManagementAPI = true, PublicAPIController = "CertificateAuthority", - PublicAPIRoute = "accounts/{instanceId}", + PublicAPIRoute = "{instanceId}/accounts/", ServiceAPIRoute = "accounts", ReturnType = "ICollection", Params =new Dictionary{ { "instanceId", "string" } } @@ -195,17 +195,17 @@ public static List GetApiDefinitions() OperationMethod = HttpDelete, Comment = "Remove ACME Account", PublicAPIController = "CertificateAuthority", - PublicAPIRoute = "accounts/{storageKey}/{deactivate}", + PublicAPIRoute = "{instanceId}/accounts/{storageKey}/{deactivate}", ServiceAPIRoute = "accounts/remove/{storageKey}/{deactivate}", ReturnType = "Models.Config.ActionResult", - Params =new Dictionary{{ "storageKey", "string" }, { "deactivate", "bool" } } + Params =new Dictionary{ { "instanceId", "string" }, { "storageKey", "string" }, { "deactivate", "bool" } } }, new GeneratedAPI { OperationName = "GetStoredCredentials", OperationMethod = HttpGet, Comment = "Get List of Stored Credentials", PublicAPIController = "StoredCredential", - PublicAPIRoute = "credentials/{instanceId}", + PublicAPIRoute = "{instanceId}", ServiceAPIRoute = "credentials", ReturnType = "ICollection", UseManagementAPI = true, @@ -216,7 +216,7 @@ public static List GetApiDefinitions() OperationMethod = HttpPost, Comment = "Add/Update Stored Credential", PublicAPIController = "StoredCredential", - PublicAPIRoute = "credentials/{instanceId}", + PublicAPIRoute = "{instanceId}", ServiceAPIRoute = "credentials", ReturnType = "Models.Config.ActionResult", UseManagementAPI = true, @@ -227,7 +227,7 @@ public static List GetApiDefinitions() OperationMethod = HttpDelete, Comment = "Remove Stored Credential", PublicAPIController = "StoredCredential", - PublicAPIRoute = "credentials/{instanceId}/{storageKey}", + PublicAPIRoute = "{instanceId}/{storageKey}", ServiceAPIRoute = "credentials", ReturnType = "Models.Config.ActionResult", UseManagementAPI = true, @@ -251,7 +251,7 @@ public static List GetApiDefinitions() OperationMethod = HttpGet, Comment = "Get List of Zones with the current DNS provider and credential", PublicAPIController = "ChallengeProvider", - PublicAPIRoute = "dnszones/{instanceId}/{providerTypeId}/{credentialId}", + PublicAPIRoute = "{instanceId}/dnszones/{providerTypeId}/{credentialId}", ServiceAPIRoute = "managedcertificates/dnszones/{providerTypeId}/{credentialId}", ReturnType = "ICollection", UseManagementAPI = true, @@ -287,7 +287,7 @@ public static List GetApiDefinitions() OperationMethod = HttpDelete, Comment = "Remove Managed Certificate", PublicAPIController = "Certificate", - PublicAPIRoute = "settings/{instanceId}/{managedCertId}", + PublicAPIRoute = "{instanceId}/settings/{managedCertId}", UseManagementAPI = true, ServiceAPIRoute = "managedcertificates/delete/{managedCertId}", ReturnType = "bool", From e52b7c67dbe02f9883b1d52bf89d28dd87bff8eb Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Sat, 2 Nov 2024 13:45:15 +0800 Subject: [PATCH 260/328] Cleanup --- src/Certify.CLI/CertifyCLI,StoredCredentials.cs | 2 -- src/Certify.CLI/Program.cs | 2 +- src/Certify.Client/ManagementServerClient.cs | 2 +- src/Certify.Core/Management/BindingDeploymentManager.cs | 2 +- src/Certify.Models/API/VersionInfo.cs | 6 +----- .../Controllers/internal/ChallengeProviderController.cs | 3 --- src/Certify.Server/Certify.Server.Api.Public/Startup.cs | 2 +- src/Certify.Shared/Management/CertificateManager.cs | 4 ++-- src/Certify.SourceGenerators/PublicAPISourceGenerator.cs | 2 +- .../Certify.Core.Tests.Integration/DeploymentTaskTests.cs | 2 +- .../Certify.Core.Tests.Unit/Tests/MiscTests.cs | 2 +- .../Certify.Core.Tests.Unit/Tests/RenewalRequiredTests.cs | 2 +- src/Certify.UI.Shared/Windows/EditAccountDialog.xaml.cs | 1 - 13 files changed, 11 insertions(+), 21 deletions(-) diff --git a/src/Certify.CLI/CertifyCLI,StoredCredentials.cs b/src/Certify.CLI/CertifyCLI,StoredCredentials.cs index 4f3be5af4..11d4a9620 100644 --- a/src/Certify.CLI/CertifyCLI,StoredCredentials.cs +++ b/src/Certify.CLI/CertifyCLI,StoredCredentials.cs @@ -1,8 +1,6 @@ using System; -using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using Certify.Models; using Certify.Models.Config; using Newtonsoft.Json; diff --git a/src/Certify.CLI/Program.cs b/src/Certify.CLI/Program.cs index f732077ec..0cd9a10b3 100644 --- a/src/Certify.CLI/Program.cs +++ b/src/Certify.CLI/Program.cs @@ -178,7 +178,7 @@ private static async Task Main(string[] args) { await p.UpdateStoredCredential(args); } - + if (command == "credential" && args.Contains("list")) { await p.ListStoredCredentials(args); diff --git a/src/Certify.Client/ManagementServerClient.cs b/src/Certify.Client/ManagementServerClient.cs index 543c3464c..4c49cb29e 100644 --- a/src/Certify.Client/ManagementServerClient.cs +++ b/src/Certify.Client/ManagementServerClient.cs @@ -67,7 +67,7 @@ public async Task ConnectAsync() }; opts.UseStatefulReconnect = true; - + }) .WithAutomaticReconnect() .AddMessagePackProtocol() diff --git a/src/Certify.Core/Management/BindingDeploymentManager.cs b/src/Certify.Core/Management/BindingDeploymentManager.cs index 73e23f1b8..116718b6d 100644 --- a/src/Certify.Core/Management/BindingDeploymentManager.cs +++ b/src/Certify.Core/Management/BindingDeploymentManager.cs @@ -57,7 +57,7 @@ public async Task> StoreAndDeploy(IBindingDeploymentTarget depl if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - actions.Add(new ActionStep { Title = "Certificate Store and Deploy Skipped", Category = "CertificateStorage", Description = "Platform not supported for certificate store, skipping"}); + actions.Add(new ActionStep { Title = "Certificate Store and Deploy Skipped", Category = "CertificateStorage", Description = "Platform not supported for certificate store, skipping" }); return actions; } diff --git a/src/Certify.Models/API/VersionInfo.cs b/src/Certify.Models/API/VersionInfo.cs index cc96353d1..65e630d26 100644 --- a/src/Certify.Models/API/VersionInfo.cs +++ b/src/Certify.Models/API/VersionInfo.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Certify.Models.API +namespace Certify.Models.API { public class VersionInfo { diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/ChallengeProviderController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/ChallengeProviderController.cs index 1bddf16c9..0d6c4f449 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/ChallengeProviderController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/ChallengeProviderController.cs @@ -1,8 +1,5 @@ using Certify.Client; -using Certify.Models.Providers; using Certify.Server.Api.Public.Services; -using Microsoft.AspNetCore.Authentication.JwtBearer; -using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace Certify.Server.Api.Public.Controllers diff --git a/src/Certify.Server/Certify.Server.Api.Public/Startup.cs b/src/Certify.Server/Certify.Server.Api.Public/Startup.cs index 1504c467f..991b23bf9 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Startup.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Startup.cs @@ -66,7 +66,7 @@ public void ConfigureServices(IServiceCollection services) .AddSignalR(opt => opt.MaximumReceiveMessageSize = null) .AddMessagePackProtocol(); - + services.AddResponseCompression(opts => { opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat( diff --git a/src/Certify.Shared/Management/CertificateManager.cs b/src/Certify.Shared/Management/CertificateManager.cs index 21f54afe9..b4d7802bb 100644 --- a/src/Certify.Shared/Management/CertificateManager.cs +++ b/src/Certify.Shared/Management/CertificateManager.cs @@ -378,7 +378,7 @@ public static async Task StoreCertificate( { var pfxBytes = File.ReadAllBytes(pfxFile); certificate = X509CertificateLoader.LoadPkcs12(pfxBytes, pwd, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable); - + } catch (CryptographicException) { @@ -389,7 +389,7 @@ public static async Task StoreCertificate( pwd = ""; } #else - try + try { certificate = new X509Certificate2(pfxFile, pwd, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable); } diff --git a/src/Certify.SourceGenerators/PublicAPISourceGenerator.cs b/src/Certify.SourceGenerators/PublicAPISourceGenerator.cs index f3f3f99e4..b98821814 100644 --- a/src/Certify.SourceGenerators/PublicAPISourceGenerator.cs +++ b/src/Certify.SourceGenerators/PublicAPISourceGenerator.cs @@ -234,7 +234,7 @@ public void Initialize(GeneratorInitializationContext context) // then add a watch on if (!Debugger.IsAttached) { - // Debugger.Launch(); + // Debugger.Launch(); } #endif } diff --git a/src/Certify.Tests/Certify.Core.Tests.Integration/DeploymentTaskTests.cs b/src/Certify.Tests/Certify.Core.Tests.Integration/DeploymentTaskTests.cs index f831052d7..01ef472e3 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Integration/DeploymentTaskTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Integration/DeploymentTaskTests.cs @@ -179,7 +179,7 @@ public async Task TestRunPostTasksWithTaskFailTrigger() //ensure 3rd task runs because task 1 failed var expectedFailureTaskStepKey = managedCertificate.PostRequestTasks.First(t => t.TaskName == "Post Task 3 (on task fail)").Id; - + var skippedStep = result .Actions.Find(s => s.Key == "PostRequestTasks") .Substeps.Find(s => s.Key == expectedFailureTaskStepKey); diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/MiscTests.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/MiscTests.cs index 3e66b640b..80bd0bc08 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/MiscTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/MiscTests.cs @@ -119,7 +119,7 @@ public void TestDemoDataGeneration() [TestMethod, Description("Source gen test")] public void TestSourceGen() { - var typeName = SourceGenerators.ApiMethods.GetFormattedTypeName(typeof(string)); + var typeName = SourceGenerators.ApiMethods.GetFormattedTypeName(typeof(string)); Assert.AreEqual("System.String", typeName); diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/RenewalRequiredTests.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/RenewalRequiredTests.cs index d4fc47fa7..03a91b3e7 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/RenewalRequiredTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/RenewalRequiredTests.cs @@ -77,7 +77,7 @@ var renewalDueCheck Assert.AreEqual(renewalDueCheck.HoldHrs, 48, "Hold should be for 48 Hrs"); managedCertificate.DateLastRenewalAttempt = DateTimeOffset.UtcNow.AddHours(-49); - + // perform check as if last attempt was over 48rs ago, item should require renewal and not be on hold renewalDueCheck = ManagedCertificate.CalculateNextRenewalAttempt(managedCertificate, renewalPeriodDays, renewalIntervalMode, true); diff --git a/src/Certify.UI.Shared/Windows/EditAccountDialog.xaml.cs b/src/Certify.UI.Shared/Windows/EditAccountDialog.xaml.cs index 18f1e05cf..b281ac576 100644 --- a/src/Certify.UI.Shared/Windows/EditAccountDialog.xaml.cs +++ b/src/Certify.UI.Shared/Windows/EditAccountDialog.xaml.cs @@ -8,7 +8,6 @@ using System.Windows.Data; using System.Windows.Input; using Certify.Models; -using Org.BouncyCastle.Asn1.Pkcs; using Org.BouncyCastle.Crypto.EC; using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Security; From 208feb730e3ec960c6a96b6331a8d112f7e033a1 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Sat, 2 Nov 2024 16:31:18 +0800 Subject: [PATCH 261/328] Implement per instance CA edit --- src/Certify.Client/CertifyApiClient.cs | 5 - src/Certify.Client/ICertifyClient.cs | 1 - .../CertifyManager.ManagementHub.cs | 18 +- .../API/Management/ManagementHubMessages.cs | 8 +- .../Certify.API.Public.cs | 302 ++++++++++-------- .../CertificateAuthorityController.cs | 15 - .../Services/ManagementAPI.cs | 24 +- src/Certify.SourceGenerators/ApiMethods.cs | 37 ++- .../AppViewModel/AppViewModel.Config.cs | 4 +- 9 files changed, 229 insertions(+), 185 deletions(-) diff --git a/src/Certify.Client/CertifyApiClient.cs b/src/Certify.Client/CertifyApiClient.cs index 5c0dee43f..014e0603d 100644 --- a/src/Certify.Client/CertifyApiClient.cs +++ b/src/Certify.Client/CertifyApiClient.cs @@ -619,11 +619,6 @@ public async Task> GetCertificateAuthorities(AuthCont var result = await FetchAsync("accounts/authorities", authContext); return JsonConvert.DeserializeObject>(result); } - public async Task UpdateCertificateAuthority(CertificateAuthority ca, AuthContext authContext = null) - { - var result = await PostAsync("accounts/authorities", ca, authContext); - return JsonConvert.DeserializeObject(await result.Content.ReadAsStringAsync()); - } public async Task DeleteCertificateAuthority(string id, AuthContext authContext = null) { diff --git a/src/Certify.Client/ICertifyClient.cs b/src/Certify.Client/ICertifyClient.cs index 9dc04810c..e73fba9ba 100644 --- a/src/Certify.Client/ICertifyClient.cs +++ b/src/Certify.Client/ICertifyClient.cs @@ -116,7 +116,6 @@ public partial interface ICertifyInternalApiClient #region Accounts Task> GetCertificateAuthorities(AuthContext authContext = null); - Task UpdateCertificateAuthority(CertificateAuthority ca, AuthContext authContext = null); Task DeleteCertificateAuthority(string id, AuthContext authContext = null); Task> GetAccounts(AuthContext authContext = null); Task AddAccount(ContactRegistration contact, AuthContext authContext = null); diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs index 1c3da6b9d..225350aa3 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs @@ -123,7 +123,7 @@ private async Task _managementServerClient_OnGetCommandRe val = await UpdateManagedCertificate(managedCert); } - else if (arg.CommandType == ManagementHubCommands.DeleteManagedItem) + else if (arg.CommandType == ManagementHubCommands.RemoveDeleteManagedItem) { // delete a single managed item var args = JsonSerializer.Deserialize[]>(arg.Value); @@ -163,6 +163,18 @@ private async Task _managementServerClient_OnGetCommandRe val = true; } + else if (arg.CommandType == ManagementHubCommands.GetCertificateAuthorities) + { + val = await GetCertificateAuthorities(); + } + else if (arg.CommandType == ManagementHubCommands.UpdateCertificateAuthority) + { + var args = JsonSerializer.Deserialize[]>(arg.Value); + var itemArg = args.FirstOrDefault(a => a.Key == "certificateAuthority"); + var item = JsonSerializer.Deserialize(itemArg.Value); + + val = await UpdateCertificateAuthority(item); + } else if (arg.CommandType == ManagementHubCommands.GetAcmeAccounts) { val = await GetAccountRegistrations(); @@ -187,7 +199,7 @@ private async Task _managementServerClient_OnGetCommandRe val = await _credentialsManager.Update(storedCredential); } - else if (arg.CommandType == ManagementHubCommands.DeleteStoredCredential) + else if (arg.CommandType == ManagementHubCommands.RemoveStoredCredential) { var args = JsonSerializer.Deserialize[]>(arg.Value); var itemArg = args.FirstOrDefault(a => a.Key == "storageKey"); @@ -202,7 +214,7 @@ private async Task _managementServerClient_OnGetCommandRe var args = JsonSerializer.Deserialize[]>(arg.Value); var providerTypeArg = args.FirstOrDefault(a => a.Key == "providerTypeId"); var credentialIdArg = args.FirstOrDefault(a => a.Key == "credentialId"); - + val = await GetDnsProviderZones(providerTypeArg.Value, credentialIdArg.Value); } else if (arg.CommandType == ManagementHubCommands.Reconnect) diff --git a/src/Certify.Models/API/Management/ManagementHubMessages.cs b/src/Certify.Models/API/Management/ManagementHubMessages.cs index 9aa6232fa..108e35042 100644 --- a/src/Certify.Models/API/Management/ManagementHubMessages.cs +++ b/src/Certify.Models/API/Management/ManagementHubMessages.cs @@ -20,16 +20,20 @@ public class ManagementHubCommands public const string GetManagedItemRenewalPreview = "GetManagedItemRenewalPreview"; public const string UpdateManagedItem = "UpdateManagedItem"; - public const string DeleteManagedItem = "DeleteManagedItem"; + public const string RemoveDeleteManagedItem = "RemoveManagedItem"; public const string TestManagedItemConfiguration = "TestManagedItemConfiguration"; public const string PerformManagedItemRequest = "PerformManagedItemRequest"; + public const string GetCertificateAuthorities = "GetCertificateAuthorities"; + public const string UpdateCertificateAuthority = "UpdateCertificateAuthority"; + public const string RemoveCertificateAuthority = "RemoveCertificateAuthority"; + public const string GetAcmeAccounts = "GetAcmeAccounts"; public const string AddAcmeAccount = "AddAcmeAccount"; public const string GetStoredCredentials = "GetStoredCredentials"; public const string UpdateStoredCredential = "UpdateStoredCredential"; - public const string DeleteStoredCredential = "DeleteStoredCredential"; + public const string RemoveStoredCredential = "RemoveStoredCredential"; public const string GetChallengeProviders = "GetChallengeProviders"; public const string GetDnsZones = "GetDnsZones"; diff --git a/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs b/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs index 1fcb698d6..cb6cf464c 100644 --- a/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs +++ b/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs @@ -690,22 +690,22 @@ public virtual async System.Threading.Tasks.Task AddSecurityPrinci } /// - /// Delete security principle [Generated by Certify.SourceGenerators] + /// Remove security principle [Generated by Certify.SourceGenerators] /// /// OK /// A server side error occurred. - public virtual System.Threading.Tasks.Task DeleteSecurityPrincipleAsync(string id) + public virtual System.Threading.Tasks.Task RemoveSecurityPrincipleAsync(string id) { - return DeleteSecurityPrincipleAsync(id, System.Threading.CancellationToken.None); + return RemoveSecurityPrincipleAsync(id, System.Threading.CancellationToken.None); } /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Delete security principle [Generated by Certify.SourceGenerators] + /// Remove security principle [Generated by Certify.SourceGenerators] /// /// OK /// A server side error occurred. - public virtual async System.Threading.Tasks.Task DeleteSecurityPrincipleAsync(string id, System.Threading.CancellationToken cancellationToken) + public virtual async System.Threading.Tasks.Task RemoveSecurityPrincipleAsync(string id, System.Threading.CancellationToken cancellationToken) { var client_ = _httpClient; var disposeClient_ = false; @@ -1690,99 +1690,6 @@ public virtual async System.Threading.Tasks.Task GetManagedC } } - /// - /// Remove Managed Certificate [Generated by Certify.SourceGenerators] - /// - /// OK - /// A server side error occurred. - public virtual System.Threading.Tasks.Task RemoveManagedCertificateAsync(string instanceId, string managedCertId) - { - return RemoveManagedCertificateAsync(instanceId, managedCertId, System.Threading.CancellationToken.None); - } - - /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. - /// - /// Remove Managed Certificate [Generated by Certify.SourceGenerators] - /// - /// OK - /// A server side error occurred. - public virtual async System.Threading.Tasks.Task RemoveManagedCertificateAsync(string instanceId, string managedCertId, System.Threading.CancellationToken cancellationToken) - { - if (instanceId == null) - throw new System.ArgumentNullException("instanceId"); - - if (managedCertId == null) - throw new System.ArgumentNullException("managedCertId"); - - var client_ = _httpClient; - var disposeClient_ = false; - try - { - using (var request_ = new System.Net.Http.HttpRequestMessage()) - { - request_.Method = new System.Net.Http.HttpMethod("DELETE"); - request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); - - var urlBuilder_ = new System.Text.StringBuilder(); - if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); - // Operation Path: "api/v1/certificate/settings/{instanceId}/{managedCertId}" - urlBuilder_.Append("api/v1/certificate/settings/"); - urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(instanceId, System.Globalization.CultureInfo.InvariantCulture))); - urlBuilder_.Append('/'); - urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(managedCertId, System.Globalization.CultureInfo.InvariantCulture))); - - PrepareRequest(client_, request_, urlBuilder_); - - var url_ = urlBuilder_.ToString(); - request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - - PrepareRequest(client_, request_, url_); - - var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); - var disposeResponse_ = true; - try - { - var headers_ = new System.Collections.Generic.Dictionary>(); - foreach (var item_ in response_.Headers) - headers_[item_.Key] = item_.Value; - if (response_.Content != null && response_.Content.Headers != null) - { - foreach (var item_ in response_.Content.Headers) - headers_[item_.Key] = item_.Value; - } - - ProcessResponse(client_, response_); - - var status_ = (int)response_.StatusCode; - if (status_ == 200) - { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); - if (objectResponse_.Object == null) - { - throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); - } - return objectResponse_.Object; - } - else - { - var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); - throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); - } - } - finally - { - if (disposeResponse_) - response_.Dispose(); - } - } - } - finally - { - if (disposeClient_) - client_.Dispose(); - } - } - /// /// Add/update the full settings for a specific managed certificate /// @@ -2154,36 +2061,45 @@ public virtual async System.Threading.Tasks.Task PerformRene } /// - /// Get list of known certificate authorities + /// Remove Managed Certificate [Generated by Certify.SourceGenerators] /// /// OK /// A server side error occurred. - public virtual System.Threading.Tasks.Task> GetCertificateAuthoritiesAsync() + public virtual System.Threading.Tasks.Task RemoveManagedCertificateAsync(string instanceId, string managedCertId) { - return GetCertificateAuthoritiesAsync(System.Threading.CancellationToken.None); + return RemoveManagedCertificateAsync(instanceId, managedCertId, System.Threading.CancellationToken.None); } /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Get list of known certificate authorities + /// Remove Managed Certificate [Generated by Certify.SourceGenerators] /// /// OK /// A server side error occurred. - public virtual async System.Threading.Tasks.Task> GetCertificateAuthoritiesAsync(System.Threading.CancellationToken cancellationToken) + public virtual async System.Threading.Tasks.Task RemoveManagedCertificateAsync(string instanceId, string managedCertId, System.Threading.CancellationToken cancellationToken) { + if (instanceId == null) + throw new System.ArgumentNullException("instanceId"); + + if (managedCertId == null) + throw new System.ArgumentNullException("managedCertId"); + var client_ = _httpClient; var disposeClient_ = false; try { using (var request_ = new System.Net.Http.HttpRequestMessage()) { - request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Method = new System.Net.Http.HttpMethod("DELETE"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); var urlBuilder_ = new System.Text.StringBuilder(); if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); - // Operation Path: "internal/v1/certificateauthority" - urlBuilder_.Append("internal/v1/certificateauthority"); + // Operation Path: "api/v1/certificate/{instanceId}/settings/{managedCertId}" + urlBuilder_.Append("api/v1/certificate/"); + urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(instanceId, System.Globalization.CultureInfo.InvariantCulture))); + urlBuilder_.Append("/settings/"); + urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(managedCertId, System.Globalization.CultureInfo.InvariantCulture))); PrepareRequest(client_, request_, urlBuilder_); @@ -2210,7 +2126,7 @@ public virtual async System.Threading.Tasks.Task PerformRene var status_ = (int)response_.StatusCode; if (status_ == 200) { - var objectResponse_ = await ReadObjectResponseAsync>(response_, headers_, cancellationToken).ConfigureAwait(false); + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); @@ -2269,9 +2185,10 @@ public virtual async System.Threading.Tasks.Task PerformRene var urlBuilder_ = new System.Text.StringBuilder(); if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); - // Operation Path: "internal/v1/certificateauthority/accounts/{instanceId}" - urlBuilder_.Append("internal/v1/certificateauthority/accounts/"); + // Operation Path: "internal/v1/certificateauthority/{instanceId}/accounts" + urlBuilder_.Append("internal/v1/certificateauthority/"); urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(instanceId, System.Globalization.CultureInfo.InvariantCulture))); + urlBuilder_.Append("/accounts"); PrepareRequest(client_, request_, urlBuilder_); @@ -2361,9 +2278,10 @@ public virtual async System.Threading.Tasks.Task AddAcmeAccountAsy var urlBuilder_ = new System.Text.StringBuilder(); if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); - // Operation Path: "internal/v1/certificateauthority/account/{instanceId}" - urlBuilder_.Append("internal/v1/certificateauthority/account/"); + // Operation Path: "internal/v1/certificateauthority/{instanceId}/account" + urlBuilder_.Append("internal/v1/certificateauthority/"); urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(instanceId, System.Globalization.CultureInfo.InvariantCulture))); + urlBuilder_.Append("/account"); PrepareRequest(client_, request_, urlBuilder_); @@ -2418,23 +2336,115 @@ public virtual async System.Threading.Tasks.Task AddAcmeAccountAsy } /// - /// Add New Certificate Authority [Generated by Certify.SourceGenerators] + /// Get list of defined Certificate Authorities [Generated by Certify.SourceGenerators] /// /// OK /// A server side error occurred. - public virtual System.Threading.Tasks.Task AddCertificateAuthorityAsync(CertificateAuthority body) + public virtual System.Threading.Tasks.Task> GetCertificateAuthoritiesAsync(string instanceId) { - return AddCertificateAuthorityAsync(body, System.Threading.CancellationToken.None); + return GetCertificateAuthoritiesAsync(instanceId, System.Threading.CancellationToken.None); } /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Add New Certificate Authority [Generated by Certify.SourceGenerators] + /// Get list of defined Certificate Authorities [Generated by Certify.SourceGenerators] /// /// OK /// A server side error occurred. - public virtual async System.Threading.Tasks.Task AddCertificateAuthorityAsync(CertificateAuthority body, System.Threading.CancellationToken cancellationToken) + public virtual async System.Threading.Tasks.Task> GetCertificateAuthoritiesAsync(string instanceId, System.Threading.CancellationToken cancellationToken) { + if (instanceId == null) + throw new System.ArgumentNullException("instanceId"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); + // Operation Path: "internal/v1/certificateauthority/{instanceId}/authority" + urlBuilder_.Append("internal/v1/certificateauthority/"); + urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(instanceId, System.Globalization.CultureInfo.InvariantCulture))); + urlBuilder_.Append("/authority"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync>(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// + /// Add/Update Certificate Authority [Generated by Certify.SourceGenerators] + /// + /// OK + /// A server side error occurred. + public virtual System.Threading.Tasks.Task UpdateCertificateAuthorityAsync(string instanceId, CertificateAuthority body) + { + return UpdateCertificateAuthorityAsync(instanceId, body, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Add/Update Certificate Authority [Generated by Certify.SourceGenerators] + /// + /// OK + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task UpdateCertificateAuthorityAsync(string instanceId, CertificateAuthority body, System.Threading.CancellationToken cancellationToken) + { + if (instanceId == null) + throw new System.ArgumentNullException("instanceId"); + var client_ = _httpClient; var disposeClient_ = false; try @@ -2450,8 +2460,10 @@ public virtual async System.Threading.Tasks.Task AddCertificateAut var urlBuilder_ = new System.Text.StringBuilder(); if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); - // Operation Path: "internal/v1/certificateauthority/authority" - urlBuilder_.Append("internal/v1/certificateauthority/authority"); + // Operation Path: "internal/v1/certificateauthority/{instanceId}/authority" + urlBuilder_.Append("internal/v1/certificateauthority/"); + urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(instanceId, System.Globalization.CultureInfo.InvariantCulture))); + urlBuilder_.Append("/authority"); PrepareRequest(client_, request_, urlBuilder_); @@ -2510,9 +2522,9 @@ public virtual async System.Threading.Tasks.Task AddCertificateAut /// /// OK /// A server side error occurred. - public virtual System.Threading.Tasks.Task RemoveCertificateAuthorityAsync(string id) + public virtual System.Threading.Tasks.Task RemoveCertificateAuthorityAsync(string instanceId, string id) { - return RemoveCertificateAuthorityAsync(id, System.Threading.CancellationToken.None); + return RemoveCertificateAuthorityAsync(instanceId, id, System.Threading.CancellationToken.None); } /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. @@ -2521,8 +2533,11 @@ public virtual System.Threading.Tasks.Task RemoveCertificateAuthor /// /// OK /// A server side error occurred. - public virtual async System.Threading.Tasks.Task RemoveCertificateAuthorityAsync(string id, System.Threading.CancellationToken cancellationToken) + public virtual async System.Threading.Tasks.Task RemoveCertificateAuthorityAsync(string instanceId, string id, System.Threading.CancellationToken cancellationToken) { + if (instanceId == null) + throw new System.ArgumentNullException("instanceId"); + if (id == null) throw new System.ArgumentNullException("id"); @@ -2537,8 +2552,10 @@ public virtual async System.Threading.Tasks.Task RemoveCertificate var urlBuilder_ = new System.Text.StringBuilder(); if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); - // Operation Path: "internal/v1/certificateauthority/authority/{id}" - urlBuilder_.Append("internal/v1/certificateauthority/authority/"); + // Operation Path: "internal/v1/certificateauthority/{instanceId}/authority/{id}" + urlBuilder_.Append("internal/v1/certificateauthority/"); + urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(instanceId, System.Globalization.CultureInfo.InvariantCulture))); + urlBuilder_.Append("/authority/"); urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(id, System.Globalization.CultureInfo.InvariantCulture))); PrepareRequest(client_, request_, urlBuilder_); @@ -2598,9 +2615,9 @@ public virtual async System.Threading.Tasks.Task RemoveCertificate /// /// OK /// A server side error occurred. - public virtual System.Threading.Tasks.Task RemoveAcmeAccountAsync(string storageKey, bool deactivate) + public virtual System.Threading.Tasks.Task RemoveAcmeAccountAsync(string instanceId, string storageKey, bool deactivate) { - return RemoveAcmeAccountAsync(storageKey, deactivate, System.Threading.CancellationToken.None); + return RemoveAcmeAccountAsync(instanceId, storageKey, deactivate, System.Threading.CancellationToken.None); } /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. @@ -2609,8 +2626,11 @@ public virtual System.Threading.Tasks.Task RemoveAcmeAccountAsync( /// /// OK /// A server side error occurred. - public virtual async System.Threading.Tasks.Task RemoveAcmeAccountAsync(string storageKey, bool deactivate, System.Threading.CancellationToken cancellationToken) + public virtual async System.Threading.Tasks.Task RemoveAcmeAccountAsync(string instanceId, string storageKey, bool deactivate, System.Threading.CancellationToken cancellationToken) { + if (instanceId == null) + throw new System.ArgumentNullException("instanceId"); + if (storageKey == null) throw new System.ArgumentNullException("storageKey"); @@ -2628,8 +2648,10 @@ public virtual async System.Threading.Tasks.Task RemoveAcmeAccount var urlBuilder_ = new System.Text.StringBuilder(); if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); - // Operation Path: "internal/v1/certificateauthority/accounts/{storageKey}/{deactivate}" - urlBuilder_.Append("internal/v1/certificateauthority/accounts/"); + // Operation Path: "internal/v1/certificateauthority/{instanceId}/accounts/{storageKey}/{deactivate}" + urlBuilder_.Append("internal/v1/certificateauthority/"); + urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(instanceId, System.Globalization.CultureInfo.InvariantCulture))); + urlBuilder_.Append("/accounts/"); urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(storageKey, System.Globalization.CultureInfo.InvariantCulture))); urlBuilder_.Append('/'); urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(deactivate, System.Globalization.CultureInfo.InvariantCulture))); @@ -2812,10 +2834,10 @@ public virtual async System.Threading.Tasks.Task RemoveAcmeAccount var urlBuilder_ = new System.Text.StringBuilder(); if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); - // Operation Path: "internal/v1/challengeprovider/dnszones/{instanceId}/{providerTypeId}/{credentialId}" - urlBuilder_.Append("internal/v1/challengeprovider/dnszones/"); + // Operation Path: "internal/v1/challengeprovider/{instanceId}/dnszones/{providerTypeId}/{credentialId}" + urlBuilder_.Append("internal/v1/challengeprovider/"); urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(instanceId, System.Globalization.CultureInfo.InvariantCulture))); - urlBuilder_.Append('/'); + urlBuilder_.Append("/dnszones/"); urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(providerTypeId, System.Globalization.CultureInfo.InvariantCulture))); urlBuilder_.Append('/'); urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(credentialId, System.Globalization.CultureInfo.InvariantCulture))); @@ -3334,8 +3356,8 @@ public virtual async System.Threading.Tasks.Task UpdateStoredCrede var urlBuilder_ = new System.Text.StringBuilder(); if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); - // Operation Path: "internal/v1/storedcredential/credentials/{instanceId}" - urlBuilder_.Append("internal/v1/storedcredential/credentials/"); + // Operation Path: "internal/v1/credentials/{instanceId}" + urlBuilder_.Append("internal/v1/credentials/"); urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(instanceId, System.Globalization.CultureInfo.InvariantCulture))); PrepareRequest(client_, request_, urlBuilder_); @@ -3487,9 +3509,9 @@ public virtual async System.Threading.Tasks.Task UpdateStoredCrede /// /// OK /// A server side error occurred. - public virtual System.Threading.Tasks.Task DeleteStoredCredentialAsync(string instanceId, string storageKey) + public virtual System.Threading.Tasks.Task RemoveStoredCredentialAsync(string instanceId, string storageKey) { - return DeleteStoredCredentialAsync(instanceId, storageKey, System.Threading.CancellationToken.None); + return RemoveStoredCredentialAsync(instanceId, storageKey, System.Threading.CancellationToken.None); } /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. @@ -3498,7 +3520,7 @@ public virtual System.Threading.Tasks.Task DeleteStoredCredentialA /// /// OK /// A server side error occurred. - public virtual async System.Threading.Tasks.Task DeleteStoredCredentialAsync(string instanceId, string storageKey, System.Threading.CancellationToken cancellationToken) + public virtual async System.Threading.Tasks.Task RemoveStoredCredentialAsync(string instanceId, string storageKey, System.Threading.CancellationToken cancellationToken) { if (instanceId == null) throw new System.ArgumentNullException("instanceId"); @@ -3517,8 +3539,8 @@ public virtual async System.Threading.Tasks.Task DeleteStoredCrede var urlBuilder_ = new System.Text.StringBuilder(); if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); - // Operation Path: "internal/v1/storedcredential/credentials/{instanceId}/{storageKey}" - urlBuilder_.Append("internal/v1/storedcredential/credentials/"); + // Operation Path: "internal/v1/credentials/{instanceId}/{storageKey}" + urlBuilder_.Append("internal/v1/credentials/"); urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(instanceId, System.Globalization.CultureInfo.InvariantCulture))); urlBuilder_.Append('/'); urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(storageKey, System.Globalization.CultureInfo.InvariantCulture))); diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/CertificateAuthorityController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/CertificateAuthorityController.cs index f6687833b..e3c8b55bb 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/CertificateAuthorityController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/CertificateAuthorityController.cs @@ -1,7 +1,5 @@ using Certify.Client; using Certify.Server.Api.Public.Services; -using Microsoft.AspNetCore.Authentication.JwtBearer; -using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace Certify.Server.Api.Public.Controllers @@ -30,18 +28,5 @@ public CertificateAuthorityController(ILogger lo _client = client; _mgmtAPI = mgmtApi; } - - /// - /// Get list of known certificate authorities - /// - /// - [HttpGet] - [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] - [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(List))] - public async Task GetCertificateAuthorities() - { - var list = await _client.GetCertificateAuthorities(); - return new OkObjectResult(list); - } } } diff --git a/src/Certify.Server/Certify.Server.Api.Public/Services/ManagementAPI.cs b/src/Certify.Server/Certify.Server.Api.Public/Services/ManagementAPI.cs index c7e5a73ac..06d031dd5 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Services/ManagementAPI.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Services/ManagementAPI.cs @@ -140,7 +140,7 @@ public async Task RemoveManagedCertificate(string instanceId, string manag new("managedCertId",managedCertId) }; - var deletedOK = await PerformInstanceCommandTaskWithResult(instanceId, args, ManagementHubCommands.DeleteManagedItem); + var deletedOK = await PerformInstanceCommandTaskWithResult(instanceId, args, ManagementHubCommands.RemoveDeleteManagedItem); if (deletedOK) { @@ -179,6 +179,24 @@ public async Task GetManagedCertificateSummary(AuthContext? curre return await Task.FromResult(sum); } + public async Task?> GetCertificateAuthorities(string instanceId, AuthContext? currentAuthContext) + { + var args = new KeyValuePair[] { + new("instanceId", instanceId) + }; + + return await PerformInstanceCommandTaskWithResult>(instanceId, args, ManagementHubCommands.GetCertificateAuthorities); + } + public async Task UpdateCertificateAuthority(string instanceId, CertificateAuthority certificateAuthority, AuthContext? currentAuthContext) + { + var args = new KeyValuePair[] { + new("instanceId", instanceId) , + new("certificateAuthority", JsonSerializer.Serialize(certificateAuthority)) + }; + + return await PerformInstanceCommandTaskWithResult(instanceId, args, ManagementHubCommands.UpdateCertificateAuthority); + } + public async Task?> GetAcmeAccounts(string instanceId, AuthContext? currentAuthContext) { var args = new KeyValuePair[] { @@ -236,7 +254,7 @@ public async Task GetManagedCertificateSummary(AuthContext? curre return await PerformInstanceCommandTaskWithResult(instanceId, args, ManagementHubCommands.UpdateStoredCredential); } - public async Task DeleteStoredCredential(string instanceId, string storageKey, AuthContext authContext) + public async Task RemoveStoredCredential(string instanceId, string storageKey, AuthContext authContext) { // delete stored credential via management hub @@ -245,7 +263,7 @@ public async Task GetManagedCertificateSummary(AuthContext? curre new("storageKey",storageKey) }; - return await PerformInstanceCommandTaskWithResult(instanceId, args, ManagementHubCommands.DeleteStoredCredential); + return await PerformInstanceCommandTaskWithResult(instanceId, args, ManagementHubCommands.RemoveStoredCredential); } public async Task GetItemLog(string instanceId, string managedCertId, int maxLines, AuthContext? currentAuthContext) diff --git a/src/Certify.SourceGenerators/ApiMethods.cs b/src/Certify.SourceGenerators/ApiMethods.cs index 1f4d790f2..d28395960 100644 --- a/src/Certify.SourceGenerators/ApiMethods.cs +++ b/src/Certify.SourceGenerators/ApiMethods.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using SourceGenerator; @@ -135,9 +135,9 @@ public static List GetApiDefinitions() }, new GeneratedAPI { - OperationName = "DeleteSecurityPrinciple", + OperationName = "RemoveSecurityPrinciple", OperationMethod = HttpDelete, - Comment = "Delete security principle", + Comment = "Remove security principle", PublicAPIController = "Access", PublicAPIRoute = "securityprinciple", ServiceAPIRoute = "access/securityprinciple/{id}", @@ -162,33 +162,42 @@ public static List GetApiDefinitions() Comment = "Add New Acme Account", UseManagementAPI = true, PublicAPIController = "CertificateAuthority", - PublicAPIRoute = "account/{instanceId}", + PublicAPIRoute = "{instanceId}/account/", ServiceAPIRoute = "accounts", ReturnType = "Models.Config.ActionResult", Params =new Dictionary{ { "instanceId", "string" },{ "registration", "Certify.Models.ContactRegistration" } } }, new GeneratedAPI { - - OperationName = "AddCertificateAuthority", + OperationName = "GetCertificateAuthorities", + OperationMethod = HttpGet, + Comment = "Get list of defined Certificate Authorities", + PublicAPIController = "CertificateAuthority", + PublicAPIRoute = "{instanceId}/authority", + ServiceAPIRoute = "accounts/authorities", + ReturnType = "ICollection", + UseManagementAPI = true, + Params =new Dictionary{ { "instanceId", "string" } } + }, + new GeneratedAPI { + OperationName = "UpdateCertificateAuthority", OperationMethod = HttpPost, - Comment = "Add New Certificate Authority", + Comment = "Add/Update Certificate Authority", + UseManagementAPI = true, PublicAPIController = "CertificateAuthority", - PublicAPIRoute = "authority", + PublicAPIRoute = "{instanceId}/authority", ServiceAPIRoute = "accounts/authorities", ReturnType = "Models.Config.ActionResult", - Params =new Dictionary{{ "certificateAuthority", "Certify.Models.CertificateAuthority" } } + Params =new Dictionary{ { "instanceId", "string" }, { "ca", "Certify.Models.CertificateAuthority" } } }, - new GeneratedAPI { - OperationName = "RemoveCertificateAuthority", OperationMethod = HttpDelete, Comment = "Remove Certificate Authority", PublicAPIController = "CertificateAuthority", - PublicAPIRoute = "authority/{id}", + PublicAPIRoute = "{instanceId}/authority/{id}", ServiceAPIRoute = "accounts/authorities/{id}", ReturnType = "Models.Config.ActionResult", - Params =new Dictionary{{ "id", "string" } } + Params =new Dictionary{ { "instanceId", "string" },{ "id", "string" } } }, new GeneratedAPI { OperationName = "RemoveAcmeAccount", @@ -223,7 +232,7 @@ public static List GetApiDefinitions() Params =new Dictionary{ { "instanceId", "string" }, { "item", "Models.Config.StoredCredential" } } }, new GeneratedAPI { - OperationName = "DeleteStoredCredential", + OperationName = "RemoveStoredCredential", OperationMethod = HttpDelete, Comment = "Remove Stored Credential", PublicAPIController = "StoredCredential", diff --git a/src/Certify.UI.Shared/ViewModel/AppViewModel/AppViewModel.Config.cs b/src/Certify.UI.Shared/ViewModel/AppViewModel/AppViewModel.Config.cs index 4f208c07b..f4ace781f 100644 --- a/src/Certify.UI.Shared/ViewModel/AppViewModel/AppViewModel.Config.cs +++ b/src/Certify.UI.Shared/ViewModel/AppViewModel/AppViewModel.Config.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; @@ -44,7 +44,7 @@ public async Task RefreshCertificateAuthorityList() /// public async Task UpdateCertificateAuthority(CertificateAuthority ca) { - var result = await _certifyClient.UpdateCertificateAuthority(ca); + var result = await _certifyClient.UpdateCertificateAuthority(ca, authContext: null); if (result.IsSuccess) { From 24cd8b4a1683a7aa9b40300c9ef728e06c15cefe Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Sat, 2 Nov 2024 16:31:42 +0800 Subject: [PATCH 262/328] Package updates --- .../DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj | 2 +- .../Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj index cc30c1abf..35ad77cc8 100644 --- a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj +++ b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj index 74f32ab94..8cbe99a9f 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj @@ -116,7 +116,7 @@ - + From 24c181ee72c2fd2b35e6488644d79cf8717830f2 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Tue, 5 Nov 2024 08:19:12 +0800 Subject: [PATCH 263/328] Fix name typo and update error handling --- ...als.cs => CertifyCLI.StoredCredentials.cs} | 34 +++++++++++++------ 1 file changed, 23 insertions(+), 11 deletions(-) rename src/Certify.CLI/{CertifyCLI,StoredCredentials.cs => CertifyCLI.StoredCredentials.cs} (52%) diff --git a/src/Certify.CLI/CertifyCLI,StoredCredentials.cs b/src/Certify.CLI/CertifyCLI.StoredCredentials.cs similarity index 52% rename from src/Certify.CLI/CertifyCLI,StoredCredentials.cs rename to src/Certify.CLI/CertifyCLI.StoredCredentials.cs index 11d4a9620..3c4d20934 100644 --- a/src/Certify.CLI/CertifyCLI,StoredCredentials.cs +++ b/src/Certify.CLI/CertifyCLI.StoredCredentials.cs @@ -25,25 +25,32 @@ internal async Task UpdateStoredCredential(string[] args) var cred = new StoredCredential { StorageKey = storageKey, - DateCreated = DateTime.Now, + DateCreated = DateTime.UtcNow, ProviderType = credentialType, Secret = secretValue, Title = title }; - var result = await _certifyClient.UpdateCredentials(cred); - - if (result != null) + try { - var resultObject = new { Status = "OK", Message = "Credential updated", StorageKey = result?.StorageKey }; - var output = JsonConvert.SerializeObject(resultObject, Formatting.Indented); - Console.WriteLine(output); + var result = await _certifyClient.UpdateCredentials(cred); + if (result != null) + { + + var resultObject = new { Status = "OK", Message = "Credential updated", StorageKey = result?.StorageKey }; + var output = JsonConvert.SerializeObject(resultObject, Formatting.Indented); + Console.WriteLine(output); + } + else + { + var resultObject = new { Status = "Error", Message = "Credential update failed" }; + var output = JsonConvert.SerializeObject(resultObject, Formatting.Indented); + Console.WriteLine(output); + } } - else + catch (Exception ex) { - var resultObject = new { Status = "Error", Message = "Credential update failed" }; - var output = JsonConvert.SerializeObject(resultObject, Formatting.Indented); - Console.WriteLine(output); + Console.WriteLine($"Error updating credentials: {ex.Message}"); } } @@ -55,5 +62,10 @@ internal async Task ListStoredCredentials(string[] args) Console.WriteLine(output); } + private void WriteOutput(object resultObject) + { + var output = JsonConvert.SerializeObject(resultObject, Formatting.Indented); + Console.WriteLine(output); + } } } From 2fe692bb3c0e2bc9fcb0253b28e37f7cfa075a2b Mon Sep 17 00:00:00 2001 From: jing8956 Date: Fri, 8 Nov 2024 02:56:10 +0800 Subject: [PATCH 264/328] Fall back to the CIM cmdlet when WMIC is deprecated. --- .../Scripts/Common/RDPListenerService.ps1 | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Certify.Shared.Extensions/Scripts/Common/RDPListenerService.ps1 b/src/Certify.Shared.Extensions/Scripts/Common/RDPListenerService.ps1 index 129e1e5e8..df6e5eb36 100644 --- a/src/Certify.Shared.Extensions/Scripts/Common/RDPListenerService.ps1 +++ b/src/Certify.Shared.Extensions/Scripts/Common/RDPListenerService.ps1 @@ -7,4 +7,14 @@ param($result) # Apply certificate -wmic /namespace:\\root\cimv2\TerminalServices PATH Win32_TSGeneralSetting Set SSLCertificateSHA1Hash="$($result.ManagedItem.CertificateThumbprintHash)" +if (Get-Command wmic -errorAction SilentlyContinue) +{ + # Beginning with Windows Server 2025, WMIC is available as a feature on demand. + wmic /namespace:\\root\cimv2\TerminalServices PATH Win32_TSGeneralSetting Set SSLCertificateSHA1Hash="$($result.ManagedItem.CertificateThumbprintHash)" +} +else +{ + # For new development, use the CIM cmdlets instead. + $instance = Get-CimInstance -ClassName Win32_TSGeneralSetting -Namespace root/cimv2/TerminalServices + Set-CimInstance -InputObject $instance -Property @{SSLCertificateSHA1Hash=$result.ManagedItem.CertificateThumbprintHash} +} From 1fe8d308558deaaf246021a4ef8d9b053b3bf8cb Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Fri, 8 Nov 2024 13:31:02 +0800 Subject: [PATCH 265/328] API: per instance deployment task edit/execute, refactor --- src/Certify.Client/CertifyApiClient.cs | 6 + src/Certify.Client/ICertifyClient.cs | 2 + .../CertifyManager.ManagementHub.cs | 27 ++++ .../API/Management/ManagementHubMessages.cs | 4 + .../Certify.API.Public.cs | 116 +++++++++++++++-- .../internal/DeploymentTaskController.cs | 20 +-- .../Services/ManagementAPI.cs | 37 ++++++ src/Certify.SourceGenerators/ApiMethods.cs | 73 ++++++----- .../PublicAPISourceGenerator.cs | 118 ++++++++++-------- 9 files changed, 302 insertions(+), 101 deletions(-) diff --git a/src/Certify.Client/CertifyApiClient.cs b/src/Certify.Client/CertifyApiClient.cs index 014e0603d..11ef64af9 100644 --- a/src/Certify.Client/CertifyApiClient.cs +++ b/src/Certify.Client/CertifyApiClient.cs @@ -620,6 +620,12 @@ public async Task> GetCertificateAuthorities(AuthCont return JsonConvert.DeserializeObject>(result); } + public async Task UpdateCertificateAuthority(CertificateAuthority ca, AuthContext authContext = null) + { + var result = await PostAsync("accounts/authorities", ca, authContext); + return JsonConvert.DeserializeObject(await result.Content.ReadAsStringAsync()); + } + public async Task DeleteCertificateAuthority(string id, AuthContext authContext = null) { var result = await DeleteAsync("accounts/authorities/" + id, authContext); diff --git a/src/Certify.Client/ICertifyClient.cs b/src/Certify.Client/ICertifyClient.cs index e73fba9ba..74ee82378 100644 --- a/src/Certify.Client/ICertifyClient.cs +++ b/src/Certify.Client/ICertifyClient.cs @@ -116,6 +116,8 @@ public partial interface ICertifyInternalApiClient #region Accounts Task> GetCertificateAuthorities(AuthContext authContext = null); + + Task UpdateCertificateAuthority(CertificateAuthority ca, AuthContext authContext = null); Task DeleteCertificateAuthority(string id, AuthContext authContext = null); Task> GetAccounts(AuthContext authContext = null); Task AddAccount(ContactRegistration contact, AuthContext authContext = null); diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs index 225350aa3..90e90a619 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs @@ -175,6 +175,12 @@ private async Task _managementServerClient_OnGetCommandRe val = await UpdateCertificateAuthority(item); } + else if (arg.CommandType == ManagementHubCommands.RemoveCertificateAuthority) + { + var args = JsonSerializer.Deserialize[]>(arg.Value); + var itemArg = args.FirstOrDefault(a => a.Key == "id"); + val = await RemoveCertificateAuthority(itemArg.Value); + } else if (arg.CommandType == ManagementHubCommands.GetAcmeAccounts) { val = await GetAccountRegistrations(); @@ -187,6 +193,13 @@ private async Task _managementServerClient_OnGetCommandRe val = await AddAccount(registration); } + else if (arg.CommandType == ManagementHubCommands.RemoveAcmeAccount) + { + var args = JsonSerializer.Deserialize[]>(arg.Value); + var itemArg = args.FirstOrDefault(a => a.Key == "storageKey"); + var deactivateArg = args.FirstOrDefault(a => a.Key == "deactivate"); + val = await RemoveAccount(itemArg.Value, bool.Parse(deactivateArg.Value)); + } else if (arg.CommandType == ManagementHubCommands.GetStoredCredentials) { val = await _credentialsManager.GetCredentials(); @@ -209,6 +222,7 @@ private async Task _managementServerClient_OnGetCommandRe { val = await Core.Management.Challenges.ChallengeProviders.GetChallengeAPIProviders(); } + else if (arg.CommandType == ManagementHubCommands.GetDnsZones) { var args = JsonSerializer.Deserialize[]>(arg.Value); @@ -217,6 +231,19 @@ private async Task _managementServerClient_OnGetCommandRe val = await GetDnsProviderZones(providerTypeArg.Value, credentialIdArg.Value); } + else if (arg.CommandType == ManagementHubCommands.GetDeploymentProviders) + { + val = await GetDeploymentProviders(); + } + else if (arg.CommandType == ManagementHubCommands.ExecuteDeploymentTask) + { + var args = JsonSerializer.Deserialize[]>(arg.Value); + + var managedCertificateIdArg = args.FirstOrDefault(a => a.Key == "managedCertificateId"); + var taskIdArg = args.FirstOrDefault(a => a.Key == "taskId"); + + val = await PerformDeploymentTask(null, managedCertificateIdArg.Value, taskIdArg.Value, isPreviewOnly: false, skipDeferredTasks: false, forceTaskExecution: false); + } else if (arg.CommandType == ManagementHubCommands.Reconnect) { await _managementServerClient.Disconnect(); diff --git a/src/Certify.Models/API/Management/ManagementHubMessages.cs b/src/Certify.Models/API/Management/ManagementHubMessages.cs index 108e35042..a2fc4f48d 100644 --- a/src/Certify.Models/API/Management/ManagementHubMessages.cs +++ b/src/Certify.Models/API/Management/ManagementHubMessages.cs @@ -30,6 +30,7 @@ public class ManagementHubCommands public const string GetAcmeAccounts = "GetAcmeAccounts"; public const string AddAcmeAccount = "AddAcmeAccount"; + public const string RemoveAcmeAccount = "RemoveAcmeAccount"; public const string GetStoredCredentials = "GetStoredCredentials"; public const string UpdateStoredCredential = "UpdateStoredCredential"; @@ -38,6 +39,9 @@ public class ManagementHubCommands public const string GetChallengeProviders = "GetChallengeProviders"; public const string GetDnsZones = "GetDnsZones"; + public const string GetDeploymentProviders = "GetDeploymentProviders"; + public const string ExecuteDeploymentTask = "ExecuteDeploymentTask"; + public const string Reconnect = "Reconnect"; } diff --git a/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs b/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs index cb6cf464c..14dfc1607 100644 --- a/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs +++ b/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs @@ -2895,23 +2895,26 @@ public virtual async System.Threading.Tasks.Task RemoveAcmeAccount } /// - /// Get List of supported deployment tasks + /// Get Deployment Task Providers [Generated by Certify.SourceGenerators] /// /// OK /// A server side error occurred. - public virtual System.Threading.Tasks.Task> GetDeploymentProvidersAsync() + public virtual System.Threading.Tasks.Task> GetDeploymentProvidersAsync(string instanceId) { - return GetDeploymentProvidersAsync(System.Threading.CancellationToken.None); + return GetDeploymentProvidersAsync(instanceId, System.Threading.CancellationToken.None); } /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Get List of supported deployment tasks + /// Get Deployment Task Providers [Generated by Certify.SourceGenerators] /// /// OK /// A server side error occurred. - public virtual async System.Threading.Tasks.Task> GetDeploymentProvidersAsync(System.Threading.CancellationToken cancellationToken) + public virtual async System.Threading.Tasks.Task> GetDeploymentProvidersAsync(string instanceId, System.Threading.CancellationToken cancellationToken) { + if (instanceId == null) + throw new System.ArgumentNullException("instanceId"); + var client_ = _httpClient; var disposeClient_ = false; try @@ -2923,8 +2926,9 @@ public virtual async System.Threading.Tasks.Task RemoveAcmeAccount var urlBuilder_ = new System.Text.StringBuilder(); if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); - // Operation Path: "internal/v1/deploymenttask/providers" - urlBuilder_.Append("internal/v1/deploymenttask/providers"); + // Operation Path: "internal/v1/deploymenttask/{instanceId}" + urlBuilder_.Append("internal/v1/deploymenttask/"); + urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(instanceId, System.Globalization.CultureInfo.InvariantCulture))); PrepareRequest(client_, request_, urlBuilder_); @@ -2978,6 +2982,104 @@ public virtual async System.Threading.Tasks.Task RemoveAcmeAccount } } + /// + /// Execute Deployment Task [Generated by Certify.SourceGenerators] + /// + /// OK + /// A server side error occurred. + public virtual System.Threading.Tasks.Task> ExecuteDeploymentTaskAsync(string instanceId, string managedCertificateId, string taskId) + { + return ExecuteDeploymentTaskAsync(instanceId, managedCertificateId, taskId, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Execute Deployment Task [Generated by Certify.SourceGenerators] + /// + /// OK + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task> ExecuteDeploymentTaskAsync(string instanceId, string managedCertificateId, string taskId, System.Threading.CancellationToken cancellationToken) + { + if (instanceId == null) + throw new System.ArgumentNullException("instanceId"); + + if (managedCertificateId == null) + throw new System.ArgumentNullException("managedCertificateId"); + + if (taskId == null) + throw new System.ArgumentNullException("taskId"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); + // Operation Path: "internal/v1/deploymenttask/{instanceId}/execute/{managedCertificateId}/{taskId}" + urlBuilder_.Append("internal/v1/deploymenttask/"); + urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(instanceId, System.Globalization.CultureInfo.InvariantCulture))); + urlBuilder_.Append("/execute/"); + urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(managedCertificateId, System.Globalization.CultureInfo.InvariantCulture))); + urlBuilder_.Append('/'); + urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(taskId, System.Globalization.CultureInfo.InvariantCulture))); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync>(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + /// /// Get all managed certificates matching criteria /// diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/DeploymentTaskController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/DeploymentTaskController.cs index c26c79d6d..d3f0009bf 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/DeploymentTaskController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/DeploymentTaskController.cs @@ -1,4 +1,5 @@ using Certify.Client; +using Certify.Server.Api.Public.Services; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -16,31 +17,18 @@ public partial class DeploymentTaskController : ApiControllerBase private readonly ILogger _logger; private readonly ICertifyInternalApiClient _client; + private readonly ManagementAPI _mgmtAPI; /// /// Constructor /// /// /// - public DeploymentTaskController(ILogger logger, ICertifyInternalApiClient client) + public DeploymentTaskController(ILogger logger, ICertifyInternalApiClient client, ManagementAPI mgmtAPI) { _logger = logger; _client = client; - } - - /// - /// Get List of supported deployment tasks - /// - /// - [HttpGet] - [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] - - [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(List))] - [Route("providers")] - public async Task GetDeploymentProviders() - { - var list = await _client.GetDeploymentProviderList(); - return new OkObjectResult(list); + _mgmtAPI = mgmtAPI; } } } diff --git a/src/Certify.Server/Certify.Server.Api.Public/Services/ManagementAPI.cs b/src/Certify.Server/Certify.Server.Api.Public/Services/ManagementAPI.cs index 06d031dd5..f97adddf3 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Services/ManagementAPI.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Services/ManagementAPI.cs @@ -197,6 +197,16 @@ public async Task GetManagedCertificateSummary(AuthContext? curre return await PerformInstanceCommandTaskWithResult(instanceId, args, ManagementHubCommands.UpdateCertificateAuthority); } + public async Task RemoveCertificateAuthority(string instanceId, string id, AuthContext? currentAuthContext) + { + var args = new KeyValuePair[] { + new("instanceId", instanceId), + new("id", id) + }; + + return await PerformInstanceCommandTaskWithResult(id, args, ManagementHubCommands.RemoveCertificateAuthority); + } + public async Task?> GetAcmeAccounts(string instanceId, AuthContext? currentAuthContext) { var args = new KeyValuePair[] { @@ -215,7 +225,16 @@ public async Task GetManagedCertificateSummary(AuthContext? curre return await PerformInstanceCommandTaskWithResult(instanceId, args, ManagementHubCommands.AddAcmeAccount); } + public async Task RemoveAcmeAccount(string instanceId, string storageKey, bool deactivate, AuthContext? currentAuthContext) + { + var args = new KeyValuePair[] { + new("instanceId", instanceId), + new("storageKey", storageKey), + new("deactivate", deactivate.ToString()) + }; + return await PerformInstanceCommandTaskWithResult(instanceId, args, ManagementHubCommands.RemoveAcmeAccount); + } public async Task?> GetChallengeProviders(string instanceId, AuthContext? currentAuthContext) { var args = new KeyValuePair[] { @@ -223,6 +242,24 @@ public async Task GetManagedCertificateSummary(AuthContext? curre }; return await PerformInstanceCommandTaskWithResult>(instanceId, args, ManagementHubCommands.GetChallengeProviders); } + public async Task?> GetDeploymentProviders(string instanceId, AuthContext? currentAuthContext) + { + var args = new KeyValuePair[] { + new("instanceId", instanceId) + }; + return await PerformInstanceCommandTaskWithResult>(instanceId, args, ManagementHubCommands.GetDeploymentProviders); + } + + public async Task?> ExecuteDeploymentTask(string instanceId, string managedCertificateId, string taskId, AuthContext? currentAuthContext) + { + var args = new KeyValuePair[] { + new("instanceId", instanceId), + new("managedCertificateId", managedCertificateId), + new("taskId", taskId) + }; + + return await PerformInstanceCommandTaskWithResult>(instanceId, args, ManagementHubCommands.ExecuteDeploymentTask); + } public async Task?> GetDnsZones(string instanceId, string providerTypeId, string credentialId, AuthContext? currentAuthContext) { diff --git a/src/Certify.SourceGenerators/ApiMethods.cs b/src/Certify.SourceGenerators/ApiMethods.cs index d28395960..30b1dd19d 100644 --- a/src/Certify.SourceGenerators/ApiMethods.cs +++ b/src/Certify.SourceGenerators/ApiMethods.cs @@ -144,6 +144,7 @@ public static List GetApiDefinitions() ReturnType = "Models.Config.ActionResult", Params = new Dictionary{{"id","string"}} }, + /* per instance API, via management hub */ new GeneratedAPI { OperationName = "GetAcmeAccounts", @@ -152,7 +153,6 @@ public static List GetApiDefinitions() UseManagementAPI = true, PublicAPIController = "CertificateAuthority", PublicAPIRoute = "{instanceId}/accounts/", - ServiceAPIRoute = "accounts", ReturnType = "ICollection", Params =new Dictionary{ { "instanceId", "string" } } }, @@ -163,7 +163,6 @@ public static List GetApiDefinitions() UseManagementAPI = true, PublicAPIController = "CertificateAuthority", PublicAPIRoute = "{instanceId}/account/", - ServiceAPIRoute = "accounts", ReturnType = "Models.Config.ActionResult", Params =new Dictionary{ { "instanceId", "string" },{ "registration", "Certify.Models.ContactRegistration" } } }, @@ -171,11 +170,10 @@ public static List GetApiDefinitions() OperationName = "GetCertificateAuthorities", OperationMethod = HttpGet, Comment = "Get list of defined Certificate Authorities", + UseManagementAPI = true, PublicAPIController = "CertificateAuthority", PublicAPIRoute = "{instanceId}/authority", - ServiceAPIRoute = "accounts/authorities", ReturnType = "ICollection", - UseManagementAPI = true, Params =new Dictionary{ { "instanceId", "string" } } }, new GeneratedAPI { @@ -185,7 +183,6 @@ public static List GetApiDefinitions() UseManagementAPI = true, PublicAPIController = "CertificateAuthority", PublicAPIRoute = "{instanceId}/authority", - ServiceAPIRoute = "accounts/authorities", ReturnType = "Models.Config.ActionResult", Params =new Dictionary{ { "instanceId", "string" }, { "ca", "Certify.Models.CertificateAuthority" } } }, @@ -193,9 +190,9 @@ public static List GetApiDefinitions() OperationName = "RemoveCertificateAuthority", OperationMethod = HttpDelete, Comment = "Remove Certificate Authority", + UseManagementAPI = true, PublicAPIController = "CertificateAuthority", PublicAPIRoute = "{instanceId}/authority/{id}", - ServiceAPIRoute = "accounts/authorities/{id}", ReturnType = "Models.Config.ActionResult", Params =new Dictionary{ { "instanceId", "string" },{ "id", "string" } } }, @@ -203,9 +200,9 @@ public static List GetApiDefinitions() OperationName = "RemoveAcmeAccount", OperationMethod = HttpDelete, Comment = "Remove ACME Account", + UseManagementAPI = true, PublicAPIController = "CertificateAuthority", PublicAPIRoute = "{instanceId}/accounts/{storageKey}/{deactivate}", - ServiceAPIRoute = "accounts/remove/{storageKey}/{deactivate}", ReturnType = "Models.Config.ActionResult", Params =new Dictionary{ { "instanceId", "string" }, { "storageKey", "string" }, { "deactivate", "bool" } } }, @@ -213,11 +210,10 @@ public static List GetApiDefinitions() OperationName = "GetStoredCredentials", OperationMethod = HttpGet, Comment = "Get List of Stored Credentials", + UseManagementAPI = true, PublicAPIController = "StoredCredential", PublicAPIRoute = "{instanceId}", - ServiceAPIRoute = "credentials", ReturnType = "ICollection", - UseManagementAPI = true, Params =new Dictionary{ { "instanceId", "string" } } }, new GeneratedAPI { @@ -226,7 +222,6 @@ public static List GetApiDefinitions() Comment = "Add/Update Stored Credential", PublicAPIController = "StoredCredential", PublicAPIRoute = "{instanceId}", - ServiceAPIRoute = "credentials", ReturnType = "Models.Config.ActionResult", UseManagementAPI = true, Params =new Dictionary{ { "instanceId", "string" }, { "item", "Models.Config.StoredCredential" } } @@ -235,22 +230,32 @@ public static List GetApiDefinitions() OperationName = "RemoveStoredCredential", OperationMethod = HttpDelete, Comment = "Remove Stored Credential", + UseManagementAPI = true, PublicAPIController = "StoredCredential", PublicAPIRoute = "{instanceId}/{storageKey}", - ServiceAPIRoute = "credentials", ReturnType = "Models.Config.ActionResult", - UseManagementAPI = true, Params =new Dictionary{ { "instanceId", "string" },{ "storageKey", "string" } } }, + new GeneratedAPI { + OperationName = "GetDeploymentProviders", + OperationMethod = HttpGet, + Comment = "Get Deployment Task Providers", + UseManagementAPI = true, + PublicAPIController = "DeploymentTask", + PublicAPIRoute = "{instanceId}", + ReturnType = "ICollection", + Params =new Dictionary{ + { "instanceId", "string" } + } + }, new GeneratedAPI { OperationName = "GetChallengeProviders", OperationMethod = HttpGet, Comment = "Get Dns Challenge Providers", + UseManagementAPI = true, PublicAPIController = "ChallengeProvider", PublicAPIRoute = "{instanceId}", - ServiceAPIRoute = "managedcertificates/challengeapis/", ReturnType = "ICollection", - UseManagementAPI = true, Params =new Dictionary{ { "instanceId", "string" } } @@ -259,17 +264,41 @@ public static List GetApiDefinitions() OperationName = "GetDnsZones", OperationMethod = HttpGet, Comment = "Get List of Zones with the current DNS provider and credential", + UseManagementAPI = true, PublicAPIController = "ChallengeProvider", PublicAPIRoute = "{instanceId}/dnszones/{providerTypeId}/{credentialId}", - ServiceAPIRoute = "managedcertificates/dnszones/{providerTypeId}/{credentialId}", ReturnType = "ICollection", - UseManagementAPI = true, Params =new Dictionary{ { "instanceId", "string" } , { "providerTypeId", "string" }, { "credentialId", "string" } } }, + new GeneratedAPI { + OperationName = "ExecuteDeploymentTask", + OperationMethod = HttpGet, + Comment = "Execute Deployment Task", + UseManagementAPI = true, + PublicAPIController = "DeploymentTask", + PublicAPIRoute = "{instanceId}/execute/{managedCertificateId}/{taskId}", + ReturnType = "ICollection", + Params =new Dictionary{ + { "instanceId", "string" }, + { "managedCertificateId", "string" }, + { "taskId", "string" } + } + }, + new GeneratedAPI { + OperationName = "RemoveManagedCertificate", + OperationMethod = HttpDelete, + Comment = "Remove Managed Certificate", + UseManagementAPI = true, + PublicAPIController = "Certificate", + PublicAPIRoute = "{instanceId}/settings/{managedCertId}", + ReturnType = "bool", + Params =new Dictionary{ { "instanceId", "string" },{ "managedCertId", "string" } } + }, + // TODO new GeneratedAPI { OperationName = "PerformExport", OperationMethod = HttpPost, @@ -290,18 +319,6 @@ public static List GetApiDefinitions() ReturnType = "ICollection", Params =new Dictionary{{ "importRequest", "Certify.Models.Config.Migration.ImportRequest" } } }, - new GeneratedAPI { - - OperationName = "RemoveManagedCertificate", - OperationMethod = HttpDelete, - Comment = "Remove Managed Certificate", - PublicAPIController = "Certificate", - PublicAPIRoute = "{instanceId}/settings/{managedCertId}", - UseManagementAPI = true, - ServiceAPIRoute = "managedcertificates/delete/{managedCertId}", - ReturnType = "bool", - Params =new Dictionary{ { "instanceId", "string" },{ "managedCertId", "string" } } - }, }; } } diff --git a/src/Certify.SourceGenerators/PublicAPISourceGenerator.cs b/src/Certify.SourceGenerators/PublicAPISourceGenerator.cs index b98821814..c330aeae5 100644 --- a/src/Certify.SourceGenerators/PublicAPISourceGenerator.cs +++ b/src/Certify.SourceGenerators/PublicAPISourceGenerator.cs @@ -44,7 +44,46 @@ public void Execute(GeneratorExecutionContext context) if (context.Compilation.AssemblyName.EndsWith("Api.Public")) { - context.AddSource($"{config.PublicAPIController}Controller.{config.OperationName}.g.cs", SourceText.From($@" + ImplementPublicAPI(context, config, apiParamDeclWithoutAuthContext, apiParamCall); + } + + if (context.Compilation.AssemblyName.EndsWith("Certify.UI.Blazor")) + { + ImplementAppModel(context, config, apiParamDeclWithoutAuthContext, apiParamCallWithoutAuthContext); + } + + if (context.Compilation.AssemblyName.EndsWith("Certify.Client") && !config.UseManagementAPI) + { + // for methods which directly call the backend service (e.g. main server settings), implement the client API + ImplementInternalAPIClient(context, config, apiParamDecl, apiParamCall); + } + } + } + + private static void ImplementAppModel(GeneratorExecutionContext context, GeneratedAPI config, string apiParamDeclWithoutAuthContext, string apiParamCallWithoutAuthContext) + { + context.AddSource($"AppModel.{config.OperationName}.g.cs", SourceText.From($@" +using System.Collections.Generic; +using System.Threading.Tasks; +using Certify.Models; +using Certify.Models.Providers; +using Certify.Models.Config.AccessControl; + + namespace Certify.UI.Client.Core + {{ + public partial class AppModel + {{ + public async Task<{config.ReturnType}> {config.OperationName}({apiParamDeclWithoutAuthContext}) + {{ + return await _api.{config.OperationName}Async({apiParamCallWithoutAuthContext}); + }} + }} + }}", Encoding.UTF8)); + } + + private static void ImplementPublicAPI(GeneratorExecutionContext context, GeneratedAPI config, string apiParamDeclWithoutAuthContext, string apiParamCall) + { + context.AddSource($"{config.PublicAPIController}Controller.{config.OperationName}.g.cs", SourceText.From($@" using Certify.Client; using Certify.Server.Api.Public.Controllers; @@ -79,11 +118,11 @@ public partial class {config.PublicAPIController}Controller }} }}", Encoding.UTF8)); - } + } - if (context.Compilation.AssemblyName.EndsWith("Certify.Client")) - { - var template = @" + private static void ImplementInternalAPIClient(GeneratorExecutionContext context, GeneratedAPI config, string apiParamDecl, string apiParamCall) + { + var template = @" using Certify.Models; using Certify.Models.Config.Migration; using Certify.Models.Providers; @@ -97,9 +136,9 @@ namespace Certify.Client } "; - if (config.OperationMethod == "HttpGet") - { - var source = SourceText.From(template.Replace("MethodTemplate", $@" + if (config.OperationMethod == "HttpGet") + { + var code = template.Replace("MethodTemplate", $@" public partial interface ICertifyInternalApiClient {{ @@ -124,23 +163,24 @@ public partial class CertifyApiClient }} }} - "), Encoding.UTF8); - context.AddSource($"{config.PublicAPIController}.{config.OperationName}.ICertifyInternalApiClient.g.cs", source); - } + "); + var source = SourceText.From(code, Encoding.UTF8); + context.AddSource($"{config.PublicAPIController}.{config.OperationName}.ICertifyInternalApiClient.g.cs", source); + } - if (config.OperationMethod == "HttpPost") - { - var postAPIRoute = config.ServiceAPIRoute; - var postApiCall = apiParamCall; - var postApiParamDecl = apiParamDecl; + if (config.OperationMethod == "HttpPost") + { + var postAPIRoute = config.ServiceAPIRoute; + var postApiCall = apiParamCall; + var postApiParamDecl = apiParamDecl; - if (config.UseManagementAPI) - { - postApiCall = apiParamCall.Replace("instanceId,", ""); - postApiParamDecl = apiParamDecl.Replace("string instanceId,", ""); - } + if (config.UseManagementAPI) + { + postApiCall = apiParamCall.Replace("instanceId,", ""); + postApiParamDecl = apiParamDecl.Replace("string instanceId,", ""); + } - context.AddSource($"{config.PublicAPIController}.{config.OperationName}.ICertifyInternalApiClient.g.cs", SourceText.From(template.Replace("MethodTemplate", $@" + context.AddSource($"{config.PublicAPIController}.{config.OperationName}.ICertifyInternalApiClient.g.cs", SourceText.From(template.Replace("MethodTemplate", $@" public partial interface ICertifyInternalApiClient {{ @@ -167,11 +207,11 @@ public partial class CertifyApiClient }} "), Encoding.UTF8)); - } + } - if (config.OperationMethod == "HttpDelete") - { - context.AddSource($"{config.PublicAPIController}.{config.OperationName}.ICertifyInternalApiClient.g.cs", SourceText.From(template.Replace("MethodTemplate", $@" + if (config.OperationMethod == "HttpDelete") + { + context.AddSource($"{config.PublicAPIController}.{config.OperationName}.ICertifyInternalApiClient.g.cs", SourceText.From(template.Replace("MethodTemplate", $@" public partial interface ICertifyInternalApiClient {{ @@ -202,31 +242,9 @@ public partial class CertifyApiClient }} "), Encoding.UTF8)); - } - } - - if (context.Compilation.AssemblyName.EndsWith("Certify.UI.Blazor")) - { - context.AddSource($"AppModel.{config.OperationName}.g.cs", SourceText.From($@" -using System.Collections.Generic; -using System.Threading.Tasks; -using Certify.Models; -using Certify.Models.Providers; -using Certify.Models.Config.AccessControl; - - namespace Certify.UI.Client.Core - {{ - public partial class AppModel - {{ - public async Task<{config.ReturnType}> {config.OperationName}({apiParamDeclWithoutAuthContext}) - {{ - return await _api.{config.OperationName}Async({apiParamCallWithoutAuthContext}); - }} - }} - }}", Encoding.UTF8)); - } } } + public void Initialize(GeneratorInitializationContext context) { #if DEBUG @@ -234,7 +252,7 @@ public void Initialize(GeneratorInitializationContext context) // then add a watch on if (!Debugger.IsAttached) { - // Debugger.Launch(); + // Debugger.Launch(); } #endif } From d03996108305bb4e01b5d07972f26c8d7c10e5c2 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Fri, 8 Nov 2024 14:28:53 +0800 Subject: [PATCH 266/328] Cleanup --- .../Controllers/internal/DeploymentTaskController.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/DeploymentTaskController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/DeploymentTaskController.cs index d3f0009bf..2a8dc75bf 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/DeploymentTaskController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/DeploymentTaskController.cs @@ -1,7 +1,5 @@ using Certify.Client; using Certify.Server.Api.Public.Services; -using Microsoft.AspNetCore.Authentication.JwtBearer; -using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace Certify.Server.Api.Public.Controllers From 4143374601824086e620f8c30b30441c2e36115a Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Fri, 8 Nov 2024 17:38:19 +0800 Subject: [PATCH 267/328] Fix access control store deletes --- .../CertifyManager/CertifyManager.cs | 8 ++------ src/Certify.Models/Util/EnvironmentUtil.cs | 18 +++++++++++------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.cs index 7d9b57bd9..6c11f0279 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.cs @@ -290,6 +290,7 @@ private async Task InitDataStore() // default sqlite storage _itemManager = new SQLiteManagedItemStore("", _serviceLog); _credentialsManager = new SQLiteCredentialStore("", _serviceLog); + _accessControl = new AccessControl(_serviceLog, new SQLiteAccessControlStore("", _serviceLog)); } else { @@ -318,6 +319,7 @@ private async Task InitDataStore() { _itemManager = new SQLiteManagedItemStore("", _serviceLog); _credentialsManager = new SQLiteCredentialStore("", _serviceLog); + _accessControl = new AccessControl(_serviceLog, new SQLiteAccessControlStore("",_serviceLog)); } // attempt to create and delete a test item @@ -517,12 +519,6 @@ public Task ApplyPreferences() private IAccessControl _accessControl; public Task GetCurrentAccessControl() { - if (_accessControl == null) - { - var store = new SQLiteAccessControlStore(); - _accessControl = new AccessControl(_serviceLog, store); - } - return Task.FromResult(_accessControl); } } diff --git a/src/Certify.Models/Util/EnvironmentUtil.cs b/src/Certify.Models/Util/EnvironmentUtil.cs index 4ac127970..bad3337fe 100644 --- a/src/Certify.Models/Util/EnvironmentUtil.cs +++ b/src/Certify.Models/Util/EnvironmentUtil.cs @@ -85,7 +85,7 @@ private static void CreateAndApplyRestrictedACL(string path) /// /// optional subfolder to include /// full app data with with optional subdirectory - public static string CreateAppDataPath(string? subDirectory = null) + public static string CreateAppDataPath(string? subDirectory = null, bool skipCreation=false) { var parts = new List() { @@ -99,16 +99,20 @@ public static string CreateAppDataPath(string? subDirectory = null) } var path = Path.Combine(parts.ToArray()); - CreateAndApplyRestrictedACL(path); - if (subDirectory != null) + if (!skipCreation) { - parts.Add(subDirectory); - path = Path.Combine(parts.ToArray()); + CreateAndApplyRestrictedACL(path); - if (!Directory.Exists(path)) + if (subDirectory != null) { - Directory.CreateDirectory(path); + parts.Add(subDirectory); + path = Path.Combine(parts.ToArray()); + + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } } } From 4bccd864d25f5ad5d0223b494415b2d8067c57f0 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Fri, 8 Nov 2024 17:38:30 +0800 Subject: [PATCH 268/328] Update API --- .../Certify.API.Public.cs | 12 +++--------- .../Controllers/internal/HubController.cs | 2 +- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs b/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs index 14dfc1607..c55f1ae1f 100644 --- a/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs +++ b/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs @@ -3262,7 +3262,7 @@ public virtual async System.Threading.Tasks.TaskOK /// A server side error occurred. - public virtual System.Threading.Tasks.Task> FlushHubManagedInstancesAsync() + public virtual System.Threading.Tasks.Task FlushHubManagedInstancesAsync() { return FlushHubManagedInstancesAsync(System.Threading.CancellationToken.None); } @@ -3270,7 +3270,7 @@ public virtual async System.Threading.Tasks.TaskA cancellation token that can be used by other objects or threads to receive notice of cancellation. /// OK /// A server side error occurred. - public virtual async System.Threading.Tasks.Task> FlushHubManagedInstancesAsync(System.Threading.CancellationToken cancellationToken) + public virtual async System.Threading.Tasks.Task FlushHubManagedInstancesAsync(System.Threading.CancellationToken cancellationToken) { var client_ = _httpClient; var disposeClient_ = false; @@ -3279,7 +3279,6 @@ public virtual async System.Threading.Tasks.Task>(response_, headers_, cancellationToken).ConfigureAwait(false); - if (objectResponse_.Object == null) - { - throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); - } - return objectResponse_.Object; + return; } else { diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/HubController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/HubController.cs index 13687ac78..86983db3f 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/HubController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/HubController.cs @@ -101,7 +101,7 @@ public async Task GetHubManagedInstances() [HttpGet] [Route("flush")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] - [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(List))] + [ProducesResponseType(StatusCodes.Status200OK)] public async Task FlushHubManagedInstances() { _mgmtStateProvider.Clear(); From b0de33c0679a71316f36e099220753bdde2a2b8f Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Sun, 10 Nov 2024 15:56:12 +0800 Subject: [PATCH 269/328] Hub model namespace refactoring --- src/Certify.Client/CertifyApiClient.cs | 3 +- src/Certify.Client/ICertifyClient.cs | 51 +------------------ src/Certify.Client/ManagementServerClient.cs | 2 +- .../Management/Access/AccessControl.cs | 3 +- .../Management/Access/IAccessControl.cs | 3 +- .../CertifyManager.Maintenance.cs | 2 +- .../CertifyManager.ManagedCertificates.cs | 2 +- .../CertifyManager.ManagementHub.cs | 2 +- .../CertifyManager/ICertifyManager.cs | 2 +- .../{Config => Hub}/AccessControl.cs | 2 +- .../{Config => Hub}/AccessControlConfig.cs | 2 +- .../{API => Hub}/AuthRequest.cs | 6 +-- src/Certify.Models/{API => Hub}/LogResult.cs | 2 +- .../{API => Hub}/ManagedCertificateSummary.cs | 3 +- src/Certify.Models/Hub/ManagedChallenge.cs | 25 +++++++++ .../Management => Hub}/ManagedInstanceInfo.cs | 2 +- .../ManagementHubMessages.cs | 2 +- .../{API => Hub}/VersionInfo.cs | 2 +- .../Certify.API.Public.cs | 3 +- .../nswag.json | 7 ++- .../Controllers/internal/HubController.cs | 5 +- .../Controllers/v1/AuthController.cs | 2 +- .../Controllers/v1/CertificateController.cs | 2 +- .../Controllers/v1/SystemController.cs | 5 +- .../Services/ManagementAPI.cs | 3 +- .../Services/ManagementWorker.cs | 2 +- .../ManagementHub/InstanceManagementHub.cs | 2 +- .../InstanceManagementStateProvider.cs | 2 +- .../Controllers/AccessController.cs | 5 +- .../ManagedCertificateController.cs | 2 +- .../ManagedCertificateController.cs | 2 +- src/Certify.Shared/Utils/NetworkUtils.cs | 4 +- src/Certify.Shared/Utils/Util.cs | 2 +- src/Certify.SourceGenerators/ApiMethods.cs | 12 ++--- .../PublicAPISourceGenerator.cs | 6 +-- .../DataStores/AccessControlDataStoreTests.cs | 2 +- .../Tests/AccessControlTests.cs | 12 ++--- .../Tests/MiscTests.cs | 2 +- .../AppViewModel.ManagedCerticates.cs | 2 +- 39 files changed, 86 insertions(+), 114 deletions(-) rename src/Certify.Models/{Config => Hub}/AccessControl.cs (98%) rename src/Certify.Models/{Config => Hub}/AccessControlConfig.cs (99%) rename src/Certify.Models/{API => Hub}/AuthRequest.cs (93%) rename src/Certify.Models/{API => Hub}/LogResult.cs (98%) rename src/Certify.Models/{API => Hub}/ManagedCertificateSummary.cs (97%) create mode 100644 src/Certify.Models/Hub/ManagedChallenge.cs rename src/Certify.Models/{API/Management => Hub}/ManagedInstanceInfo.cs (94%) rename src/Certify.Models/{API/Management => Hub}/ManagementHubMessages.cs (99%) rename src/Certify.Models/{API => Hub}/VersionInfo.cs (83%) diff --git a/src/Certify.Client/CertifyApiClient.cs b/src/Certify.Client/CertifyApiClient.cs index 11ef64af9..c393c3b1d 100644 --- a/src/Certify.Client/CertifyApiClient.cs +++ b/src/Certify.Client/CertifyApiClient.cs @@ -6,10 +6,9 @@ using System.Net.Http.Headers; using System.Threading; using System.Threading.Tasks; +using Certify.Models.Hub; using Certify.Models; -using Certify.Models.API; using Certify.Models.Config; -using Certify.Models.Config.AccessControl; using Certify.Models.Reporting; using Certify.Models.Utils; using Certify.Shared; diff --git a/src/Certify.Client/ICertifyClient.cs b/src/Certify.Client/ICertifyClient.cs index 74ee82378..cbac95bd8 100644 --- a/src/Certify.Client/ICertifyClient.cs +++ b/src/Certify.Client/ICertifyClient.cs @@ -5,6 +5,7 @@ using Certify.Models.Config; using Certify.Models.Reporting; using Certify.Models.Utils; +using Certify.Models.Hub; using Certify.Shared; namespace Certify.Client @@ -15,108 +16,67 @@ namespace Certify.Client /// public partial interface ICertifyInternalApiClient { - #region System - Task GetAppVersion(AuthContext authContext = null); - Task CheckForUpdates(AuthContext authContext = null); - Task> PerformServiceDiagnostics(AuthContext authContext = null); Task> PerformManagedCertMaintenance(string id = null, AuthContext authContext = null); - Task> SetDefaultDataStore(string dataStoreId, AuthContext authContext = null); Task> GetDataStoreProviders(AuthContext authContext = null); Task> GetDataStoreConnections(AuthContext authContext = null); Task> CopyDataStore(string sourceId, string targetId, AuthContext authContext = null); Task> UpdateDataStoreConnection(DataStoreConnection dataStoreConnection, AuthContext authContext = null); Task> TestDataStoreConnection(DataStoreConnection dataStoreConnection, AuthContext authContext = null); - #endregion System #region Server Task IsServerAvailable(StandardServerTypes serverType, AuthContext authContext = null); - Task> GetServerSiteList(StandardServerTypes serverType, string itemId = null, AuthContext authContext = null); - Task GetServerVersion(StandardServerTypes serverType, AuthContext authContext = null); - Task> GetServerSiteDomains(StandardServerTypes serverType, string serverSiteId, AuthContext authContext = null); - Task> RunConfigurationDiagnostics(StandardServerTypes serverType, string serverSiteId, AuthContext authContext = null); - Task> GetCurrentChallenges(string type, string key, AuthContext authContext = null); - #endregion Server #region Preferences - Task GetPreferences(AuthContext authContext = null); - Task SetPreferences(Preferences preferences, AuthContext authContext = null); - #endregion Preferences #region Credentials - Task> GetCredentials(AuthContext authContext = null); - Task UpdateCredentials(StoredCredential credential, AuthContext authContext = null); - Task DeleteCredential(string credentialKey, AuthContext authContext = null); - Task TestCredentials(string credentialKey, AuthContext authContext = null); - #endregion Credentials #region Managed Certificates - Task> GetManagedCertificates(ManagedCertificateFilter filter, AuthContext authContext = null); Task GetManagedCertificateSearchResult(ManagedCertificateFilter filter, AuthContext authContext = null); Task GetManagedCertificateSummary(ManagedCertificateFilter filter, AuthContext authContext = null); - Task GetManagedCertificate(string managedItemId, AuthContext authContext = null); - Task UpdateManagedCertificate(ManagedCertificate site, AuthContext authContext = null); - Task DeleteManagedCertificate(string managedItemId, AuthContext authContext = null); - Task RevokeManageSiteCertificate(string managedItemId, AuthContext authContext = null); - Task> BeginAutoRenewal(RenewalSettings settings, AuthContext authContext = null); - Task> RedeployManagedCertificates(bool isPreviewOnly, bool includeDeploymentTasks, AuthContext authContext = null); - Task ReapplyCertificateBindings(string managedItemId, bool isPreviewOnly, bool includeDeploymentTasks, AuthContext authContext = null); - Task RefetchCertificate(string managedItemId, AuthContext authContext = null); - Task BeginCertificateRequest(string managedItemId, bool resumePaused, bool isInteractive, AuthContext authContext = null); - Task> TestChallengeConfiguration(ManagedCertificate site, AuthContext authContext = null); Task> PerformChallengeCleanup(ManagedCertificate site, AuthContext authContext = null); - Task> GetDnsProviderZones(string providerTypeId, string credentialId, AuthContext authContext = null); - Task> PreviewActions(ManagedCertificate site, AuthContext authContext = null); - Task> GetChallengeAPIList(AuthContext authContext = null); - Task> GetDeploymentProviderList(AuthContext authContext = null); - Task GetDeploymentProviderDefinition(string id, Config.DeploymentTaskConfig config, AuthContext authContext = null); - Task> PerformDeployment(string managedCertificateId, string taskId, bool isPreviewOnly, bool forceTaskExecute, AuthContext authContext = null); - Task> ValidateDeploymentTask(DeploymentTaskValidationInfo info, AuthContext authContext = null); - - Task GetItemLog(string id, int limit, AuthContext authContext = null); - + Task GetItemLog(string id, int limit, AuthContext authContext = null); #endregion Managed Certificates #region Accounts Task> GetCertificateAuthorities(AuthContext authContext = null); - Task UpdateCertificateAuthority(CertificateAuthority ca, AuthContext authContext = null); Task DeleteCertificateAuthority(string id, AuthContext authContext = null); Task> GetAccounts(AuthContext authContext = null); @@ -124,9 +84,7 @@ public partial interface ICertifyInternalApiClient Task UpdateAccountContact(ContactRegistration contact, AuthContext authContext = null); Task RemoveAccount(string storageKey, bool deactivate, AuthContext authContext = null); Task ChangeAccountKey(string storageKey, string newKeyPEM = null, AuthContext authContext = null); - #endregion Accounts - } /// @@ -135,15 +93,10 @@ public partial interface ICertifyInternalApiClient public interface ICertifyClient : ICertifyInternalApiClient { event Action OnMessageFromService; - event Action OnRequestProgressStateUpdated; - event Action OnManagedCertificateUpdated; - Task ConnectStatusStreamAsync(); - Shared.ServerConnection GetConnectionInfo(); - Task EnsureServiceHubConnected(); } } diff --git a/src/Certify.Client/ManagementServerClient.cs b/src/Certify.Client/ManagementServerClient.cs index 4c49cb29e..e2cd6dffb 100644 --- a/src/Certify.Client/ManagementServerClient.cs +++ b/src/Certify.Client/ManagementServerClient.cs @@ -1,6 +1,6 @@ using System; using System.Threading.Tasks; -using Certify.API.Management; +using Certify.Models.Hub; using Microsoft.AspNetCore.SignalR.Client; using Microsoft.Extensions.DependencyInjection; diff --git a/src/Certify.Core/Management/Access/AccessControl.cs b/src/Certify.Core/Management/Access/AccessControl.cs index 5610ccc25..ecbf0d8b4 100644 --- a/src/Certify.Core/Management/Access/AccessControl.cs +++ b/src/Certify.Core/Management/Access/AccessControl.cs @@ -4,8 +4,7 @@ using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; -using Certify.Models.API; -using Certify.Models.Config.AccessControl; +using Certify.Models.Hub; using Certify.Models.Providers; using Certify.Providers; diff --git a/src/Certify.Core/Management/Access/IAccessControl.cs b/src/Certify.Core/Management/Access/IAccessControl.cs index 8e9a8b8c9..f1f711820 100644 --- a/src/Certify.Core/Management/Access/IAccessControl.cs +++ b/src/Certify.Core/Management/Access/IAccessControl.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using System.Threading.Tasks; -using Certify.Models.API; -using Certify.Models.Config.AccessControl; +using Certify.Models.Hub; namespace Certify.Core.Management.Access { diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.Maintenance.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.Maintenance.cs index 0dfb52078..90c9ea746 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.Maintenance.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.Maintenance.cs @@ -6,9 +6,9 @@ using System.Threading; using System.Threading.Tasks; using Certify.Core.Management.Access; +using Certify.Models.Hub; using Certify.Models; using Certify.Models.Config; -using Certify.Models.Config.AccessControl; using Certify.Models.Shared; namespace Certify.Management diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagedCertificates.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagedCertificates.cs index d4ffccc97..0eec7b296 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagedCertificates.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagedCertificates.cs @@ -4,8 +4,8 @@ using System.Diagnostics; using System.Linq; using System.Threading.Tasks; +using Certify.Models.Hub; using Certify.Models; -using Certify.Models.API; using Certify.Models.Providers; using Certify.Models.Reporting; using Certify.Models.Shared; diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs index 90e90a619..ce8f43856 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs @@ -3,8 +3,8 @@ using System.Linq; using System.Text.Json; using System.Threading.Tasks; -using Certify.API.Management; using Certify.Client; +using Certify.Models.Hub; using Certify.Models; using Certify.Models.Config; using Certify.Shared.Core.Utils; diff --git a/src/Certify.Core/Management/CertifyManager/ICertifyManager.cs b/src/Certify.Core/Management/CertifyManager/ICertifyManager.cs index d63fbb762..e5c96540c 100644 --- a/src/Certify.Core/Management/CertifyManager/ICertifyManager.cs +++ b/src/Certify.Core/Management/CertifyManager/ICertifyManager.cs @@ -3,8 +3,8 @@ using System.Collections.Generic; using System.Threading.Tasks; using Certify.Config; +using Certify.Models.Hub; using Certify.Models; -using Certify.Models.API; using Certify.Models.Config; using Certify.Models.Config.Migration; using Certify.Models.Providers; diff --git a/src/Certify.Models/Config/AccessControl.cs b/src/Certify.Models/Hub/AccessControl.cs similarity index 98% rename from src/Certify.Models/Config/AccessControl.cs rename to src/Certify.Models/Hub/AccessControl.cs index fd6fcbbc8..50e8ff9b4 100644 --- a/src/Certify.Models/Config/AccessControl.cs +++ b/src/Certify.Models/Hub/AccessControl.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; -namespace Certify.Models.Config.AccessControl +namespace Certify.Models.Hub { public class AccessStoreItem { diff --git a/src/Certify.Models/Config/AccessControlConfig.cs b/src/Certify.Models/Hub/AccessControlConfig.cs similarity index 99% rename from src/Certify.Models/Config/AccessControlConfig.cs rename to src/Certify.Models/Hub/AccessControlConfig.cs index a1d119a50..0c545b72e 100644 --- a/src/Certify.Models/Config/AccessControlConfig.cs +++ b/src/Certify.Models/Hub/AccessControlConfig.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; -namespace Certify.Models.Config.AccessControl +namespace Certify.Models.Hub { public enum SecurityPrincipleType { diff --git a/src/Certify.Models/API/AuthRequest.cs b/src/Certify.Models/Hub/AuthRequest.cs similarity index 93% rename from src/Certify.Models/API/AuthRequest.cs rename to src/Certify.Models/Hub/AuthRequest.cs index d5ad8eeac..ada89804e 100644 --- a/src/Certify.Models/API/AuthRequest.cs +++ b/src/Certify.Models/Hub/AuthRequest.cs @@ -1,6 +1,4 @@ -using Certify.Models.Config.AccessControl; - -namespace Certify.Models.API +namespace Certify.Models.Hub { /// /// Required info to begin auth @@ -40,7 +38,7 @@ public class AuthResponse /// public string RefreshToken { get; set; } = string.Empty; - public Models.Config.AccessControl.SecurityPrinciple? SecurityPrinciple { get; set; } + public SecurityPrinciple? SecurityPrinciple { get; set; } public RoleStatus? RoleStatus { get; set; } } diff --git a/src/Certify.Models/API/LogResult.cs b/src/Certify.Models/Hub/LogResult.cs similarity index 98% rename from src/Certify.Models/API/LogResult.cs rename to src/Certify.Models/Hub/LogResult.cs index b74bdcc2e..67e50e0f1 100644 --- a/src/Certify.Models/API/LogResult.cs +++ b/src/Certify.Models/Hub/LogResult.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; -namespace Certify.Models.API +namespace Certify.Models.Hub { public class LogItem { diff --git a/src/Certify.Models/API/ManagedCertificateSummary.cs b/src/Certify.Models/Hub/ManagedCertificateSummary.cs similarity index 97% rename from src/Certify.Models/API/ManagedCertificateSummary.cs rename to src/Certify.Models/Hub/ManagedCertificateSummary.cs index 01a5b65e9..1b6804447 100644 --- a/src/Certify.Models/API/ManagedCertificateSummary.cs +++ b/src/Certify.Models/Hub/ManagedCertificateSummary.cs @@ -1,7 +1,8 @@ using System; using System.Collections.Generic; +using Certify.Models; -namespace Certify.Models.API +namespace Certify.Models.Hub { /// /// Summary information for a managed certificate diff --git a/src/Certify.Models/Hub/ManagedChallenge.cs b/src/Certify.Models/Hub/ManagedChallenge.cs new file mode 100644 index 000000000..3a2e96934 --- /dev/null +++ b/src/Certify.Models/Hub/ManagedChallenge.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Certify.Models; + +namespace Certify.Models.Hub +{ + /// + /// Configuration for a managed challenge, such as a DNS challenge for a specific domain/zone + /// A managed challenge is one the management hub can complete on behalf of another ACME client + /// + public class ManagedChallenge + { + public ManagedChallenge() + { + Id = Guid.NewGuid().ToString(); + } + public string Id { get; set; } + public string Title { get; set; } = string.Empty; + public string Description { get; set; } = string.Empty; + public string ItemType { get; set; } = string.Empty; + + public CertRequestChallengeConfig ChallengeConfig { get; set; } + } +} diff --git a/src/Certify.Models/API/Management/ManagedInstanceInfo.cs b/src/Certify.Models/Hub/ManagedInstanceInfo.cs similarity index 94% rename from src/Certify.Models/API/Management/ManagedInstanceInfo.cs rename to src/Certify.Models/Hub/ManagedInstanceInfo.cs index d0c1ef84f..38d199f5e 100644 --- a/src/Certify.Models/API/Management/ManagedInstanceInfo.cs +++ b/src/Certify.Models/Hub/ManagedInstanceInfo.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using Certify.Models; -namespace Certify.API.Management +namespace Certify.Models.Hub { public class ManagedInstanceInfo { diff --git a/src/Certify.Models/API/Management/ManagementHubMessages.cs b/src/Certify.Models/Hub/ManagementHubMessages.cs similarity index 99% rename from src/Certify.Models/API/Management/ManagementHubMessages.cs rename to src/Certify.Models/Hub/ManagementHubMessages.cs index a2fc4f48d..f6cebc7d7 100644 --- a/src/Certify.Models/API/Management/ManagementHubMessages.cs +++ b/src/Certify.Models/Hub/ManagementHubMessages.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; -namespace Certify.API.Management +namespace Certify.Models.Hub { public class ManagementHubMessages { diff --git a/src/Certify.Models/API/VersionInfo.cs b/src/Certify.Models/Hub/VersionInfo.cs similarity index 83% rename from src/Certify.Models/API/VersionInfo.cs rename to src/Certify.Models/Hub/VersionInfo.cs index 65e630d26..a8327f859 100644 --- a/src/Certify.Models/API/VersionInfo.cs +++ b/src/Certify.Models/Hub/VersionInfo.cs @@ -1,4 +1,4 @@ -namespace Certify.Models.API +namespace Certify.Models.Hub { public class VersionInfo { diff --git a/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs b/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs index c55f1ae1f..962b12550 100644 --- a/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs +++ b/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs @@ -9,9 +9,8 @@ using Certify.Models.Config; using Certify.Models.API; using Certify.Models.Providers; -using Certify.Models.Config.AccessControl; +using Certify.Models.Hub; using Certify.Models.Config.Migration; -using Certify.API.Management; #pragma warning disable 108 // Disable "CS0108 '{derivedDto}.ToJson()' hides inherited member '{dtoBase}.ToJson()'. Use the new keyword if hiding was intended." #pragma warning disable 114 // Disable "CS0114 '{derivedDto}.RaisePropertyChanged(String)' hides inherited member 'dtoBase.RaisePropertyChanged(String)'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword." diff --git a/src/Certify.Server/Certify.Server.Api.Public.Client/nswag.json b/src/Certify.Server/Certify.Server.Api.Public.Client/nswag.json index afefcbf6e..6fe5e2669 100644 --- a/src/Certify.Server/Certify.Server.Api.Public.Client/nswag.json +++ b/src/Certify.Server/Certify.Server.Api.Public.Client/nswag.json @@ -52,9 +52,8 @@ "Certify.Models.Config", "Certify.Models.API", "Certify.Models.Providers", - "Certify.Models.Config.AccessControl", - "Certify.Models.Config.Migration", - "Certify.API.Management" + "Certify.Models.Hub", + "Certify.Models.Config.Migration" ], "additionalContractNamespaceUsages": [], "generateOptionalParameters": false, @@ -115,4 +114,4 @@ "newLineBehavior": "Auto" } } -} \ No newline at end of file +} diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/HubController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/HubController.cs index 86983db3f..b969a09e3 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/HubController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/HubController.cs @@ -1,6 +1,5 @@ -using Certify.API.Management; -using Certify.Client; -using Certify.Models.API; +using Certify.Client; +using Certify.Models.Hub; using Certify.Server.Api.Public.SignalR.ManagementHub; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/AuthController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/AuthController.cs index 60bceb3c1..18e9dac70 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/AuthController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/AuthController.cs @@ -1,7 +1,7 @@ using System.Net.Http.Headers; using System.Security.Claims; using Certify.Client; -using Certify.Models.API; +using Certify.Models.Hub; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs index 57005eef1..4ec2e7eab 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs @@ -1,5 +1,5 @@ using Certify.Client; -using Certify.Models.API; +using Certify.Models.Hub; using Certify.Models.Reporting; using Certify.Server.Api.Public.Services; using Microsoft.AspNetCore.Authentication.JwtBearer; diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/SystemController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/SystemController.cs index b9dff4dcd..cee67f72e 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/SystemController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/SystemController.cs @@ -1,4 +1,5 @@ using Certify.Client; +using Certify.Models.Hub; using Microsoft.AspNetCore.Mvc; namespace Certify.Server.Api.Public.Controllers @@ -32,11 +33,11 @@ public SystemController(ILogger logger, ICertifyInternalApiCli /// [HttpGet] [Route("version")] - [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Models.API.VersionInfo))] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(VersionInfo))] public async Task GetSystemVersion() { var versionInfo = await _client.GetAppVersion(); - var result = new Models.API.VersionInfo { Version = versionInfo, Product = "Certify Management Hub" }; + var result = new Models.Hub.VersionInfo { Version = versionInfo, Product = "Certify Management Hub" }; return new OkObjectResult(result); } diff --git a/src/Certify.Server/Certify.Server.Api.Public/Services/ManagementAPI.cs b/src/Certify.Server/Certify.Server.Api.Public/Services/ManagementAPI.cs index f97adddf3..cd9b60a34 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Services/ManagementAPI.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Services/ManagementAPI.cs @@ -1,8 +1,7 @@ using System.Text.Json; -using Certify.API.Management; using Certify.Client; +using Certify.Models.Hub; using Certify.Models; -using Certify.Models.API; using Certify.Models.Config; using Certify.Models.Providers; using Certify.Models.Reporting; diff --git a/src/Certify.Server/Certify.Server.Api.Public/Services/ManagementWorker.cs b/src/Certify.Server/Certify.Server.Api.Public/Services/ManagementWorker.cs index d40563126..8f62d7a66 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Services/ManagementWorker.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Services/ManagementWorker.cs @@ -1,4 +1,4 @@ -using Certify.API.Management; +using Certify.Models.Hub; using Certify.Server.Api.Public.SignalR.ManagementHub; using Microsoft.AspNetCore.SignalR; diff --git a/src/Certify.Server/Certify.Server.Api.Public/SignalR/ManagementHub/InstanceManagementHub.cs b/src/Certify.Server/Certify.Server.Api.Public/SignalR/ManagementHub/InstanceManagementHub.cs index d6058f7d4..8b8f47e6c 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/SignalR/ManagementHub/InstanceManagementHub.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/SignalR/ManagementHub/InstanceManagementHub.cs @@ -1,4 +1,4 @@ -using Certify.API.Management; +using Certify.Models.Hub; using Certify.Models.Reporting; using Microsoft.AspNetCore.SignalR; diff --git a/src/Certify.Server/Certify.Server.Api.Public/SignalR/ManagementHub/InstanceManagementStateProvider.cs b/src/Certify.Server/Certify.Server.Api.Public/SignalR/ManagementHub/InstanceManagementStateProvider.cs index 3e74e0ecc..a859aa7ef 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/SignalR/ManagementHub/InstanceManagementStateProvider.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/SignalR/ManagementHub/InstanceManagementStateProvider.cs @@ -1,5 +1,5 @@ using System.Collections.Concurrent; -using Certify.API.Management; +using Certify.Models.Hub; using Certify.Models; using Certify.Models.Reporting; diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccessController.cs b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccessController.cs index fbdba67a4..d0b445726 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccessController.cs +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccessController.cs @@ -1,6 +1,5 @@ -using Certify.Management; -using Certify.Models.API; -using Certify.Models.Config.AccessControl; +using Certify.Models.Hub; +using Certify.Management; using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Mvc; diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/ManagedCertificateController.cs b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/ManagedCertificateController.cs index 131f6dc6c..16ca6edcd 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/ManagedCertificateController.cs +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/ManagedCertificateController.cs @@ -1,7 +1,7 @@ using Certify.Config; +using Certify.Models.Hub; using Certify.Management; using Certify.Models; -using Certify.Models.API; using Certify.Models.Config; using Certify.Models.Reporting; using Certify.Models.Utils; diff --git a/src/Certify.Service/Controllers/ManagedCertificateController.cs b/src/Certify.Service/Controllers/ManagedCertificateController.cs index 94a5b12ec..65785e2e7 100644 --- a/src/Certify.Service/Controllers/ManagedCertificateController.cs +++ b/src/Certify.Service/Controllers/ManagedCertificateController.cs @@ -5,9 +5,9 @@ using System.Web.Http; using System.Web.Http.Cors; using Certify.Config; +using Certify.Models.Hub; using Certify.Management; using Certify.Models; -using Certify.Models.API; using Certify.Models.Config; using Certify.Models.Reporting; using Certify.Models.Utils; diff --git a/src/Certify.Shared/Utils/NetworkUtils.cs b/src/Certify.Shared/Utils/NetworkUtils.cs index 271446f8a..24e56c838 100644 --- a/src/Certify.Shared/Utils/NetworkUtils.cs +++ b/src/Certify.Shared/Utils/NetworkUtils.cs @@ -7,6 +7,8 @@ using System.Net.Sockets; using System.Threading.Tasks; using Certify.Management; +using Certify.Models.API; + #if NET6_0_OR_GREATER using ARSoft.Tools.Net; using ARSoft.Tools.Net.Dns; @@ -157,7 +159,7 @@ public async Task CheckURL(ILog log, string url, bool? useProxyAPI = null) { var jsonText = await response.Content.ReadAsStringAsync(); - var result = Newtonsoft.Json.JsonConvert.DeserializeObject(jsonText); + var result = Newtonsoft.Json.JsonConvert.DeserializeObject(jsonText); if (result.IsAccessible == true) { diff --git a/src/Certify.Shared/Utils/Util.cs b/src/Certify.Shared/Utils/Util.cs index f8dd77318..44498ee31 100644 --- a/src/Certify.Shared/Utils/Util.cs +++ b/src/Certify.Shared/Utils/Util.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; diff --git a/src/Certify.SourceGenerators/ApiMethods.cs b/src/Certify.SourceGenerators/ApiMethods.cs index 30b1dd19d..4980bf857 100644 --- a/src/Certify.SourceGenerators/ApiMethods.cs +++ b/src/Certify.SourceGenerators/ApiMethods.cs @@ -82,8 +82,8 @@ public static List GetApiDefinitions() PublicAPIController = "Access", PublicAPIRoute = "validate", ServiceAPIRoute = "access/validate", - ReturnType = "Certify.Models.API.SecurityPrincipleCheckResponse", - Params = new Dictionary{{"passwordCheck", "Certify.Models.API.SecurityPrinciplePasswordCheck" } } + ReturnType = "Certify.Models.Hub.SecurityPrincipleCheckResponse", + Params = new Dictionary{{"passwordCheck", "Certify.Models.Hub.SecurityPrinciplePasswordCheck" } } }, new GeneratedAPI { @@ -94,7 +94,7 @@ public static List GetApiDefinitions() PublicAPIRoute = "updatepassword", ServiceAPIRoute = "access/updatepassword", ReturnType = "Models.Config.ActionResult", - Params = new Dictionary{{"passwordUpdate", "Certify.Models.API.SecurityPrinciplePasswordUpdate" } } + Params = new Dictionary{{"passwordUpdate", "Certify.Models.Hub.SecurityPrinciplePasswordUpdate" } } }, new GeneratedAPI { @@ -105,7 +105,7 @@ public static List GetApiDefinitions() PublicAPIRoute = "securityprinciple", ServiceAPIRoute = "access/securityprinciple", ReturnType = "Models.Config.ActionResult", - Params = new Dictionary{{"principle", "Certify.Models.Config.AccessControl.SecurityPrinciple" } } + Params = new Dictionary{{"principle", "Certify.Models.Hub.SecurityPrinciple" } } }, new GeneratedAPI { @@ -117,7 +117,7 @@ public static List GetApiDefinitions() ServiceAPIRoute = "access/securityprinciple/update", ReturnType = "Models.Config.ActionResult", Params = new Dictionary{ - { "principle", "Certify.Models.Config.AccessControl.SecurityPrinciple" } + { "principle", "Certify.Models.Hub.SecurityPrinciple" } } }, new GeneratedAPI { @@ -130,7 +130,7 @@ public static List GetApiDefinitions() ServiceAPIRoute = "access/securityprinciple/roles/update", ReturnType = "Models.Config.ActionResult", Params = new Dictionary{ - { "update", "Certify.Models.Config.AccessControl.SecurityPrincipleAssignedRoleUpdate" } + { "update", "Certify.Models.Hub.SecurityPrincipleAssignedRoleUpdate" } } }, new GeneratedAPI { diff --git a/src/Certify.SourceGenerators/PublicAPISourceGenerator.cs b/src/Certify.SourceGenerators/PublicAPISourceGenerator.cs index c330aeae5..493eaf5a5 100644 --- a/src/Certify.SourceGenerators/PublicAPISourceGenerator.cs +++ b/src/Certify.SourceGenerators/PublicAPISourceGenerator.cs @@ -67,7 +67,7 @@ private static void ImplementAppModel(GeneratorExecutionContext context, Generat using System.Threading.Tasks; using Certify.Models; using Certify.Models.Providers; -using Certify.Models.Config.AccessControl; +using Certify.Models.Hub; namespace Certify.UI.Client.Core {{ @@ -95,7 +95,7 @@ private static void ImplementPublicAPI(GeneratorExecutionContext context, Genera using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using Certify.Models; -using Certify.Models.Config.AccessControl; +using Certify.Models.Hub; namespace Certify.Server.Api.Public.Controllers @@ -126,7 +126,7 @@ private static void ImplementInternalAPIClient(GeneratorExecutionContext context using Certify.Models; using Certify.Models.Config.Migration; using Certify.Models.Providers; -using Certify.Models.Config.AccessControl; +using Certify.Models.Hub; using System.Collections.Generic; using System.Threading.Tasks; diff --git a/src/Certify.Tests/Certify.Core.Tests.Integration/DataStores/AccessControlDataStoreTests.cs b/src/Certify.Tests/Certify.Core.Tests.Integration/DataStores/AccessControlDataStoreTests.cs index ac1261907..55cbb39de 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Integration/DataStores/AccessControlDataStoreTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Integration/DataStores/AccessControlDataStoreTests.cs @@ -4,7 +4,7 @@ using System.Threading.Tasks; using Certify.Core.Management.Access; using Certify.Datastore.SQLite; -using Certify.Models.Config.AccessControl; +using Certify.Models.Hub; using Certify.Providers; using Microsoft.VisualStudio.TestTools.UnitTesting; diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/AccessControlTests.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/AccessControlTests.cs index 1bc4a1339..e820ec5d3 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/AccessControlTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/AccessControlTests.cs @@ -4,8 +4,8 @@ using System.Linq; using System.Threading.Tasks; using Certify.Core.Management.Access; +using Certify.Models.Hub; using Certify.Models; -using Certify.Models.Config.AccessControl; using Certify.Providers; using Microsoft.Extensions.Logging; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -413,7 +413,7 @@ public async Task TestUpdateSecurityPrinciplePassword() // Update security principle in AccessControl with a new password var newPassword = "GFEDCBA"; - var securityPrincipleUpdated = await access.UpdateSecurityPrinciplePassword(contextUserId, new Models.API.SecurityPrinciplePasswordUpdate(adminSecurityPrinciples[0].Id, firstPassword, newPassword)); + var securityPrincipleUpdated = await access.UpdateSecurityPrinciplePassword(contextUserId, new Models.Hub.SecurityPrinciplePasswordUpdate(adminSecurityPrinciples[0].Id, firstPassword, newPassword)); Assert.IsTrue(securityPrincipleUpdated, $"Expected security principle password update for {adminSecurityPrinciples[0].Id} to succeed"); // Validate password of SecurityPrinciple object returned by AccessControl.GetSecurityPrinciple() after update @@ -434,7 +434,7 @@ public async Task TestUpdateSecurityPrinciplePasswordNoRoles() // Update security principle in AccessControl with a new password var newPassword = "GFEDCBA"; - var securityPrincipleUpdated = await access.UpdateSecurityPrinciplePassword(contextUserId, new Models.API.SecurityPrinciplePasswordUpdate(adminSecurityPrinciples[0].Id, firstPassword, newPassword)); + var securityPrincipleUpdated = await access.UpdateSecurityPrinciplePassword(contextUserId, new Models.Hub.SecurityPrinciplePasswordUpdate(adminSecurityPrinciples[0].Id, firstPassword, newPassword)); Assert.IsFalse(securityPrincipleUpdated, $"Expected security principle password update for {adminSecurityPrinciples[0].Id} to fail without roles"); // Validate password of SecurityPrinciple object returned by AccessControl.GetSecurityPrinciple() after failed update @@ -470,7 +470,7 @@ public async Task TestUpdateSecurityPrinciplePasswordBadPassword() // Update security principle in AccessControl with a new password, but wrong original password var newPassword = "GFEDCBA"; - var securityPrincipleUpdated = await access.UpdateSecurityPrinciplePassword(contextUserId, new Models.API.SecurityPrinciplePasswordUpdate(adminSecurityPrinciples[0].Id, firstPassword.ToLower(), newPassword)); + var securityPrincipleUpdated = await access.UpdateSecurityPrinciplePassword(contextUserId, new Models.Hub.SecurityPrinciplePasswordUpdate(adminSecurityPrinciples[0].Id, firstPassword.ToLower(), newPassword)); Assert.IsFalse(securityPrincipleUpdated, $"Expected security principle password update for {adminSecurityPrinciples[0].Id} to fail with wrong password"); // Validate password of SecurityPrinciple object returned by AccessControl.GetSecurityPrinciple() after failed update @@ -739,7 +739,7 @@ public async Task TestSecurityPrinciplePwdValid() { // Add test devops user security principle _ = await access.AddSecurityPrinciple(contextUserId, TestSecurityPrinciples.DevopsUser, bypassIntegrityCheck: true); - var check = await access.CheckSecurityPrinciplePassword(contextUserId, new Models.API.SecurityPrinciplePasswordCheck(TestSecurityPrinciples.DevopsUser.Id, TestSecurityPrinciples.DevopsUser.Password)); + var check = await access.CheckSecurityPrinciplePassword(contextUserId, new Models.Hub.SecurityPrinciplePasswordCheck(TestSecurityPrinciples.DevopsUser.Id, TestSecurityPrinciples.DevopsUser.Password)); Assert.IsTrue(check.IsSuccess, "Password should be valid"); } @@ -749,7 +749,7 @@ public async Task TestSecurityPrinciplePwdInvalid() { // Add test devops user security principle _ = await access.AddSecurityPrinciple(contextUserId, TestSecurityPrinciples.DevopsUser, bypassIntegrityCheck: true); - var check = await access.CheckSecurityPrinciplePassword(contextUserId, new Models.API.SecurityPrinciplePasswordCheck(TestSecurityPrinciples.DevopsUser.Id, "INVALID_PWD")); + var check = await access.CheckSecurityPrinciplePassword(contextUserId, new Models.Hub.SecurityPrinciplePasswordCheck(TestSecurityPrinciples.DevopsUser.Id, "INVALID_PWD")); Assert.IsFalse(check.IsSuccess, "Password should not be valid"); } diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/MiscTests.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/MiscTests.cs index 80bd0bc08..461fceb3e 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/MiscTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/MiscTests.cs @@ -3,7 +3,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -using Certify.Models.API; +using Certify.Models.Hub; using Certify.Shared.Core.Utils; using Certify.Shared.Core.Utils.PKI; using Microsoft.VisualStudio.TestTools.UnitTesting; diff --git a/src/Certify.UI.Shared/ViewModel/AppViewModel/AppViewModel.ManagedCerticates.cs b/src/Certify.UI.Shared/ViewModel/AppViewModel/AppViewModel.ManagedCerticates.cs index 49d7f8f38..b0201450e 100644 --- a/src/Certify.UI.Shared/ViewModel/AppViewModel/AppViewModel.ManagedCerticates.cs +++ b/src/Certify.UI.Shared/ViewModel/AppViewModel/AppViewModel.ManagedCerticates.cs @@ -6,9 +6,9 @@ using System.Threading.Tasks; using System.Windows; using System.Windows.Input; +using Certify.Models.Hub; using Certify.Locales; using Certify.Models; -using Certify.Models.API; using Certify.Models.Reporting; using Certify.UI.Shared; using PropertyChanged; From 9b89c07ad80d469f7af0d9e66155cbe48b835f8e Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Wed, 13 Nov 2024 16:22:03 +0800 Subject: [PATCH 270/328] Package updates --- .../Certify.Aspire.AppHost.csproj | 29 ++++++++++--------- .../Certify.Aspire.ServiceDefaults.csproj | 8 ++--- src/Certify.Client/Certify.Client.csproj | 8 ++--- src/Certify.Core/Certify.Core.csproj | 2 +- src/Certify.Models/Certify.Models.csproj | 4 +-- .../AWSRoute53/Plugin.DNS.AWSRoute53.csproj | 2 +- .../DNS/Azure/Plugin.DNS.Azure.csproj | 2 +- .../Certify.Server.Api.Public.csproj | 6 ++-- .../Certify.Server.Core.csproj | 16 +++++----- src/Certify.Service/App.config | 6 ++-- src/Certify.Service/Certify.Service.csproj | 14 ++++----- src/Certify.Shared/Certify.Shared.Core.csproj | 6 ++-- .../Certify.Core.Tests.Integration.csproj | 8 ++--- .../Certify.Core.Tests.Unit.csproj | 14 ++++----- .../Certify.Service.Tests.Integration.csproj | 8 ++--- .../Certify.UI.Tests.Integration.csproj | 6 ++-- .../Certify.UI.Shared.csproj | 4 +-- src/Certify.UI/App.config | 2 +- src/Certify.UI/Certify.UI.csproj | 2 +- 19 files changed, 74 insertions(+), 73 deletions(-) diff --git a/src/Certify.Aspire/Certify.Aspire.AppHost/Certify.Aspire.AppHost.csproj b/src/Certify.Aspire/Certify.Aspire.AppHost/Certify.Aspire.AppHost.csproj index 5ddd04426..b79fdc93c 100644 --- a/src/Certify.Aspire/Certify.Aspire.AppHost/Certify.Aspire.AppHost.csproj +++ b/src/Certify.Aspire/Certify.Aspire.AppHost/Certify.Aspire.AppHost.csproj @@ -1,20 +1,21 @@  + + + Exe + net9.0; + enable + enable + true + - - Exe - net9.0; - enable - enable - true - + + - - - + - - - - + + + + diff --git a/src/Certify.Aspire/Certify.Aspire.ServiceDefaults/Certify.Aspire.ServiceDefaults.csproj b/src/Certify.Aspire/Certify.Aspire.ServiceDefaults/Certify.Aspire.ServiceDefaults.csproj index 268956d7d..dbe20fb09 100644 --- a/src/Certify.Aspire/Certify.Aspire.ServiceDefaults/Certify.Aspire.ServiceDefaults.csproj +++ b/src/Certify.Aspire/Certify.Aspire.ServiceDefaults/Certify.Aspire.ServiceDefaults.csproj @@ -11,10 +11,10 @@ - - - - + + + + diff --git a/src/Certify.Client/Certify.Client.csproj b/src/Certify.Client/Certify.Client.csproj index cf59f7061..e16c2a851 100644 --- a/src/Certify.Client/Certify.Client.csproj +++ b/src/Certify.Client/Certify.Client.csproj @@ -16,11 +16,11 @@ - - - + + + - + diff --git a/src/Certify.Core/Certify.Core.csproj b/src/Certify.Core/Certify.Core.csproj index 3ec352508..87331d8d0 100644 --- a/src/Certify.Core/Certify.Core.csproj +++ b/src/Certify.Core/Certify.Core.csproj @@ -52,7 +52,7 @@ 1701;1702;CA1068 - + diff --git a/src/Certify.Models/Certify.Models.csproj b/src/Certify.Models/Certify.Models.csproj index 197f014ad..a0efdca33 100644 --- a/src/Certify.Models/Certify.Models.csproj +++ b/src/Certify.Models/Certify.Models.csproj @@ -21,7 +21,7 @@ all runtime; build; native; contentfiles; analyzers - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -30,7 +30,7 @@ all - + diff --git a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj index 35ad77cc8..30d7afd8b 100644 --- a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj +++ b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj b/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj index 3c56bc20d..ce1a334b3 100644 --- a/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj +++ b/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj @@ -10,7 +10,7 @@ - + diff --git a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj index 7b3ab2fe9..396e92d47 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj +++ b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj @@ -18,10 +18,10 @@ - - + + - + diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj index 002d1f8f0..8b229ef55 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj @@ -9,15 +9,15 @@ - - - + + + - - - - - + + + + + diff --git a/src/Certify.Service/App.config b/src/Certify.Service/App.config index 5c0610812..25d314f33 100644 --- a/src/Certify.Service/App.config +++ b/src/Certify.Service/App.config @@ -8,7 +8,7 @@ - + @@ -28,13 +28,13 @@ - + - + diff --git a/src/Certify.Service/Certify.Service.csproj b/src/Certify.Service/Certify.Service.csproj index 011729e81..7442a1dec 100644 --- a/src/Certify.Service/Certify.Service.csproj +++ b/src/Certify.Service/Certify.Service.csproj @@ -27,7 +27,7 @@ - + @@ -37,8 +37,8 @@ - - + + @@ -49,13 +49,13 @@ - - - + + + - + diff --git a/src/Certify.Shared/Certify.Shared.Core.csproj b/src/Certify.Shared/Certify.Shared.Core.csproj index 2ec67bbe0..7a9acd7da 100644 --- a/src/Certify.Shared/Certify.Shared.Core.csproj +++ b/src/Certify.Shared/Certify.Shared.Core.csproj @@ -6,8 +6,8 @@ - - + + @@ -19,7 +19,7 @@ - + diff --git a/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj b/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj index 1f978d2fa..5da394ab1 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj @@ -76,17 +76,17 @@ - - + + - + - + diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj index 8cbe99a9f..aaddb7e9f 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj @@ -105,17 +105,17 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + - - - - + + + + - - + + diff --git a/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj b/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj index 1652ae8d7..2c8b66d2f 100644 --- a/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj @@ -62,10 +62,10 @@ - - - - + + + + diff --git a/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj b/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj index 987844c11..03574aaca 100644 --- a/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj @@ -56,10 +56,10 @@ - + - - + + diff --git a/src/Certify.UI.Shared/Certify.UI.Shared.csproj b/src/Certify.UI.Shared/Certify.UI.Shared.csproj index 521c23eb7..0d0a8a46f 100644 --- a/src/Certify.UI.Shared/Certify.UI.Shared.csproj +++ b/src/Certify.UI.Shared/Certify.UI.Shared.csproj @@ -40,9 +40,9 @@ - + - + NU1701 diff --git a/src/Certify.UI/App.config b/src/Certify.UI/App.config index 919459795..bacd4db00 100644 --- a/src/Certify.UI/App.config +++ b/src/Certify.UI/App.config @@ -8,7 +8,7 @@ - + diff --git a/src/Certify.UI/Certify.UI.csproj b/src/Certify.UI/Certify.UI.csproj index 17e5bee05..ae33f1ffd 100644 --- a/src/Certify.UI/Certify.UI.csproj +++ b/src/Certify.UI/Certify.UI.csproj @@ -160,7 +160,7 @@ all - 8.0.1 + 9.0.0 4.3.4 From ae7e77dae22421ce50ae3a5c15e999903ffef54a Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Wed, 13 Nov 2024 16:24:40 +0800 Subject: [PATCH 271/328] Access control: use generic config item for store --- .../Management/Access/AccessControl.cs | 4 +- .../CertifyManager.Maintenance.cs | 17 +-- .../CertifyManager/CertifyManager.cs | 14 ++- src/Certify.Models/Hub/AccessControl.cs | 28 ++--- src/Certify.Models/Hub/AccessControlConfig.cs | 100 ++++++++++++------ .../Hub/ConfigurationStoreItem.cs | 19 ++++ ...ControlStore.cs => IConfigurationStore.cs} | 2 +- .../DataStores/AccessControlDataStoreTests.cs | 6 +- .../Tests/AccessControlTests.cs | 16 +-- 9 files changed, 133 insertions(+), 73 deletions(-) create mode 100644 src/Certify.Models/Hub/ConfigurationStoreItem.cs rename src/Certify.Models/Providers/{IAccessControlStore.cs => IConfigurationStore.cs} (90%) diff --git a/src/Certify.Core/Management/Access/AccessControl.cs b/src/Certify.Core/Management/Access/AccessControl.cs index ecbf0d8b4..4f7a37e6a 100644 --- a/src/Certify.Core/Management/Access/AccessControl.cs +++ b/src/Certify.Core/Management/Access/AccessControl.cs @@ -13,10 +13,10 @@ namespace Certify.Core.Management.Access public class AccessControl : IAccessControl { - private IAccessControlStore _store; + private IConfigurationStore _store; private ILog _log; - public AccessControl(ILog log, IAccessControlStore store) + public AccessControl(ILog log, IConfigurationStore store) { _store = store; _log = log; diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.Maintenance.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.Maintenance.cs index 90c9ea746..831c7124d 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.Maintenance.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.Maintenance.cs @@ -40,18 +40,21 @@ private async Task UpgradeSettings() var accessControl = await GetCurrentAccessControl(); - if (await accessControl.IsInitialized() == false) + if (CoreAppSettings.Current.IsManagementHub) { - await BootstrapTestAdminUserAndRoles(accessControl); - } - else - { - await UpdateStandardRoles(accessControl); + if (await accessControl.IsInitialized() == false) + { + await BootstrapAdminUserAndRoles(accessControl); + } + else + { + await UpdateStandardRoles(accessControl); + } } } } - private static async Task BootstrapTestAdminUserAndRoles(IAccessControl access) + private static async Task BootstrapAdminUserAndRoles(IAccessControl access) { // setup roles with policies await UpdateStandardRoles(access); diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.cs index 6c11f0279..822dcadc7 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; @@ -9,6 +9,8 @@ using Certify.Core.Management.Challenges; using Certify.Datastore.SQLite; using Certify.Models; +using Certify.Models.Config; +using Certify.Models.Hub; using Certify.Models.Providers; using Certify.Providers; using Microsoft.Extensions.Logging; @@ -18,6 +20,7 @@ namespace Certify.Management { public partial class CertifyManager : ICertifyManager, IDisposable { + private IConfigurationStore _configStore = null; /// /// Storage service for managed certificates /// @@ -290,7 +293,10 @@ private async Task InitDataStore() // default sqlite storage _itemManager = new SQLiteManagedItemStore("", _serviceLog); _credentialsManager = new SQLiteCredentialStore("", _serviceLog); - _accessControl = new AccessControl(_serviceLog, new SQLiteAccessControlStore("", _serviceLog)); + + // config store is a generic store for settings etc + _configStore = new SQLiteConfigurationStore("", _serviceLog); + _accessControl = new AccessControl(_serviceLog, _configStore); } else { @@ -319,7 +325,9 @@ private async Task InitDataStore() { _itemManager = new SQLiteManagedItemStore("", _serviceLog); _credentialsManager = new SQLiteCredentialStore("", _serviceLog); - _accessControl = new AccessControl(_serviceLog, new SQLiteAccessControlStore("",_serviceLog)); + + _configStore = new SQLiteConfigurationStore("", _serviceLog); + _accessControl = new AccessControl(_serviceLog, _configStore); } // attempt to create and delete a test item diff --git a/src/Certify.Models/Hub/AccessControl.cs b/src/Certify.Models/Hub/AccessControl.cs index 50e8ff9b4..eacea4dc7 100644 --- a/src/Certify.Models/Hub/AccessControl.cs +++ b/src/Certify.Models/Hub/AccessControl.cs @@ -3,22 +3,11 @@ namespace Certify.Models.Hub { - public class AccessStoreItem - { - public AccessStoreItem() - { - Id = Guid.NewGuid().ToString(); - } - public string Id { get; set; } - public string Title { get; set; } = string.Empty; - public string Description { get; set; } = string.Empty; - public string ItemType { get; set; } = string.Empty; - } /// /// A Security Principle is a user or service account which can be assigned roles and other permissions /// - public class SecurityPrinciple : AccessStoreItem + public class SecurityPrinciple : ConfigurationStoreItem { public string? Username { get; set; } @@ -42,7 +31,10 @@ public class SecurityPrinciple : AccessStoreItem public string AvatarUrl { get; set; } = string.Empty; } - public class Role : AccessStoreItem + /// + /// A role is a collection of policies which can be assigned to a security principle via AssignedRole + /// + public class Role : ConfigurationStoreItem { public List Policies { get; set; } = new List(); public Role(string id, string title, string description, List? policies = null) @@ -59,9 +51,9 @@ public Role(string id, string title, string description, List? policies } /// - /// A role assigned to a security principle + /// A role assigned to a security principle, optionally specific to a set of resources /// - public class AssignedRole : AccessStoreItem + public class AssignedRole : ConfigurationStoreItem { /// /// Defines the role to be assigned @@ -79,7 +71,7 @@ public class AssignedRole : AccessStoreItem /// /// Defines a restricted resource /// - public class Resource : AccessStoreItem + public class Resource : ConfigurationStoreItem { /// /// Type of this resource @@ -92,7 +84,7 @@ public class Resource : AccessStoreItem public string? Identifier { get; set; } } - public class ResourcePolicy : AccessStoreItem + public class ResourcePolicy : ConfigurationStoreItem { /// @@ -114,7 +106,7 @@ public class ResourcePolicy : AccessStoreItem /// /// Specific system action which may be allowed/disallowed on a specific type of resource /// - public class ResourceAction : AccessStoreItem + public class ResourceAction : ConfigurationStoreItem { public ResourceAction(string id, string title, string resourceType) { diff --git a/src/Certify.Models/Hub/AccessControlConfig.cs b/src/Certify.Models/Hub/AccessControlConfig.cs index 0c545b72e..1d2f0145f 100644 --- a/src/Certify.Models/Hub/AccessControlConfig.cs +++ b/src/Certify.Models/Hub/AccessControlConfig.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Diagnostics; namespace Certify.Models.Hub { @@ -21,6 +22,7 @@ public class StandardRoles policies: new List { StandardPolicies.ManagedItemAdmin, StandardPolicies.StoredCredentialAdmin, + StandardPolicies.ManagedChallengeAdmin, StandardPolicies.AccessAdmin }); @@ -33,6 +35,8 @@ public class StandardRoles public static Role CertificateConsumer { get; } = new Role("cert_consumer", "Certificate Consumer", "User of a given certificate", policies: new List { StandardPolicies.CertificateConsumer }); public static Role StoredCredentialConsumer { get; } = new Role("storedcredential_consumer", "Stored Credential Fetch Consumer", "Can fetch a decrypted stored credential", policies: new List { StandardPolicies.StoredCredentialConsumer }); + + public static Role ManagedChallengeConsumer { get; } = new Role("managedchallenge_consumer", "Managed Challenge Consumer", "Can perform specific managed challenges", policies: new List { StandardPolicies.ManagedChallengeConsumer }); } public class StandardIdentityProviders @@ -67,6 +71,7 @@ public class ResourceTypes public static string Certificate { get; } = "certificate"; public static string StoredCredential { get; } = "storedcredential"; public static string CertificateAuthority { get; } = "ca"; + public static string ManagedChallenge { get; } = "managedchallenge"; } public static class StandardResourceActions @@ -74,7 +79,7 @@ public static class StandardResourceActions public const string CertificateDownload = "certificate_download"; public const string CertificateKeyDownload = "certificate_key_download"; - public const string ManagedItemRequester = "manageditem_requester"; + public const string ManagedItemRequest = "manageditem_requester"; public const string ManagedItemAdd = "manageditem_add"; public const string ManagedItemList = "manageditem_list"; public const string ManagedItemUpdate = "manageditem_update"; @@ -98,15 +103,22 @@ public static class StandardResourceActions public const string SecurityPrincipleDelete = "securityprinciple_delete"; public const string SecurityPrinciplePasswordUpdate = "securityprinciple_password_update"; + public const string ManagedChallengeList = "managedchallenge_list"; + public const string ManagedChallengeUpdate = "managedchallenge_update"; + public const string ManagedChallengeDelete = "managedchallenge_update"; + public const string ManagedChallengeRequest = "managedchallenge_request"; + } public class StandardPolicies { public const string AccessAdmin = "access_admin"; - public const string ManagedItemAdmin = "managed_item_admin"; + public const string ManagedItemAdmin = "manageditem_admin"; public const string CertificateConsumer = "certificate_consumer"; public const string StoredCredentialAdmin = "storedcredential_admin"; public const string StoredCredentialConsumer = "storedcredential_consumer"; + public const string ManagedChallengeConsumer = "managedchallenge_consumer"; + public const string ManagedChallengeAdmin = "managedchallenge_admin"; } public static class Policies @@ -118,7 +130,8 @@ public static List GetStandardRoles() StandardRoles.Administrator, StandardRoles.CertificateManager, StandardRoles.CertificateConsumer, - StandardRoles.StoredCredentialConsumer + StandardRoles.StoredCredentialConsumer, + StandardRoles.ManagedChallengeConsumer }; } @@ -126,43 +139,48 @@ public static List GetStandardResourceActions() { return new List { - new ResourceAction(StandardResourceActions.CertificateDownload, "Certificate Download", ResourceTypes.Certificate), - new ResourceAction(StandardResourceActions.CertificateKeyDownload, "Certificate Private Key Download", ResourceTypes.Certificate), + new(StandardResourceActions.CertificateDownload, "Certificate Download", ResourceTypes.Certificate), + new(StandardResourceActions.CertificateKeyDownload, "Certificate Private Key Download", ResourceTypes.Certificate), + + new(StandardResourceActions.StoredCredentialAdd, "Add New Stored Credential", ResourceTypes.StoredCredential), + new(StandardResourceActions.StoredCredentialUpdate, "Update Stored Credential", ResourceTypes.StoredCredential), + new(StandardResourceActions.StoredCredentialDelete, "Delete Stored Credential", ResourceTypes.StoredCredential), + new(StandardResourceActions.StoredCredentialList, "List Stored Credentials", ResourceTypes.StoredCredential), + new(StandardResourceActions.StoredCredentialDownload, "Fetch Decrypted Stored Credential", ResourceTypes.StoredCredential), - new ResourceAction(StandardResourceActions.StoredCredentialAdd, "Add New Stored Credential", ResourceTypes.StoredCredential), - new ResourceAction(StandardResourceActions.StoredCredentialUpdate, "Update Stored Credential", ResourceTypes.StoredCredential), - new ResourceAction(StandardResourceActions.StoredCredentialDelete, "Delete Stored Credential", ResourceTypes.StoredCredential), - new ResourceAction(StandardResourceActions.StoredCredentialList, "List Stored Credentials", ResourceTypes.StoredCredential), - new ResourceAction(StandardResourceActions.StoredCredentialDownload, "Fetch Decrypted Stored Credential", ResourceTypes.StoredCredential), + new(StandardResourceActions.SecurityPrincipleList, "List Security Principles", ResourceTypes.SecurityPrinciple), + new(StandardResourceActions.SecurityPrincipleAdd, "Add New Security Principle", ResourceTypes.SecurityPrinciple), + new(StandardResourceActions.SecurityPrincipleUpdate,"Update Security Principles", ResourceTypes.SecurityPrinciple), + new(StandardResourceActions.SecurityPrinciplePasswordUpdate, "Update Security Principle Passwords", ResourceTypes.SecurityPrinciple), + new(StandardResourceActions.SecurityPrincipleDelete, "Delete Security Principle", ResourceTypes.SecurityPrinciple), - new ResourceAction(StandardResourceActions.SecurityPrincipleList, "List Security Principles", ResourceTypes.SecurityPrinciple), - new ResourceAction(StandardResourceActions.SecurityPrincipleAdd, "Add New Security Principle", ResourceTypes.SecurityPrinciple), - new ResourceAction(StandardResourceActions.SecurityPrincipleUpdate,"Update Security Principles", ResourceTypes.SecurityPrinciple), - new ResourceAction(StandardResourceActions.SecurityPrinciplePasswordUpdate, "Update Security Principle Passwords", ResourceTypes.SecurityPrinciple), - new ResourceAction(StandardResourceActions.SecurityPrincipleDelete, "Delete Security Principle", ResourceTypes.SecurityPrinciple), + new(StandardResourceActions.ManagedItemRequest, "Request New Managed Items", ResourceTypes.ManagedItem), - new ResourceAction(StandardResourceActions.ManagedItemRequester, "Request New Managed Items", ResourceTypes.ManagedItem), + new(StandardResourceActions.ManagedItemList, "List Managed Items", ResourceTypes.ManagedItem), + new(StandardResourceActions.ManagedItemAdd, "Add Managed Items", ResourceTypes.ManagedItem), + new(StandardResourceActions.ManagedItemUpdate, "Update Managed Items", ResourceTypes.ManagedItem), + new(StandardResourceActions.ManagedItemDelete, "Delete Managed Items", ResourceTypes.ManagedItem), - new ResourceAction(StandardResourceActions.ManagedItemList, "List Managed Items", ResourceTypes.ManagedItem), - new ResourceAction(StandardResourceActions.ManagedItemAdd, "Add Managed Items", ResourceTypes.ManagedItem), - new ResourceAction(StandardResourceActions.ManagedItemUpdate, "Update Managed Items", ResourceTypes.ManagedItem), - new ResourceAction(StandardResourceActions.ManagedItemDelete, "Delete Managed Items", ResourceTypes.ManagedItem), + new(StandardResourceActions.ManagedItemTest, "Test Managed Item Renewal Checks", ResourceTypes.ManagedItem), + new(StandardResourceActions.ManagedItemRenew, "Request/Renew Managed Items", ResourceTypes.ManagedItem), - new ResourceAction(StandardResourceActions.ManagedItemTest, "Test Managed Item Renewal Checks", ResourceTypes.ManagedItem), - new ResourceAction(StandardResourceActions.ManagedItemRenew, "Request/Renew Managed Items", ResourceTypes.ManagedItem), + new(StandardResourceActions.ManagedItemTaskAdd, "Add Managed Item Tasks", ResourceTypes.ManagedItem), + new(StandardResourceActions.ManagedItemTaskUpdate, "Update Managed Item Tasks", ResourceTypes.ManagedItem), + new(StandardResourceActions.ManagedItemTaskDelete, "Delete Managed Item Tasks", ResourceTypes.ManagedItem), - new ResourceAction(StandardResourceActions.ManagedItemTaskAdd, "Add Managed Item Tasks", ResourceTypes.ManagedItem), - new ResourceAction(StandardResourceActions.ManagedItemTaskUpdate, "Update Managed Item Tasks", ResourceTypes.ManagedItem), - new ResourceAction(StandardResourceActions.ManagedItemTaskDelete, "Delete Managed Item Tasks", ResourceTypes.ManagedItem), + new(StandardResourceActions.ManagedItemLogView, "View/Download Managed Item Log", ResourceTypes.ManagedItem), - new ResourceAction(StandardResourceActions.ManagedItemLogView, "View/Download Managed Item Log", ResourceTypes.ManagedItem), + new(StandardResourceActions.ManagedChallengeList, "List managed challenges", ResourceTypes.ManagedChallenge), + new(StandardResourceActions.ManagedChallengeUpdate, "Update managed challenge", ResourceTypes.ManagedChallenge), + new(StandardResourceActions.ManagedChallengeDelete, "Delete managed challenge", ResourceTypes.ManagedChallenge), + new(StandardResourceActions.ManagedChallengeRequest, "Request to perform a managed challenge response", ResourceTypes.ManagedChallenge), }; } public static List GetStandardPolicies() { return new List { - new ResourcePolicy{ + new() { Id=StandardPolicies.ManagedItemAdmin, Title="Managed Item Administration", SecurityPermissionType= SecurityPermissionType.ALLOW, @@ -179,7 +197,7 @@ public static List GetStandardPolicies() StandardResourceActions.ManagedItemLogView } }, - new ResourcePolicy{ + new() { Id=StandardPolicies.AccessAdmin, Title="Access Control Administration", SecurityPermissionType= SecurityPermissionType.ALLOW, @@ -191,7 +209,7 @@ public static List GetStandardPolicies() StandardResourceActions.SecurityPrinciplePasswordUpdate } }, - new ResourcePolicy{ + new() { Id=StandardPolicies.CertificateConsumer, Title="Consume Certificates", SecurityPermissionType= SecurityPermissionType.ALLOW, @@ -200,7 +218,7 @@ public static List GetStandardPolicies() StandardResourceActions.CertificateKeyDownload } }, - new ResourcePolicy{ + new() { Id=StandardPolicies.StoredCredentialAdmin, Title="Stored Credential Administration", SecurityPermissionType= SecurityPermissionType.ALLOW, @@ -211,7 +229,7 @@ public static List GetStandardPolicies() StandardResourceActions.StoredCredentialDelete } }, - new ResourcePolicy{ + new() { Id=StandardPolicies.StoredCredentialConsumer, Title="Stored Credential Consumer", Description="Provides access to fetch a decrypted stored credential.", @@ -220,6 +238,26 @@ public static List GetStandardPolicies() ResourceActions= new List{ StandardResourceActions.StoredCredentialDownload } + }, + new() { + Id=StandardPolicies.ManagedChallengeAdmin, + Title="Managed Challenge Administration", + SecurityPermissionType= SecurityPermissionType.ALLOW, + ResourceActions= new List{ + StandardResourceActions.ManagedChallengeList, + StandardResourceActions.ManagedChallengeUpdate, + StandardResourceActions.ManagedChallengeDelete + } + }, + new() { + Id=StandardPolicies.ManagedChallengeConsumer, + Title="Managed Challenge Consumer", + Description="Allows consumer to request that a managed challenge be performed.", + SecurityPermissionType= SecurityPermissionType.ALLOW, + IsResourceSpecific=true, + ResourceActions= new List{ + StandardResourceActions.ManagedChallengeRequest + } } }; } diff --git a/src/Certify.Models/Hub/ConfigurationStoreItem.cs b/src/Certify.Models/Hub/ConfigurationStoreItem.cs new file mode 100644 index 000000000..bb7f72c94 --- /dev/null +++ b/src/Certify.Models/Hub/ConfigurationStoreItem.cs @@ -0,0 +1,19 @@ +using System; + +namespace Certify.Models.Hub +{ + /// + /// Common data store base type for access control, configuration and security + /// + public class ConfigurationStoreItem + { + public ConfigurationStoreItem() + { + Id = Guid.NewGuid().ToString(); + } + public string Id { get; set; } + public string Title { get; set; } = string.Empty; + public string Description { get; set; } = string.Empty; + public string ItemType { get; set; } = string.Empty; + } +} diff --git a/src/Certify.Models/Providers/IAccessControlStore.cs b/src/Certify.Models/Providers/IConfigurationStore.cs similarity index 90% rename from src/Certify.Models/Providers/IAccessControlStore.cs rename to src/Certify.Models/Providers/IConfigurationStore.cs index 6aa6d62c3..204852e83 100644 --- a/src/Certify.Models/Providers/IAccessControlStore.cs +++ b/src/Certify.Models/Providers/IConfigurationStore.cs @@ -3,7 +3,7 @@ namespace Certify.Providers { - public interface IAccessControlStore + public interface IConfigurationStore { Task Get(string itemType, string id); Task Add(string itemType, T item); diff --git a/src/Certify.Tests/Certify.Core.Tests.Integration/DataStores/AccessControlDataStoreTests.cs b/src/Certify.Tests/Certify.Core.Tests.Integration/DataStores/AccessControlDataStoreTests.cs index 55cbb39de..b5ec84d6d 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Integration/DataStores/AccessControlDataStoreTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Integration/DataStores/AccessControlDataStoreTests.cs @@ -29,9 +29,9 @@ public static IEnumerable TestDataStores } } - private IAccessControlStore GetStore(string storeType = null) + private IConfigurationStore GetStore(string storeType = null) { - IAccessControlStore store = null; + IConfigurationStore store = null; if (storeType == null) { @@ -40,7 +40,7 @@ private IAccessControlStore GetStore(string storeType = null) if (storeType == "sqlite") { - store = new SQLiteAccessControlStore(storageSubfolder: TEST_PATH); + store = new SQLiteConfigurationStore(storageSubfolder: TEST_PATH); } /* else if (storeType == "postgres") { diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/AccessControlTests.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/AccessControlTests.cs index e820ec5d3..9d4c31cc0 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/AccessControlTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/AccessControlTests.cs @@ -13,16 +13,16 @@ namespace Certify.Core.Tests.Unit { - public class MemoryObjectStore : IAccessControlStore + public class MemoryObjectStore : IConfigurationStore { - private ConcurrentDictionary _store = new ConcurrentDictionary(); + private ConcurrentDictionary _store = new ConcurrentDictionary(); - public Task Add(string itemType, AccessStoreItem item) + public Task Add(string itemType, ConfigurationStoreItem item) { item.ItemType = itemType; // clone the item to avoid reference issue mutating the same object, as we are using an in-memory store - var clonedItem = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(item)) as AccessStoreItem; + var clonedItem = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(item)) as ConfigurationStoreItem; return Task.FromResult(_store.TryAdd(clonedItem.Id, clonedItem)); } @@ -48,19 +48,19 @@ public Task Get(string itemType, string id) public Task Add(string itemType, T item) { - var o = item as AccessStoreItem; + var o = item as ConfigurationStoreItem; o.ItemType = itemType; - var clonedItem = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(o)) as AccessStoreItem; + var clonedItem = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(o)) as ConfigurationStoreItem; return Task.FromResult(_store.TryAdd(clonedItem.Id, clonedItem)); } public Task Update(string itemType, T item) { - var o = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(item)) as AccessStoreItem; + var o = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(item)) as ConfigurationStoreItem; _store.TryGetValue(o.Id, out var value); - var c = Task.FromResult((T)Convert.ChangeType(value, typeof(T))).Result as AccessStoreItem; + var c = Task.FromResult((T)Convert.ChangeType(value, typeof(T))).Result as ConfigurationStoreItem; var r = Task.FromResult(_store.TryUpdate(o.Id, o, c)); if (r.Result == false) { From 9391984ce7c06845dab4f670e0a93cedf57a3ab1 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Wed, 13 Nov 2024 16:25:45 +0800 Subject: [PATCH 272/328] Begin Managed (DNS) Challenge implementation --- .../CertifyManager.ManagedChallenges.cs | 255 ++++++++++ .../CertifyManager/ICertifyManager.cs | 7 + .../Challenges/ChallengeResponseService.cs | 4 +- .../Management/SettingsManager.cs | 5 + src/Certify.Models/Hub/ManagedChallenge.cs | 31 +- .../DnsProviderCertifyManaged.cs | 231 +++++++++ .../Plugin.DNS.CertifyManaged.csproj | 14 + .../Certify.API.Public.cs | 438 ++++++++++++++++++ .../nswag.json | 2 +- .../v1/ManagedChallengeController.cs | 60 +++ .../Controllers/ManagedChallengeController.cs | 63 +++ src/Certify.SourceGenerators/ApiMethods.cs | 61 +++ .../PublicAPISourceGenerator.cs | 2 +- 13 files changed, 1156 insertions(+), 17 deletions(-) create mode 100644 src/Certify.Core/Management/CertifyManager/CertifyManager.ManagedChallenges.cs create mode 100644 src/Certify.Providers/DNS/CertifyManaged/DnsProviderCertifyManaged.cs create mode 100644 src/Certify.Providers/DNS/CertifyManaged/Plugin.DNS.CertifyManaged.csproj create mode 100644 src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/ManagedChallengeController.cs create mode 100644 src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/ManagedChallengeController.cs diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagedChallenges.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagedChallenges.cs new file mode 100644 index 000000000..b377a2af6 --- /dev/null +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagedChallenges.cs @@ -0,0 +1,255 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Threading.Tasks; +using Certify.Core.Management.Challenges; +using Certify.Models; +using Certify.Models.Config; +using Certify.Models.Hub; +using Serilog; + +namespace Certify.Management +{ + public partial class CertifyManager + { + public async Task> GetManagedChallenges() + { + return await _configStore.GetItems(nameof(ManagedChallenge)); + } + + public async Task UpdateManagedChallenge(ManagedChallenge update) + { + if (string.IsNullOrEmpty(update.Id)) + { + update.Id = Guid.NewGuid().ToString(); + } + + await _configStore.Update(nameof(ManagedChallenge), update); + return new ActionResult { IsSuccess = true }; + } + + public async Task DeleteManagedChallenge(string id) + { + var deleted = await _configStore.Delete(nameof(ManagedChallenge), id); + + return new ActionResult { IsSuccess = deleted }; + } + + private ManagedChallenge ManagedChallengeFindBestMatch(ManagedChallengeRequest request, ICollection managedChallenges) + { + // find most specific matching challenge for the request - based on ManagedCertificate.GetChallengeConfig + //TODO: filter based on access + var matchedConfig = managedChallenges.FirstOrDefault(c => string.IsNullOrEmpty(c.ChallengeConfig.DomainMatch)); + + if (request.Identifier != null && !string.IsNullOrEmpty(request.Identifier)) + { + // expand configs into per identifier list + var configsPerDomain = new Dictionary(); + foreach (var managedChallenge in managedChallenges.Where(c => !string.IsNullOrEmpty(c.ChallengeConfig.DomainMatch))) + { + var c = managedChallenge.ChallengeConfig; + if (c != null) + { + if (c.DomainMatch != null && !string.IsNullOrEmpty(c.DomainMatch)) + { + c.DomainMatch = c.DomainMatch.Replace(",", ";"); // if user has entered comma separators instead of semicolons, convert now. + + if (!c.DomainMatch.Contains(';')) + { + var domainMatchKey = c.DomainMatch.Trim(); + + // if identifier key is test.com for example we only support one matching config + if (!configsPerDomain.ContainsKey(domainMatchKey)) + { + configsPerDomain.Add(domainMatchKey, managedChallenge); + } + } + else + { + var domains = c.DomainMatch.Split(';'); + foreach (var d in domains) + { + if (!string.IsNullOrWhiteSpace(d)) + { + var domainMatchKey = d.Trim().ToLowerInvariant(); + if (!configsPerDomain.ContainsKey(domainMatchKey)) + { + configsPerDomain.Add(domainMatchKey, managedChallenge); + } + } + } + } + } + } + } + + // if exact match exists, use that + var identifierKey = request.Identifier.ToLowerInvariant() ?? ""; + if (configsPerDomain.TryGetValue(identifierKey, out var value)) + { + return value; + } + + // if explicit wildcard match exists, use that + if (configsPerDomain.TryGetValue("*." + identifierKey, out var wildValue)) + { + return wildValue; + } + + //if a more specific config matches the identifier, use that, in order of longest identifier name match first + var allMatchingConfigKeys = configsPerDomain.Keys.OrderByDescending(l => l.Length); + + foreach (var wildcard in allMatchingConfigKeys.Where(k => k.StartsWith("*.", StringComparison.CurrentCultureIgnoreCase))) + { + if (ManagedCertificate.IsDomainOrWildcardMatch(new List { wildcard }, request.Identifier)) + { + return configsPerDomain[wildcard]; + } + } + + foreach (var configDomain in allMatchingConfigKeys) + { + if (configDomain.EndsWith(request.Identifier.ToLowerInvariant(), StringComparison.CurrentCultureIgnoreCase)) + { + // use longest matching identifier (so subdomain.test.com takes priority + // over test.com, ) + return configsPerDomain[configDomain]; + } + } + } + + // no other matches, just use first + if (matchedConfig != null) + { + return matchedConfig; + } + else + { + // no match, return null + return default; + } + } + public async Task PerformManagedChallengeRequest(ManagedChallengeRequest request) + { + var log = _serviceLog; + + var managedChallenges = await GetManagedChallenges(); + + var matchingChallenge = ManagedChallengeFindBestMatch(request, managedChallenges); + + if (matchingChallenge == null) + { + return new ActionResult { IsSuccess = false, Message = "No matching challenge found" }; + } + else + { + // perform challenge + var _dnsHelper = new DnsChallengeHelper(_credentialsManager); + + DnsChallengeHelperResult dnsResult; + var managedCertificate = new ManagedCertificate + { + RequestConfig = new CertRequestConfig + { + Challenges = new ObservableCollection( + new List + { + matchingChallenge.ChallengeConfig + }) + } + }; + + var domain = new CertIdentifierItem { IdentifierType = CertIdentifierType.Dns, Value = request.Identifier }; + + dnsResult = await _dnsHelper.CompleteDNSChallenge(log, managedCertificate, domain, request.ResponseKey, request.ResponseValue, isTestMode: false); + + if (!dnsResult.Result.IsSuccess) + { + if (dnsResult.IsAwaitingUser) + { + log?.Error($"Action Required: {dnsResult.Result.Message}"); + } + else + { + log?.Error($"DNS update failed: {dnsResult.Result.Message}"); + } + + return dnsResult.Result; + } + else + { + log.Information($"DNS: {dnsResult.Result.Message}"); + } + + var cleanupQueue = new List { }; + + // configure cleanup actions for use after challenge completes + /* pendingAuth.Cleanup = async () => + { + _ = await _dnsHelper.DeleteDNSChallenge(log, managedCertificate, domain, dnsChallenge.Key, dnsChallenge.Value); + }; + */ + + return new ActionResult { IsSuccess = true, Message = $"Challenge response {request.ChallengeType} completed {request.ResponseKey} : {request.ResponseValue}" }; + + } + } + + public async Task CleanupManagedChallengeRequest(ManagedChallengeRequest request) + { + var log = _serviceLog; + + var managedChallenges = await GetManagedChallenges(); + + var matchingChallenge = ManagedChallengeFindBestMatch(request, managedChallenges); + + if (matchingChallenge == null) + { + return new ActionResult { IsSuccess = false, Message = "No matching challenge found" }; + } + else + { + // perform challenge + var _dnsHelper = new DnsChallengeHelper(_credentialsManager); + + var managedCertificate = new ManagedCertificate + { + RequestConfig = new CertRequestConfig + { + Challenges = new ObservableCollection( + new List + { + matchingChallenge.ChallengeConfig + }) + } + }; + + var domain = new CertIdentifierItem { IdentifierType = CertIdentifierType.Dns, Value = request.Identifier }; + + var dnsResult = await _dnsHelper.DeleteDNSChallenge(log, managedCertificate, domain, request.ResponseKey, request.ResponseValue); + + if (!dnsResult.Result.IsSuccess) + { + if (dnsResult.IsAwaitingUser) + { + log?.Error($"Action Required: {dnsResult.Result.Message}"); + } + else + { + log?.Error($"DNS cleanup failed: {dnsResult.Result.Message}"); + } + + return dnsResult.Result; + } + else + { + log.Information($"DNS: {dnsResult.Result.Message}"); + } + + return new ActionResult { IsSuccess = true, Message = $"Challenge cleanup {request.ChallengeType} completed {request.ResponseKey} : {request.ResponseValue}" }; + + } + } + } +} diff --git a/src/Certify.Core/Management/CertifyManager/ICertifyManager.cs b/src/Certify.Core/Management/CertifyManager/ICertifyManager.cs index e5c96540c..1db35f326 100644 --- a/src/Certify.Core/Management/CertifyManager/ICertifyManager.cs +++ b/src/Certify.Core/Management/CertifyManager/ICertifyManager.cs @@ -108,7 +108,14 @@ public interface ICertifyManager Task> UpdateDataStoreConnection(DataStoreConnection dataStore); Task> RemoveDataStoreConnection(string dataStoreId); Task> TestDataStoreConnection(DataStoreConnection connection); + Task TestCredentials(string storageKey); Task GetCurrentAccessControl(); + + Task> GetManagedChallenges(); + Task UpdateManagedChallenge(ManagedChallenge update); + Task DeleteManagedChallenge(string id); + Task PerformManagedChallengeRequest(ManagedChallengeRequest request); + Task CleanupManagedChallengeRequest(ManagedChallengeRequest request); } } diff --git a/src/Certify.Core/Management/Challenges/ChallengeResponseService.cs b/src/Certify.Core/Management/Challenges/ChallengeResponseService.cs index f7adadcab..19e6cc7a0 100644 --- a/src/Certify.Core/Management/Challenges/ChallengeResponseService.cs +++ b/src/Certify.Core/Management/Challenges/ChallengeResponseService.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -621,7 +621,7 @@ private Func PrepareChallengeResponse_TlsSni01(ILog log, ITargetWebServer private DnsChallengeHelper _dnsHelper = null; - private async Task PerformChallengeResponse_Dns01(ILog log, CertIdentifierItem domain, ManagedCertificate managedCertificate, PendingAuthorization pendingAuth, bool isTestMode, bool isCleanupOnly, ICredentialsManager credentialsManager) + internal async Task PerformChallengeResponse_Dns01(ILog log, CertIdentifierItem domain, ManagedCertificate managedCertificate, PendingAuthorization pendingAuth, bool isTestMode, bool isCleanupOnly, ICredentialsManager credentialsManager) { var dnsChallenge = pendingAuth.Challenges.FirstOrDefault(c => c.ChallengeType == SupportedChallengeTypes.CHALLENGE_TYPE_DNS); diff --git a/src/Certify.Core/Management/SettingsManager.cs b/src/Certify.Core/Management/SettingsManager.cs index 3425fadb2..4047aa40d 100644 --- a/src/Certify.Core/Management/SettingsManager.cs +++ b/src/Certify.Core/Management/SettingsManager.cs @@ -183,6 +183,11 @@ public static CoreAppSettings Current /// public bool PerformChallengeCleanupsLast { get; set; } public string CurrentServiceVersion { get; set; } + + /// + /// if true, additional management hub features and data stores may be enabled + /// + public bool IsManagementHub { get; set; } } public class SettingsManager diff --git a/src/Certify.Models/Hub/ManagedChallenge.cs b/src/Certify.Models/Hub/ManagedChallenge.cs index 3a2e96934..98b80dabb 100644 --- a/src/Certify.Models/Hub/ManagedChallenge.cs +++ b/src/Certify.Models/Hub/ManagedChallenge.cs @@ -1,7 +1,4 @@ using System; -using System.Collections.Generic; -using System.Text; -using Certify.Models; namespace Certify.Models.Hub { @@ -9,17 +6,25 @@ namespace Certify.Models.Hub /// Configuration for a managed challenge, such as a DNS challenge for a specific domain/zone /// A managed challenge is one the management hub can complete on behalf of another ACME client /// - public class ManagedChallenge + public class ManagedChallenge : ConfigurationStoreItem { - public ManagedChallenge() - { - Id = Guid.NewGuid().ToString(); - } - public string Id { get; set; } - public string Title { get; set; } = string.Empty; - public string Description { get; set; } = string.Empty; - public string ItemType { get; set; } = string.Empty; + public CertRequestChallengeConfig? ChallengeConfig { get; set; } + } + + public class ManagedChallengeRequest + { + /// + /// The type of challenge to perform (e.g. dns-01) + /// + public string ChallengeType { get; set; } = string.Empty; - public CertRequestChallengeConfig ChallengeConfig { get; set; } + /// + /// domain etc challenge is being performed for + /// + public string Identifier { get; set; } = string.Empty; + public string ResponseKey { get; set; } = string.Empty; + public string ResponseValue { get; set; } = string.Empty; + public string AuthKey { get; set; } = string.Empty; + public string AuthSecret { get; set; } = string.Empty; } } diff --git a/src/Certify.Providers/DNS/CertifyManaged/DnsProviderCertifyManaged.cs b/src/Certify.Providers/DNS/CertifyManaged/DnsProviderCertifyManaged.cs new file mode 100644 index 000000000..a68ce4b86 --- /dev/null +++ b/src/Certify.Providers/DNS/CertifyManaged/DnsProviderCertifyManaged.cs @@ -0,0 +1,231 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Threading.Tasks; +using Certify.Models; +using Certify.Models.Config; +using Certify.Models.Hub; +using Certify.Models.Plugins; +using Certify.Models.Providers; +using Certify.Plugins; +using Newtonsoft.Json; + +/// +/// Certify Managed Challenge for DNS. Uses the Certify Management Hub API to perform pre-configured DNS challenges. +/// +namespace Certify.Providers.DNS.CertifyManaged +{ + public class DnsProviderCertifyManagedProvider : PluginProviderBase, IDnsProviderProviderPlugin { } + + public class DnsProviderCertifyManaged : IDnsProvider + { + public static ChallengeProviderDefinition Definition + { + get + { + return new ChallengeProviderDefinition + { + Id = "DNS01.API.CertifyManaged", + Title = "Certify Managed Challenge API", + Description = "Performs challenge responses via the Certify Management Hub API.", + HelpUrl = "https://docs.certifytheweb.com/", + PropagationDelaySeconds = 60, + ProviderParameters = new List{ + new ProviderParameter{ Key="api",Name="Management Hub API Url", IsRequired=true, IsCredential=false, IsPassword=false, Value="https://localhost:44361/", Description="Base URL for a Certify Management Hub API" }, + new ProviderParameter{ Key="authkey",Name="Auth Key", IsRequired=true, IsCredential=true, IsPassword=false, Description="API Auth Key" }, + new ProviderParameter{ Key="authsecret",Name="Auth Secret", IsRequired=true, IsCredential=true, IsPassword=true, Description="API Auth Secret" } + }, + IsTestModeSupported = true, + ChallengeType = SupportedChallengeTypes.CHALLENGE_TYPE_DNS, + Config = "Provider=Certify.Providers.DNS.CertifyManaged", + HandlerType = ChallengeHandlerType.INTERNAL + }; + } + } + + public DnsProviderCertifyManaged() : base() + { + +#if DEBUG + // allow invalid TLS + var handler = new HttpClientHandler(); + handler.ClientCertificateOptions = ClientCertificateOption.Manual; + handler.ServerCertificateCustomValidationCallback = + (httpRequestMessage, cert, cetChain, policyErrors) => + { + return true; + }; + _client = new HttpClient(handler); + +#else + _client = new HttpClient(); + +#endif + _client.DefaultRequestHeaders.Add("User-Agent", "Certify/DnsProviderCertifyManaged"); + + _serializerSettings = new JsonSerializerSettings + { + Formatting = Formatting.None, + MissingMemberHandling = MissingMemberHandling.Ignore, + NullValueHandling = NullValueHandling.Ignore + }; + } + + private Dictionary _credentials; + + private ILog _log; + + private int? _customPropagationDelay = null; + + public int PropagationDelaySeconds => (_customPropagationDelay != null ? (int)_customPropagationDelay : Definition.PropagationDelaySeconds); + + public string ProviderId => Definition.Id; + + public string ProviderTitle => Definition.Title; + + public string ProviderDescription => Definition.Description; + + public string ProviderHelpUrl => Definition.HelpUrl; + + public bool IsTestModeSupported => Definition.IsTestModeSupported; + + public List ProviderParameters => Definition.ProviderParameters; + + private HttpClient _client; + + private Dictionary _parameters = new Dictionary(); + + private JsonSerializerSettings _serializerSettings; + + private string _settingsPath { get; set; } + private Uri _apiBaseUri { get; set; } + + public async Task Test() + { + // TODO: dummy request to test API connection + return await Task.FromResult(new ActionResult { IsSuccess = true, Message = "Test completed, but no zones returned." }); + } + + public async Task CreateRecord(DnsRecord request) + { + var apiUri = new Uri(_apiBaseUri, "/api/v1/managedchallenge/request"); + var req = new HttpRequestMessage(HttpMethod.Post, apiUri); + + var authKey = _credentials["authkey"]; + var authSecret = _credentials["authsecret"]; + + var update = new ManagedChallengeRequest + { + ChallengeType = "dns-01", + Identifier = request.TargetDomainName, + ResponseKey = request.RecordName, + ResponseValue = request.RecordValue, + AuthKey = authKey, + AuthSecret = authSecret + }; + + var json = JsonConvert.SerializeObject(update, _serializerSettings); + + req.Content = new StringContent(json, System.Text.UnicodeEncoding.UTF8, "application/json"); + + var result = await _client.SendAsync(req); + + try + { + if (result.IsSuccessStatusCode) + { + var responseJson = await result.Content.ReadAsStringAsync(); + var updateResult = JsonConvert.DeserializeObject(responseJson); + + return new ActionResult { IsSuccess = true, Message = $"Updated: {request.RecordName} :: {request.RecordValue}" }; + } + else + { + return new ActionResult { IsSuccess = false, Message = $"Update failed: API URL is valid [{apiUri}], auth credentials are correct and authorised for a matching managed challenge." }; + } + } + catch (Exception exp) + { + return new ActionResult { IsSuccess = false, Message = $"Update failed: {exp.Message}" }; + } + } + + public async Task DeleteRecord(DnsRecord request) + { + var apiUri = new Uri(_apiBaseUri, "/api/v1/managedchallenge/cleanup"); + var req = new HttpRequestMessage(HttpMethod.Post, apiUri); + + var authKey = _credentials["authkey"]; + var authSecret = _credentials["authsecret"]; + + var update = new ManagedChallengeRequest + { + ChallengeType = "dns-01", + Identifier = request.TargetDomainName, + ResponseKey = request.RecordName, + ResponseValue = request.RecordValue, + AuthKey = authKey, + AuthSecret = authSecret + }; + + var json = JsonConvert.SerializeObject(update, _serializerSettings); + + req.Content = new StringContent(json, System.Text.UnicodeEncoding.UTF8, "application/json"); + + var result = await _client.SendAsync(req); + + try + { + if (result.IsSuccessStatusCode) + { + var responseJson = await result.Content.ReadAsStringAsync(); + var updateResult = JsonConvert.DeserializeObject(responseJson); + + return new ActionResult { IsSuccess = true, Message = $"Cleanup: {request.RecordName} :: {request.RecordValue}" }; + } + else + { + return new ActionResult { IsSuccess = false, Message = $"Cleanup failed: API URL is valid [{apiUri}], auth credentials are correct and authorised for a matching managed challenge." }; + } + } + catch (Exception exp) + { + return new ActionResult { IsSuccess = false, Message = $"Cleanup failed: {exp.Message}" }; + } + } + + public async Task InitProvider(Dictionary credentials, Dictionary parameters, ILog log = null) + { + _credentials = credentials; + _log = log; + _parameters = parameters; + + if (parameters?.ContainsKey("propagationdelay") == true) + { + if (int.TryParse(parameters["propagationdelay"], out var customPropDelay)) + { + _customPropagationDelay = customPropDelay; + } + } + + if (_parameters.TryGetValue("api", out var apiBase) && !string.IsNullOrWhiteSpace(apiBase)) + { + _apiBaseUri = new System.Uri(apiBase); + + if (!_apiBaseUri.ToString().EndsWith("/")) + { + _apiBaseUri = new Uri($"{_apiBaseUri}/"); + } + + _client.BaseAddress = _apiBaseUri; + } + + return await Task.FromResult(true); + } + + public Task> GetZones() + { + return Task.FromResult(new List()); + } + } +} diff --git a/src/Certify.Providers/DNS/CertifyManaged/Plugin.DNS.CertifyManaged.csproj b/src/Certify.Providers/DNS/CertifyManaged/Plugin.DNS.CertifyManaged.csproj new file mode 100644 index 000000000..c483f519a --- /dev/null +++ b/src/Certify.Providers/DNS/CertifyManaged/Plugin.DNS.CertifyManaged.csproj @@ -0,0 +1,14 @@ + + + + netstandard2.0 + AnyCPU + Plugin.DNS.CertifyManaged + + + + + + + + diff --git a/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs b/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs index 962b12550..e0568ed97 100644 --- a/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs +++ b/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs @@ -3331,6 +3331,444 @@ public virtual async System.Threading.Tasks.Task FlushHubManagedInstancesAsync(S } } + /// + /// Request a challenge response + /// + /// OK + /// A server side error occurred. + public virtual System.Threading.Tasks.Task PerformManagedChallengeAsync(ManagedChallengeRequest body) + { + return PerformManagedChallengeAsync(body, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Request a challenge response + /// + /// OK + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task PerformManagedChallengeAsync(ManagedChallengeRequest body, System.Threading.CancellationToken cancellationToken) + { + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(body, JsonSerializerSettings); + var content_ = new System.Net.Http.StringContent(json_); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); + // Operation Path: "api/v1/managedchallenge/request" + urlBuilder_.Append("api/v1/managedchallenge/request"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// + /// Perform optional cleanup of a previously requested challenge response + /// + /// OK + /// A server side error occurred. + public virtual System.Threading.Tasks.Task CleanupManagedChallengeAsync(ManagedChallengeRequest body) + { + return CleanupManagedChallengeAsync(body, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Perform optional cleanup of a previously requested challenge response + /// + /// OK + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task CleanupManagedChallengeAsync(ManagedChallengeRequest body, System.Threading.CancellationToken cancellationToken) + { + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(body, JsonSerializerSettings); + var content_ = new System.Net.Http.StringContent(json_); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); + // Operation Path: "api/v1/managedchallenge/cleanup" + urlBuilder_.Append("api/v1/managedchallenge/cleanup"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// + /// Get list of available managed challenges (DNS challenge delegation etc) [Generated by Certify.SourceGenerators] + /// + /// OK + /// A server side error occurred. + public virtual System.Threading.Tasks.Task> GetManagedChallengesAsync() + { + return GetManagedChallengesAsync(System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Get list of available managed challenges (DNS challenge delegation etc) [Generated by Certify.SourceGenerators] + /// + /// OK + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task> GetManagedChallengesAsync(System.Threading.CancellationToken cancellationToken) + { + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); + // Operation Path: "api/v1/managedchallenge/list" + urlBuilder_.Append("api/v1/managedchallenge/list"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync>(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// + /// Add/update a managed challenge (DNS challenge delegation etc) [Generated by Certify.SourceGenerators] + /// + /// OK + /// A server side error occurred. + public virtual System.Threading.Tasks.Task UpdateManagedChallengeAsync(ManagedChallenge body) + { + return UpdateManagedChallengeAsync(body, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Add/update a managed challenge (DNS challenge delegation etc) [Generated by Certify.SourceGenerators] + /// + /// OK + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task UpdateManagedChallengeAsync(ManagedChallenge body, System.Threading.CancellationToken cancellationToken) + { + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(body, JsonSerializerSettings); + var content_ = new System.Net.Http.StringContent(json_); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); + // Operation Path: "api/v1/managedchallenge/update" + urlBuilder_.Append("api/v1/managedchallenge/update"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// + /// Delete a managed challenge (DNS challenge delegation etc) [Generated by Certify.SourceGenerators] + /// + /// OK + /// A server side error occurred. + public virtual System.Threading.Tasks.Task RemoveManagedChallengeAsync(string id) + { + return RemoveManagedChallengeAsync(id, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Delete a managed challenge (DNS challenge delegation etc) [Generated by Certify.SourceGenerators] + /// + /// OK + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task RemoveManagedChallengeAsync(string id, System.Threading.CancellationToken cancellationToken) + { + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("DELETE"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); + // Operation Path: "api/v1/managedchallenge/remove" + urlBuilder_.Append("api/v1/managedchallenge/remove"); + urlBuilder_.Append('?'); + if (id != null) + { + urlBuilder_.Append(System.Uri.EscapeDataString("id")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(id, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); + } + urlBuilder_.Length--; + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + /// /// Get preview of steps for certificate order and deployment /// diff --git a/src/Certify.Server/Certify.Server.Api.Public.Client/nswag.json b/src/Certify.Server/Certify.Server.Api.Public.Client/nswag.json index 6fe5e2669..12e2953f1 100644 --- a/src/Certify.Server/Certify.Server.Api.Public.Client/nswag.json +++ b/src/Certify.Server/Certify.Server.Api.Public.Client/nswag.json @@ -114,4 +114,4 @@ "newLineBehavior": "Auto" } } -} +} \ No newline at end of file diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/ManagedChallengeController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/ManagedChallengeController.cs new file mode 100644 index 000000000..13680e8b3 --- /dev/null +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/ManagedChallengeController.cs @@ -0,0 +1,60 @@ +using Certify.Client; +using Certify.Models.Hub; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Certify.Server.Api.Public.Controllers +{ + /// + /// Provides managed challenges such as DNS challenges on behalf of other ACME clients + /// + [ApiController] + [Route("api/v1/[controller]")] + public partial class ManagedChallengeController : ApiControllerBase + { + + private readonly ILogger _logger; + + private readonly ICertifyInternalApiClient _client; + + /// + /// Constructor + /// + /// + /// + public ManagedChallengeController(ILogger logger, ICertifyInternalApiClient client) + { + _logger = logger; + _client = client; + } + + /// + /// Request a challenge response + /// + /// + [HttpPost] + [Route("request")] + [AllowAnonymous] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Certify.Models.Config.ActionResult))] + public async Task PerformManagedChallenge(ManagedChallengeRequest request) + { + var result = await _client.PerformManagedChallenge(request, null); + return new OkObjectResult(result); + } + + /// + /// Perform optional cleanup of a previously requested challenge response + /// + /// + [HttpPost] + [Route("cleanup")] + [AllowAnonymous] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Certify.Models.Config.ActionResult))] + public async Task CleanupManagedChallenge(ManagedChallengeRequest request) + { + var result = await _client.CleanupManagedChallenge(request, null); + return new OkObjectResult(result); + } + } +} diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/ManagedChallengeController.cs b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/ManagedChallengeController.cs new file mode 100644 index 000000000..f37226f19 --- /dev/null +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/ManagedChallengeController.cs @@ -0,0 +1,63 @@ +using Certify.Management; +using Certify.Models.Config; +using Certify.Models.Hub; +using Microsoft.AspNetCore.Mvc; + +namespace Certify.Service.Controllers +{ + [ApiController] + [Route("api/managedchallenge")] + public class ManagedChallengeController : ControllerBase + { + private ICertifyManager _certifyManager; + + public ManagedChallengeController(ICertifyManager manager) + { + _certifyManager = manager; + } + + [HttpGet, Route("")] + public async Task> Get() + { + DebugLog(); + + return await _certifyManager.GetManagedChallenges(); + } + + [HttpPost, Route("")] + public async Task Update(ManagedChallenge update) + { + DebugLog(); + + return await _certifyManager.UpdateManagedChallenge(update); + } + + [HttpDelete, Route("{id}")] + public async Task Delete(string id) + { + DebugLog(); + + return await _certifyManager.DeleteManagedChallenge(id); + } + + [HttpPost, Route("request")] + public async Task PerformChallengeResponse(ManagedChallengeRequest request) + { + DebugLog(); + + var result = await _certifyManager.PerformManagedChallengeRequest(request); + + return result; + } + + [HttpPost, Route("cleanup")] + public async Task CleanupChallengeResponse(ManagedChallengeRequest request) + { + DebugLog(); + + var result = await _certifyManager.CleanupManagedChallengeRequest(request); + + return result; + } + } +} diff --git a/src/Certify.SourceGenerators/ApiMethods.cs b/src/Certify.SourceGenerators/ApiMethods.cs index 4980bf857..27f5988c0 100644 --- a/src/Certify.SourceGenerators/ApiMethods.cs +++ b/src/Certify.SourceGenerators/ApiMethods.cs @@ -144,7 +144,68 @@ public static List GetApiDefinitions() ReturnType = "Models.Config.ActionResult", Params = new Dictionary{{"id","string"}} }, + new GeneratedAPI { + OperationName = "GetManagedChallenges", + OperationMethod = HttpGet, + Comment = "Get list of available managed challenges (DNS challenge delegation etc)", + PublicAPIController = "ManagedChallenge", + PublicAPIRoute = "list", + ServiceAPIRoute = "managedchallenge", + ReturnType = "ICollection" + }, + + new GeneratedAPI { + + OperationName = "UpdateManagedChallenge", + OperationMethod = HttpPost, + Comment = "Add/update a managed challenge (DNS challenge delegation etc)", + PublicAPIController = "ManagedChallenge", + PublicAPIRoute = "update", + ServiceAPIRoute = "managedchallenge", + ReturnType = "Models.Config.ActionResult", + Params = new Dictionary{ + { "update", "Certify.Models.Hub.ManagedChallenge" } + } + }, + + new GeneratedAPI { + + OperationName = "RemoveManagedChallenge", + OperationMethod = HttpDelete, + Comment = "Delete a managed challenge (DNS challenge delegation etc)", + PublicAPIController = "ManagedChallenge", + PublicAPIRoute = "remove", + ServiceAPIRoute = "managedchallenge", + ReturnType = "Models.Config.ActionResult", + Params = new Dictionary{ + { "id", "string" } + } + }, + new GeneratedAPI { + + OperationName = "PerformManagedChallenge", + OperationMethod = HttpPost, + Comment = "Perform a managed challenge (DNS challenge delegation etc)", + PublicAPIController=null, // skip public controller implementation + ServiceAPIRoute = "managedchallenge/request", + ReturnType = "Models.Config.ActionResult", + Params = new Dictionary{ + { "request", "Certify.Models.Hub.ManagedChallengeRequest" } + } + }, + new GeneratedAPI { + + OperationName = "CleanupManagedChallenge", + OperationMethod = HttpPost, + Comment = "Perform cleanup for a previously managed challenge (DNS challenge delegation etc)", + PublicAPIController=null, // skip public controller implementation + ServiceAPIRoute = "managedchallenge/cleanup", + ReturnType = "Models.Config.ActionResult", + Params = new Dictionary{ + { "request", "Certify.Models.Hub.ManagedChallengeRequest" } + } + }, /* per instance API, via management hub */ new GeneratedAPI { OperationName = "GetAcmeAccounts", diff --git a/src/Certify.SourceGenerators/PublicAPISourceGenerator.cs b/src/Certify.SourceGenerators/PublicAPISourceGenerator.cs index 493eaf5a5..f330c2308 100644 --- a/src/Certify.SourceGenerators/PublicAPISourceGenerator.cs +++ b/src/Certify.SourceGenerators/PublicAPISourceGenerator.cs @@ -42,7 +42,7 @@ public void Execute(GeneratorExecutionContext context) var apiParamCall = paramSet.Any() ? string.Join(", ", paramSet.Select(p => $"{p.Key}")) : ""; var apiParamCallWithoutAuthContext = config.Params.Any() ? string.Join(", ", config.Params.Select(p => $"{p.Key}")) : ""; - if (context.Compilation.AssemblyName.EndsWith("Api.Public")) + if (context.Compilation.AssemblyName.EndsWith("Api.Public") && config.PublicAPIController!=null) { ImplementPublicAPI(context, config, apiParamDeclWithoutAuthContext, apiParamCall); } From b8dfe45155cd615dd7c56d30cd1115a320631eae Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Wed, 13 Nov 2024 16:26:00 +0800 Subject: [PATCH 273/328] Update API name --- .../Certify.Server.Api.Public/Startup.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Certify.Server/Certify.Server.Api.Public/Startup.cs b/src/Certify.Server/Certify.Server.Api.Public/Startup.cs index 991b23bf9..d335c8730 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Startup.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Startup.cs @@ -86,9 +86,9 @@ public void ConfigureServices(IServiceCollection services) c.SwaggerDoc("v1", new OpenApiInfo { - Title = "Certify Server API", + Title = "Certify Management Hub API", Version = "v1", - Description = "The Certify Server API provides a certificate services API for use in devops, CI/CD, middleware etc. Certificates are managed by Certify The Web (https://certifytheweb.com) on the primary server using ACME, with API access controlled using API tokens." + Description = "The Certify Management Hub API provides a certificate services API for use in UI, devops, CI/CD, middleware etc. See certifytheweb.com for more details." }); c.UseAllOfToExtendReferenceSchemas(); @@ -138,6 +138,8 @@ public void ConfigureServices(IServiceCollection services) }; }); + + }); // connect to primary certify service @@ -238,8 +240,8 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) app.UseSwaggerUI(c => { c.RoutePrefix = "docs"; - c.DocumentTitle = "Certify Server API"; - c.SwaggerEndpoint("/swagger/v1/swagger.json", "Certify Server API"); + c.DocumentTitle = "Certify Management Hub API"; + c.SwaggerEndpoint("/swagger/v1/swagger.json", "Certify Management Hub API"); }); #endif } From 5a89d4477632294d528fbed3c28e846e592c490b Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Wed, 13 Nov 2024 16:26:25 +0800 Subject: [PATCH 274/328] Cloudflare: quote DNS TXT values --- .../DNS/Cloudflare/DnsProviderCloudflare.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/Certify.Providers/DNS/Cloudflare/DnsProviderCloudflare.cs b/src/Certify.Providers/DNS/Cloudflare/DnsProviderCloudflare.cs index fc534e09a..e3299248e 100644 --- a/src/Certify.Providers/DNS/Cloudflare/DnsProviderCloudflare.cs +++ b/src/Certify.Providers/DNS/Cloudflare/DnsProviderCloudflare.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; @@ -6,6 +6,7 @@ using Certify.Models.Config; using Certify.Models.Plugins; using Certify.Models.Providers; +using Certify.Models.Shared.Validation; using Certify.Plugins; using Newtonsoft.Json; @@ -203,8 +204,17 @@ private async Task> GetDnsRecords(string zoneId) return records; } + private string NormalizeTXTValue(string val) + { + val = val.Trim("\"".ToCharArray()); + val = $"\"{val}\""; // cloudflare wants TXT value to be quoted + + return val; + } private async Task AddDnsRecord(string zoneId, string name, string value) { + value = NormalizeTXTValue(value); + var request = CreateRequest(HttpMethod.Post, string.Format(_createRecordUri, zoneId)); request.Content = new StringContent( @@ -315,6 +325,7 @@ public async Task DeleteRecord(DnsRecord request) public async Task DeleteRecord(DnsRecord request, bool requireSameValue) { + request.RecordValue = NormalizeTXTValue(request.RecordValue); if (string.IsNullOrEmpty(request.RecordName)) { From 01b66773a41232cabf3f4db48a420f9b46d489e9 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Fri, 15 Nov 2024 16:17:26 +0800 Subject: [PATCH 275/328] Report managed instance os and client details --- .../CertifyManager.ManagementHub.cs | 10 ++++++++- src/Certify.Models/Hub/ManagedInstanceInfo.cs | 5 +++++ src/Certify.Models/Util/EnvironmentUtil.cs | 22 +++++++++++++++---- 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs index ce8f43856..7a3411249 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs @@ -8,6 +8,8 @@ using Certify.Models; using Certify.Models.Config; using Certify.Shared.Core.Utils; +using System.Runtime.InteropServices; +using Certify.Locales; namespace Certify.Management { @@ -46,10 +48,16 @@ private async Task StartManagementHubConnection(string hubUri) _serviceLog.Debug("Attempting connection to management hub {hubUri}", hubUri); + var appVersion = Util.GetAppVersion().ToString(); + var instanceInfo = new ManagedInstanceInfo { InstanceId = $"{this.InstanceId}", - Title = $"{Environment.MachineName} [{EnvironmentUtil.GetFriendlyOSName()}]", + Title = $"{Environment.MachineName}", + OS = EnvironmentUtil.GetFriendlyOSName(detailed: false), + OSVersion = EnvironmentUtil.GetFriendlyOSName(), + ClientVersion = appVersion, + ClientName = ConfigResources.AppName }; if (_managementServerClient != null) diff --git a/src/Certify.Models/Hub/ManagedInstanceInfo.cs b/src/Certify.Models/Hub/ManagedInstanceInfo.cs index 38d199f5e..96d068880 100644 --- a/src/Certify.Models/Hub/ManagedInstanceInfo.cs +++ b/src/Certify.Models/Hub/ManagedInstanceInfo.cs @@ -8,6 +8,11 @@ public class ManagedInstanceInfo { public string InstanceId { get; set; } = string.Empty; public string Title { get; set; } = string.Empty; + public string OS { get; set; } = string.Empty; + public string OSVersion { get; set; } = string.Empty; + public string ClientName { get; set; } = string.Empty; + public string ClientVersion { get; set; } = string.Empty; + public List Tags { get; set; } = new List(); public DateTimeOffset LastReported { get; set; } } diff --git a/src/Certify.Models/Util/EnvironmentUtil.cs b/src/Certify.Models/Util/EnvironmentUtil.cs index bad3337fe..ae258c9e6 100644 --- a/src/Certify.Models/Util/EnvironmentUtil.cs +++ b/src/Certify.Models/Util/EnvironmentUtil.cs @@ -85,7 +85,7 @@ private static void CreateAndApplyRestrictedACL(string path) /// /// optional subfolder to include /// full app data with with optional subdirectory - public static string CreateAppDataPath(string? subDirectory = null, bool skipCreation=false) + public static string CreateAppDataPath(string? subDirectory = null, bool skipCreation = false) { var parts = new List() { @@ -119,14 +119,19 @@ public static string CreateAppDataPath(string? subDirectory = null, bool skipCre return path; } - public static string GetFriendlyOSName() + public static string GetFriendlyOSName(bool detailed = true) { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - return $"{RuntimeInformation.OSDescription}"; + return detailed ? $"{RuntimeInformation.OSDescription}" : "Windows"; } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { + if (!detailed) + { + return "Linux"; + } + var filePath = "/etc/os-release"; try @@ -149,7 +154,16 @@ public static string GetFriendlyOSName() return $"Linux - {RuntimeInformation.OSDescription}"; } } - + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + return detailed ? $"{RuntimeInformation.OSDescription}" : "macOS"; + } +#if NET9_0_OR_GREATER + else if (RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD)) + { + return detailed ? $"{RuntimeInformation.OSDescription}" : "FreeBSD"; + } +#endif return $"{RuntimeInformation.OSDescription}"; } } From 8d7893ab249badd731e9f1f15663c24a47a36a40 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Mon, 18 Nov 2024 16:20:36 +0800 Subject: [PATCH 276/328] Package updates --- src/Certify.Client/Certify.Client.csproj | 2 +- src/Certify.Models/Certify.Models.csproj | 2 +- .../DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj | 2 +- src/Certify.Shared/Certify.Shared.Core.csproj | 1 + 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Certify.Client/Certify.Client.csproj b/src/Certify.Client/Certify.Client.csproj index e16c2a851..1f44391a0 100644 --- a/src/Certify.Client/Certify.Client.csproj +++ b/src/Certify.Client/Certify.Client.csproj @@ -18,7 +18,7 @@ - + diff --git a/src/Certify.Models/Certify.Models.csproj b/src/Certify.Models/Certify.Models.csproj index a0efdca33..6a77d6930 100644 --- a/src/Certify.Models/Certify.Models.csproj +++ b/src/Certify.Models/Certify.Models.csproj @@ -21,7 +21,7 @@ all runtime; build; native; contentfiles; analyzers - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj index 30d7afd8b..ced5f8d5a 100644 --- a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj +++ b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/Certify.Shared/Certify.Shared.Core.csproj b/src/Certify.Shared/Certify.Shared.Core.csproj index 7a9acd7da..17b93737c 100644 --- a/src/Certify.Shared/Certify.Shared.Core.csproj +++ b/src/Certify.Shared/Certify.Shared.Core.csproj @@ -6,6 +6,7 @@ + From 0103809e6ff84e4e1b501c8efce0b17088e53d11 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Tue, 19 Nov 2024 19:27:51 +0800 Subject: [PATCH 277/328] Implement progress state reporting via mgmt hub --- src/Certify.Client/ManagementServerClient.cs | 19 ++++++++++ .../CertifyManager.ManagementHub.cs | 36 ++++++++++++++++--- .../CertifyManager/CertifyManager.cs | 8 +++-- .../Hub/ManagementHubMessages.cs | 10 +++++- .../ManagementHub/InstanceManagementHub.cs | 30 +++++++++++++--- .../InstanceManagementStateProvider.cs | 26 +++----------- .../ManagedCertificateController.cs | 2 +- 7 files changed, 97 insertions(+), 34 deletions(-) diff --git a/src/Certify.Client/ManagementServerClient.cs b/src/Certify.Client/ManagementServerClient.cs index e2cd6dffb..af2db99e8 100644 --- a/src/Certify.Client/ManagementServerClient.cs +++ b/src/Certify.Client/ManagementServerClient.cs @@ -137,5 +137,24 @@ public void SendInstanceInfo(Guid commandId, bool isCommandResponse = true) result.ObjectValue = _instanceInfo; _connection.SendAsync(ManagementHubMessages.ReceiveCommandResult, result); } + + /// + /// Send mgmt hub a general notification message to be actioned + /// + public void SendNotificationToManagementHub(string msgCommandType, object updateMsg) + { + var result = new InstanceCommandResult + { + CommandId = Guid.NewGuid(), + InstanceId = _instanceInfo.InstanceId, + CommandType = msgCommandType, + Value = System.Text.Json.JsonSerializer.Serialize(updateMsg), + ObjectValue = updateMsg, + IsCommandResponse = false + }; + + result.ObjectValue = updateMsg; + _connection.SendAsync(ManagementHubMessages.ReceiveCommandResult, result); + } } } diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs index 7a3411249..889ff7174 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs @@ -129,15 +129,26 @@ private async Task _managementServerClient_OnGetCommandRe var managedCertArg = args.FirstOrDefault(a => a.Key == "managedCert"); var managedCert = JsonSerializer.Deserialize(managedCertArg.Value); - val = await UpdateManagedCertificate(managedCert); + var item = await UpdateManagedCertificate(managedCert); + + val = item; + + ReportManagedItemUpdateToMgmtHub(item); } - else if (arg.CommandType == ManagementHubCommands.RemoveDeleteManagedItem) + else if (arg.CommandType == ManagementHubCommands.RemoveManagedItem) { // delete a single managed item var args = JsonSerializer.Deserialize[]>(arg.Value); var managedCertIdArg = args.FirstOrDefault(a => a.Key == "managedCertId"); - await DeleteManagedCertificate(managedCertIdArg.Value); + var actionResult = await DeleteManagedCertificate(managedCertIdArg.Value); + + val = actionResult; + + if (actionResult.IsSuccess) + { + ReportManagedItemDeleteToMgmtHub(managedCertIdArg.Value); + } } else if (arg.CommandType == ManagementHubCommands.TestManagedItemConfiguration) { @@ -264,9 +275,26 @@ private async Task _managementServerClient_OnGetCommandRe return result; } + private void ReportManagedItemUpdateToMgmtHub(ManagedCertificate item) + { + if (item != null) + { + _managementServerClient?.SendNotificationToManagementHub(ManagementHubCommands.NotificationUpdatedManagedItem, item); + } + } + private void ReportManagedItemDeleteToMgmtHub(string id) + { + _managementServerClient?.SendNotificationToManagementHub(ManagementHubCommands.NotificationRemovedManagedItem, id); + } + + private void ReportRequestProgressToMgmtHub(RequestProgressState progress) + { + _managementServerClient?.SendNotificationToManagementHub(ManagementHubCommands.NotificationManagedItemRequestProgress, progress); + } + private void _managementServerClient_OnConnectionReconnecting() { - _serviceLog.Warning("Reconnecting to Management."); + _serviceLog.Warning("Reconnecting to Management Hub."); } private void GenerateDemoItems() diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.cs index 822dcadc7..105f5b3a0 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; @@ -325,7 +325,7 @@ private async Task InitDataStore() { _itemManager = new SQLiteManagedItemStore("", _serviceLog); _credentialsManager = new SQLiteCredentialStore("", _serviceLog); - + _configStore = new SQLiteConfigurationStore("", _serviceLog); _accessControl = new AccessControl(_serviceLog, _configStore); } @@ -425,10 +425,12 @@ public void ReportProgress(IProgress progress, RequestProg progress.Report(state); } - // report request state to status hub clients + // report request state to status hub clients and optionally mgmt hub _statusReporting?.ReportRequestProgress(state); + ReportRequestProgressToMgmtHub(state); + if (state.ManagedCertificate != null && logThisEvent) { if (state.CurrentState == RequestState.Error) diff --git a/src/Certify.Models/Hub/ManagementHubMessages.cs b/src/Certify.Models/Hub/ManagementHubMessages.cs index f6cebc7d7..c5aa01ab3 100644 --- a/src/Certify.Models/Hub/ManagementHubMessages.cs +++ b/src/Certify.Models/Hub/ManagementHubMessages.cs @@ -7,6 +7,7 @@ public class ManagementHubMessages { public const string SendCommandRequest = "SendCommandRequest"; public const string ReceiveCommandResult = "ReceiveCommandResult"; + public const string Notification = "Notification"; public const string GetCommandResult = "GetCommandResult"; } @@ -20,7 +21,7 @@ public class ManagementHubCommands public const string GetManagedItemRenewalPreview = "GetManagedItemRenewalPreview"; public const string UpdateManagedItem = "UpdateManagedItem"; - public const string RemoveDeleteManagedItem = "RemoveManagedItem"; + public const string RemoveManagedItem = "RemoveManagedItem"; public const string TestManagedItemConfiguration = "TestManagedItemConfiguration"; public const string PerformManagedItemRequest = "PerformManagedItemRequest"; @@ -43,6 +44,13 @@ public class ManagementHubCommands public const string ExecuteDeploymentTask = "ExecuteDeploymentTask"; public const string Reconnect = "Reconnect"; + + /// + /// Notification messages are used to send topic specific info ad-hoc back to the mgmt hub + /// + public const string NotificationRemovedManagedItem = "NotificationRemovedManagedItem"; + public const string NotificationUpdatedManagedItem = "NotificationUpdatedManagedItem"; + public const string NotificationManagedItemRequestProgress = "NotificationManagedItemRequestProgress"; } /// diff --git a/src/Certify.Server/Certify.Server.Api.Public/SignalR/ManagementHub/InstanceManagementHub.cs b/src/Certify.Server/Certify.Server.Api.Public/SignalR/ManagementHub/InstanceManagementHub.cs index 8b8f47e6c..5b1016b58 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/SignalR/ManagementHub/InstanceManagementHub.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/SignalR/ManagementHub/InstanceManagementHub.cs @@ -39,16 +39,18 @@ public class InstanceManagementHub : Hub, IInstanceManag { private IInstanceManagementStateProvider _stateProvider; private ILogger _logger; + private IHubContext _uiStatusHub; /// /// Set up instance management hub /// /// /// - public InstanceManagementHub(IInstanceManagementStateProvider stateProvider, ILogger logger) + public InstanceManagementHub(IInstanceManagementStateProvider stateProvider, ILogger logger, IHubContext uiStatusHub) { _stateProvider = stateProvider; _logger = logger; + _uiStatusHub = uiStatusHub; } /// @@ -170,7 +172,7 @@ public Task ReceiveCommandResult(InstanceCommandResult result) if (!string.IsNullOrWhiteSpace(instanceId)) { // action this message from this instance - _logger?.LogInformation("Received instance command result {result}", result); + _logger?.LogInformation("Received instance command result {result}", result.CommandType); if (cmd.CommandType == ManagementHubCommands.GetManagedItems) { @@ -189,12 +191,32 @@ public Task ReceiveCommandResult(InstanceCommandResult result) else { // store for something else to consume - _stateProvider.AddAwaitedCommandResult(result); + if (result.IsCommandResponse) + { + _stateProvider.AddAwaitedCommandResult(result); + } + else + { + // item was not requested, queue for processing + if (result.CommandType == ManagementHubCommands.NotificationUpdatedManagedItem) + { + _uiStatusHub.Clients.All.SendAsync(Providers.StatusHubMessages.SendManagedCertificateUpdateMsg, System.Text.Json.JsonSerializer.Deserialize(result.Value)); + } + else if (result.CommandType == ManagementHubCommands.NotificationManagedItemRequestProgress) + { + _uiStatusHub.Clients.All.SendAsync(Providers.StatusHubMessages.SendProgressStateMsg, System.Text.Json.JsonSerializer.Deserialize(result.Value)); + } + else if (result.CommandType == ManagementHubCommands.NotificationRemovedManagedItem) + { + // deleted :TODO + _uiStatusHub.Clients.All.SendAsync(Providers.StatusHubMessages.SendMsg, $"Deleted item {result.Value}"); + } + } } } else { - _logger?.LogError("Received instance command result for an unknown instance {result}", result); + _logger?.LogError("Received instance command result for an unknown instance {result}", result.CommandType); } } } diff --git a/src/Certify.Server/Certify.Server.Api.Public/SignalR/ManagementHub/InstanceManagementStateProvider.cs b/src/Certify.Server/Certify.Server.Api.Public/SignalR/ManagementHub/InstanceManagementStateProvider.cs index a859aa7ef..bb3c829fe 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/SignalR/ManagementHub/InstanceManagementStateProvider.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/SignalR/ManagementHub/InstanceManagementStateProvider.cs @@ -189,19 +189,10 @@ public ConcurrentDictionary GetManagedInstanceStatusSumma public void UpdateCachedManagedInstanceItem(string instanceId, ManagedCertificate managedCertificate) { _managedInstanceItems.TryGetValue(instanceId, out var instance); - - if (instance != null) + if (instance?.Items != null) { - foreach (var item in instance.Items) - { - if (item.Id == managedCertificate.Id) - { - instance.Items.Remove(item); - managedCertificate.InstanceId = instanceId; - instance.Items.Add(managedCertificate); - return; - } - } + instance.Items.RemoveAll(r => r.Id == managedCertificate.Id); + instance.Items.Add(managedCertificate); } } @@ -219,16 +210,9 @@ public void DeleteCachedManagedInstanceItem(string instanceId, string managedCer { _managedInstanceItems.TryGetValue(instanceId, out var instance); - if (instance != null) + if (instance?.Items != null) { - foreach (var item in instance.Items) - { - if (item.Id == managedCertificateId) - { - instance.Items.Remove(item); - return; - } - } + instance.Items.RemoveAll(r => r.Id == managedCertificateId); } } } diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/ManagedCertificateController.cs b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/ManagedCertificateController.cs index 16ca6edcd..05dd3a40a 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/ManagedCertificateController.cs +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/ManagedCertificateController.cs @@ -1,4 +1,4 @@ -using Certify.Config; +using Certify.Config; using Certify.Models.Hub; using Certify.Management; using Certify.Models; From c06f804441e96e17a3b04b49fa16b0496dd49c30 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Tue, 19 Nov 2024 19:28:34 +0800 Subject: [PATCH 278/328] Fix delete managed item per instance --- .../CertifyManager.ManagedCertificates.cs | 6 +++++- .../Management/CertifyManager/ICertifyManager.cs | 2 +- .../Certify.API.Public.cs | 6 +++--- .../Services/ManagementAPI.cs | 16 +++++----------- src/Certify.SourceGenerators/ApiMethods.cs | 4 ++-- 5 files changed, 16 insertions(+), 18 deletions(-) diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagedCertificates.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagedCertificates.cs index 0eec7b296..62197fb46 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagedCertificates.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagedCertificates.cs @@ -9,6 +9,7 @@ using Certify.Models.Providers; using Certify.Models.Reporting; using Certify.Models.Shared; +using Certify.Models.Config; namespace Certify.Management { @@ -337,7 +338,7 @@ private async Task SendQueuedStatusReports() /// /// /// - public async Task DeleteManagedCertificate(string id) + public async Task DeleteManagedCertificate(string id) { if (!string.IsNullOrEmpty(id)) { @@ -345,8 +346,11 @@ public async Task DeleteManagedCertificate(string id) if (item != null) { await _itemManager.Delete(item); + return new ActionResult { IsSuccess = true, Message = "Deleted" }; } } + + return new ActionResult { IsSuccess = false, Message = "Delete failed." }; } /// diff --git a/src/Certify.Core/Management/CertifyManager/ICertifyManager.cs b/src/Certify.Core/Management/CertifyManager/ICertifyManager.cs index 1db35f326..730c6b605 100644 --- a/src/Certify.Core/Management/CertifyManager/ICertifyManager.cs +++ b/src/Certify.Core/Management/CertifyManager/ICertifyManager.cs @@ -33,7 +33,7 @@ public interface ICertifyManager Task UpdateManagedCertificate(ManagedCertificate site); - Task DeleteManagedCertificate(string id); + Task DeleteManagedCertificate(string id); Task PerformExport(ExportRequest exportRequest); Task> PerformImport(ImportRequest importRequest); diff --git a/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs b/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs index e0568ed97..de0c5abd1 100644 --- a/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs +++ b/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs @@ -2064,7 +2064,7 @@ public virtual async System.Threading.Tasks.Task PerformRene /// /// OK /// A server side error occurred. - public virtual System.Threading.Tasks.Task RemoveManagedCertificateAsync(string instanceId, string managedCertId) + public virtual System.Threading.Tasks.Task RemoveManagedCertificateAsync(string instanceId, string managedCertId) { return RemoveManagedCertificateAsync(instanceId, managedCertId, System.Threading.CancellationToken.None); } @@ -2075,7 +2075,7 @@ public virtual System.Threading.Tasks.Task RemoveManagedCertificateAsync(s /// /// OK /// A server side error occurred. - public virtual async System.Threading.Tasks.Task RemoveManagedCertificateAsync(string instanceId, string managedCertId, System.Threading.CancellationToken cancellationToken) + public virtual async System.Threading.Tasks.Task RemoveManagedCertificateAsync(string instanceId, string managedCertId, System.Threading.CancellationToken cancellationToken) { if (instanceId == null) throw new System.ArgumentNullException("instanceId"); @@ -2125,7 +2125,7 @@ public virtual async System.Threading.Tasks.Task RemoveManagedCertificateA var status_ = (int)response_.StatusCode; if (status_ == 200) { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); diff --git a/src/Certify.Server/Certify.Server.Api.Public/Services/ManagementAPI.cs b/src/Certify.Server/Certify.Server.Api.Public/Services/ManagementAPI.cs index cd9b60a34..3654ca6b8 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Services/ManagementAPI.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Services/ManagementAPI.cs @@ -130,7 +130,7 @@ private async Task SendCommandWithNoResult(string instanceId, InstanceCommandReq /// /// /// - public async Task RemoveManagedCertificate(string instanceId, string managedCertId, AuthContext authContext) + public async Task RemoveManagedCertificate(string instanceId, string managedCertId, AuthContext authContext) { // delete managed cert via management hub @@ -139,20 +139,14 @@ public async Task RemoveManagedCertificate(string instanceId, string manag new("managedCertId",managedCertId) }; - var deletedOK = await PerformInstanceCommandTaskWithResult(instanceId, args, ManagementHubCommands.RemoveDeleteManagedItem); + var result = await PerformInstanceCommandTaskWithResult(instanceId, args, ManagementHubCommands.RemoveManagedItem); - if (deletedOK) + if (result.IsSuccess) { - try - { - _mgmtStateProvider.DeleteCachedManagedInstanceItem(instanceId, managedCertId); - } - catch - { - } + _mgmtStateProvider.DeleteCachedManagedInstanceItem(instanceId, managedCertId); } - return deletedOK; + return result; } public async Task GetManagedCertificateSummary(AuthContext? currentAuthContext) diff --git a/src/Certify.SourceGenerators/ApiMethods.cs b/src/Certify.SourceGenerators/ApiMethods.cs index 27f5988c0..bfe920bd9 100644 --- a/src/Certify.SourceGenerators/ApiMethods.cs +++ b/src/Certify.SourceGenerators/ApiMethods.cs @@ -176,7 +176,7 @@ public static List GetApiDefinitions() Comment = "Delete a managed challenge (DNS challenge delegation etc)", PublicAPIController = "ManagedChallenge", PublicAPIRoute = "remove", - ServiceAPIRoute = "managedchallenge", + ServiceAPIRoute = "managedchallenge/{id}", ReturnType = "Models.Config.ActionResult", Params = new Dictionary{ { "id", "string" } @@ -356,7 +356,7 @@ public static List GetApiDefinitions() UseManagementAPI = true, PublicAPIController = "Certificate", PublicAPIRoute = "{instanceId}/settings/{managedCertId}", - ReturnType = "bool", + ReturnType = "Models.Config.ActionResult", Params =new Dictionary{ { "instanceId", "string" },{ "managedCertId", "string" } } }, // TODO From f855dc530eb530cbb9f23dc3279f65052ae512ed Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Tue, 19 Nov 2024 19:28:55 +0800 Subject: [PATCH 279/328] Implement non-windows data protection --- .../Management/CredentialsUtil.cs | 31 +++++++++++++------ 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/src/Certify.Shared/Management/CredentialsUtil.cs b/src/Certify.Shared/Management/CredentialsUtil.cs index 2788419ae..032853126 100644 --- a/src/Certify.Shared/Management/CredentialsUtil.cs +++ b/src/Certify.Shared/Management/CredentialsUtil.cs @@ -1,11 +1,14 @@ using System; using System.Diagnostics; +using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; +using Certify.Models; using Certify.Providers; +using Microsoft.AspNetCore.DataProtection; namespace Certify.Management { @@ -52,6 +55,7 @@ public static string Protect( if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { + // protect using DAPI if (scope == null) { scope = DataProtectionScope.CurrentUser; @@ -66,13 +70,12 @@ public static string Protect( } else { -#if RELEASE - Trace.Assert(true, "Using dummy encryption, not suitable for production use."); -#endif - Trace.WriteLine("Using dummy encryption, not suitable for production use."); + // protect using platform data protection provider - // TODO: dummy implementation, require alternative implementation for non-windows - return Convert.ToBase64String(Encoding.UTF8.GetBytes(clearText).Reverse().ToArray()); + var protector = GetDataProtector(); + var clearBytes = Encoding.UTF8.GetBytes(clearText); + var protectedBytes = protector.Protect(clearBytes); + return Convert.ToBase64String(protectedBytes); } } @@ -111,11 +114,19 @@ public static string Unprotect( } else { - Debug.WriteLine("Using dummy encryption, not suitable for production use."); - // TODO: dummy implementation, implement alternative implementation for non-windows - var bytes = Convert.FromBase64String(encryptedText); - return Encoding.UTF8.GetString(bytes.Reverse().ToArray()); + // protect using platform data protection provider + var protector = GetDataProtector(); + var encryptedBytes = Convert.FromBase64String(encryptedText); + var clearBytes = protector.Unprotect(encryptedBytes); + return Encoding.UTF8.GetString(clearBytes); } } + + private static IDataProtector GetDataProtector() + { + var keyDirectory = EnvironmentUtil.CreateAppDataPath("credentials"); + var dataProtectionProvider = DataProtectionProvider.Create(new DirectoryInfo(keyDirectory)); + return dataProtectionProvider.CreateProtector("StoredCredentials"); + } } } From 31bc2859fd770c9810e68c3d9d2a44a8b959fc6a Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Wed, 20 Nov 2024 12:38:30 +0800 Subject: [PATCH 280/328] Package updates --- .../DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj | 2 +- src/Certify.Service/Certify.Service.csproj | 2 +- .../Certify.Core.Tests.Integration.csproj | 3 ++- .../Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj | 3 ++- .../Certify.Service.Tests.Integration.csproj | 2 +- .../Certify.UI.Tests.Integration.csproj | 2 +- 6 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj index ced5f8d5a..a7a19502a 100644 --- a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj +++ b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/Certify.Service/Certify.Service.csproj b/src/Certify.Service/Certify.Service.csproj index 7442a1dec..d3584d24f 100644 --- a/src/Certify.Service/Certify.Service.csproj +++ b/src/Certify.Service/Certify.Service.csproj @@ -27,7 +27,7 @@ - + diff --git a/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj b/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj index 5da394ab1..e8fabc3ba 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj @@ -71,7 +71,7 @@ - + @@ -79,6 +79,7 @@ + diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj index aaddb7e9f..44d0a5831 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj @@ -108,9 +108,10 @@ - + + diff --git a/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj b/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj index 2c8b66d2f..fbb58524f 100644 --- a/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj @@ -61,7 +61,7 @@ - + diff --git a/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj b/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj index 03574aaca..04387671d 100644 --- a/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj @@ -57,7 +57,7 @@ - + From 9cfc0832880f617d9f33510a12b011a5ca076dd5 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Tue, 3 Dec 2024 08:00:38 +0800 Subject: [PATCH 281/328] Implement per instance target server/item API and mgmt hub method src gen --- .../CertifyManager.ManagementHub.cs | 39 +++- .../CertifyManager.ServerType.cs | 16 ++ .../Management/Servers/ServerProviderIIS.cs | 5 + .../Hub/ManagedCertificateSummary.cs | 3 + .../Hub/ManagementHubMessages.cs | 4 + .../Certify.API.Public.cs | 168 +++++------------- .../nswag.json | 2 +- .../Controllers/internal/HubController.cs | 34 ++-- .../Controllers/internal/TargetController.cs | 118 +----------- src/Certify.SourceGenerators/ApiMethods.cs | 42 +++++ .../Certify.SourceGenerators.csproj | 3 + .../PublicAPISourceGenerator.cs | 133 ++++++++------ 12 files changed, 262 insertions(+), 305 deletions(-) diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs index 889ff7174..faa7e22b4 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs @@ -263,16 +263,47 @@ private async Task _managementServerClient_OnGetCommandRe val = await PerformDeploymentTask(null, managedCertificateIdArg.Value, taskIdArg.Value, isPreviewOnly: false, skipDeferredTasks: false, forceTaskExecution: false); } + else if (arg.CommandType == ManagementHubCommands.GetTargetServiceTypes) + { + val = await GetTargetServiceTypes(); + } + else if (arg.CommandType == ManagementHubCommands.GetTargetServiceItems) + { + var args = JsonSerializer.Deserialize[]>(arg.Value); + var serviceTypeArg = args.FirstOrDefault(a => a.Key == "serviceType"); + + var serverType = MapStandardServerType(serviceTypeArg.Value); + + val = await GetPrimaryWebSites(serverType, ignoreStoppedSites: true); + } + else if (arg.CommandType == ManagementHubCommands.GetTargetServiceItemIdentifiers) + { + var args = JsonSerializer.Deserialize[]>(arg.Value); + var serviceTypeArg = args.FirstOrDefault(a => a.Key == "serviceType"); + var itemArg = args.FirstOrDefault(a => a.Key == "itemId"); + + var serverType = MapStandardServerType(serviceTypeArg.Value); + + val = await GetDomainOptionsFromSite(serverType, itemArg.Value); + } else if (arg.CommandType == ManagementHubCommands.Reconnect) { await _managementServerClient.Disconnect(); } - var result = new InstanceCommandResult { CommandId = arg.CommandId, Value = JsonSerializer.Serialize(val) }; - - result.ObjectValue = val; + return new InstanceCommandResult { CommandId = arg.CommandId, Value = JsonSerializer.Serialize(val), ObjectValue = val }; + } - return result; + private StandardServerTypes MapStandardServerType(string type) + { + if (StandardServerTypes.TryParse(type, out StandardServerTypes standardServerType)) + { + return standardServerType; + } + else + { + return StandardServerTypes.Other; + } } private void ReportManagedItemUpdateToMgmtHub(ManagedCertificate item) diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.ServerType.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.ServerType.cs index 76d770d05..1719b80b8 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.ServerType.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.ServerType.cs @@ -9,6 +9,22 @@ namespace Certify.Management { public partial class CertifyManager { + private async Task> GetTargetServiceTypes() + { + var list = new List(); + + // TODO: make dynamic from service + if (await IsServerTypeAvailable(StandardServerTypes.IIS)) + { + list.Add(StandardServerTypes.IIS.ToString()); + }; + + if (await IsServerTypeAvailable(StandardServerTypes.Nginx)) + { + list.Add(StandardServerTypes.Nginx.ToString()); + }; + return list; + } private ITargetWebServer GetTargetServerProvider(StandardServerTypes serverType) { diff --git a/src/Certify.Core/Management/Servers/ServerProviderIIS.cs b/src/Certify.Core/Management/Servers/ServerProviderIIS.cs index b7c45a993..bcb78fc80 100644 --- a/src/Certify.Core/Management/Servers/ServerProviderIIS.cs +++ b/src/Certify.Core/Management/Servers/ServerProviderIIS.cs @@ -54,6 +54,11 @@ public IBindingDeploymentTarget GetDeploymentTarget() public Task IsAvailable() { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return Task.FromResult(false); + } + if (!_isIISAvailable) { try diff --git a/src/Certify.Models/Hub/ManagedCertificateSummary.cs b/src/Certify.Models/Hub/ManagedCertificateSummary.cs index 1b6804447..9c5b5b3ea 100644 --- a/src/Certify.Models/Hub/ManagedCertificateSummary.cs +++ b/src/Certify.Models/Hub/ManagedCertificateSummary.cs @@ -11,6 +11,9 @@ public class ManagedCertificateSummary { public string? InstanceId { get; set; } = string.Empty; public string? InstanceTitle { get; set; } = string.Empty; + + public string? OS { get; set; } = string.Empty; + public string? ClientDetails { get; set; } = string.Empty; /// /// Id for this managed item /// diff --git a/src/Certify.Models/Hub/ManagementHubMessages.cs b/src/Certify.Models/Hub/ManagementHubMessages.cs index c5aa01ab3..14d3e6149 100644 --- a/src/Certify.Models/Hub/ManagementHubMessages.cs +++ b/src/Certify.Models/Hub/ManagementHubMessages.cs @@ -43,6 +43,10 @@ public class ManagementHubCommands public const string GetDeploymentProviders = "GetDeploymentProviders"; public const string ExecuteDeploymentTask = "ExecuteDeploymentTask"; + public const string GetTargetServiceTypes = "GetTargetServiceTypes"; + public const string GetTargetServiceItems = "GetTargetServiceItems"; + public const string GetTargetServiceItemIdentifiers = "GetTargetServiceItemIdentifiers"; + public const string Reconnect = "Reconnect"; /// diff --git a/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs b/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs index de0c5abd1..2b402d629 100644 --- a/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs +++ b/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs @@ -1,6 +1,6 @@ //---------------------- // -// Generated using the NSwag toolchain v14.1.0.0 (NJsonSchema v11.0.2.0 (Newtonsoft.Json v13.0.0.0)) (http://NSwag.org) +// Generated using the NSwag toolchain v14.2.0.0 (NJsonSchema v11.1.0.0 (Newtonsoft.Json v13.0.0.0)) (http://NSwag.org) // //---------------------- @@ -30,7 +30,7 @@ namespace Certify.API.Public { using System = global::System; - [System.CodeDom.Compiler.GeneratedCode("NSwag", "14.1.0.0 (NJsonSchema v11.0.2.0 (Newtonsoft.Json v13.0.0.0))")] + [System.CodeDom.Compiler.GeneratedCode("NSwag", "14.2.0.0 (NJsonSchema v11.1.0.0 (Newtonsoft.Json v13.0.0.0))")] public partial class Client { #pragma warning disable 8618 @@ -4475,25 +4475,25 @@ public virtual async System.Threading.Tasks.Task PerformExp } /// - /// Get list of known service items (e.g. websites etc) we may want to then check for domains etc to add to cert. May return many items (e.g. thousands of sites) + /// Get Service Types present on instance (IIS, nginx etc) [Generated by Certify.SourceGenerators] /// /// OK /// A server side error occurred. - public virtual System.Threading.Tasks.Task> GetTargetServiceItemsAsync(string serverType) + public virtual System.Threading.Tasks.Task> GetTargetServiceTypesAsync(string instanceId) { - return GetTargetServiceItemsAsync(serverType, System.Threading.CancellationToken.None); + return GetTargetServiceTypesAsync(instanceId, System.Threading.CancellationToken.None); } /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Get list of known service items (e.g. websites etc) we may want to then check for domains etc to add to cert. May return many items (e.g. thousands of sites) + /// Get Service Types present on instance (IIS, nginx etc) [Generated by Certify.SourceGenerators] /// /// OK /// A server side error occurred. - public virtual async System.Threading.Tasks.Task> GetTargetServiceItemsAsync(string serverType, System.Threading.CancellationToken cancellationToken) + public virtual async System.Threading.Tasks.Task> GetTargetServiceTypesAsync(string instanceId, System.Threading.CancellationToken cancellationToken) { - if (serverType == null) - throw new System.ArgumentNullException("serverType"); + if (instanceId == null) + throw new System.ArgumentNullException("instanceId"); var client_ = _httpClient; var disposeClient_ = false; @@ -4506,10 +4506,10 @@ public virtual async System.Threading.Tasks.Task PerformExp var urlBuilder_ = new System.Text.StringBuilder(); if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); - // Operation Path: "internal/v1/target/{serverType}/items" + // Operation Path: "internal/v1/target/{instanceId}/types" urlBuilder_.Append("internal/v1/target/"); - urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(serverType, System.Globalization.CultureInfo.InvariantCulture))); - urlBuilder_.Append("/items"); + urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(instanceId, System.Globalization.CultureInfo.InvariantCulture))); + urlBuilder_.Append("/types"); PrepareRequest(client_, request_, urlBuilder_); @@ -4536,7 +4536,7 @@ public virtual async System.Threading.Tasks.Task PerformExp var status_ = (int)response_.StatusCode; if (status_ == 200) { - var objectResponse_ = await ReadObjectResponseAsync>(response_, headers_, cancellationToken).ConfigureAwait(false); + var objectResponse_ = await ReadObjectResponseAsync>(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); @@ -4564,28 +4564,28 @@ public virtual async System.Threading.Tasks.Task PerformExp } /// - /// Return details of single target server item (e.g. 1 site) + /// Get Service items (sites) present on instance (IIS, nginx etc). [Generated by Certify.SourceGenerators] /// /// OK /// A server side error occurred. - public virtual System.Threading.Tasks.Task GetTargetServiceItemAsync(string serverType, string itemId) + public virtual System.Threading.Tasks.Task> GetTargetServiceItemsAsync(string instanceId, string serviceType) { - return GetTargetServiceItemAsync(serverType, itemId, System.Threading.CancellationToken.None); + return GetTargetServiceItemsAsync(instanceId, serviceType, System.Threading.CancellationToken.None); } /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Return details of single target server item (e.g. 1 site) + /// Get Service items (sites) present on instance (IIS, nginx etc). [Generated by Certify.SourceGenerators] /// /// OK /// A server side error occurred. - public virtual async System.Threading.Tasks.Task GetTargetServiceItemAsync(string serverType, string itemId, System.Threading.CancellationToken cancellationToken) + public virtual async System.Threading.Tasks.Task> GetTargetServiceItemsAsync(string instanceId, string serviceType, System.Threading.CancellationToken cancellationToken) { - if (serverType == null) - throw new System.ArgumentNullException("serverType"); + if (instanceId == null) + throw new System.ArgumentNullException("instanceId"); - if (itemId == null) - throw new System.ArgumentNullException("itemId"); + if (serviceType == null) + throw new System.ArgumentNullException("serviceType"); var client_ = _httpClient; var disposeClient_ = false; @@ -4598,11 +4598,12 @@ public virtual async System.Threading.Tasks.Task GetTargetServiceItemA var urlBuilder_ = new System.Text.StringBuilder(); if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); - // Operation Path: "internal/v1/target/{serverType}/item/{itemId}" + // Operation Path: "internal/v1/target/{instanceId}/{serviceType}/items" urlBuilder_.Append("internal/v1/target/"); - urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(serverType, System.Globalization.CultureInfo.InvariantCulture))); - urlBuilder_.Append("/item/"); - urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(itemId, System.Globalization.CultureInfo.InvariantCulture))); + urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(instanceId, System.Globalization.CultureInfo.InvariantCulture))); + urlBuilder_.Append('/'); + urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(serviceType, System.Globalization.CultureInfo.InvariantCulture))); + urlBuilder_.Append("/items"); PrepareRequest(client_, request_, urlBuilder_); @@ -4629,7 +4630,7 @@ public virtual async System.Threading.Tasks.Task GetTargetServiceItemA var status_ = (int)response_.StatusCode; if (status_ == 200) { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + var objectResponse_ = await ReadObjectResponseAsync>(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); @@ -4657,25 +4658,28 @@ public virtual async System.Threading.Tasks.Task GetTargetServiceItemA } /// - /// Get list of known service items (e.g. websites etc) we may want to then check for domains etc to add to cert + /// Get Service item identifiers (domains on a website etc) present on instance (IIS, nginx etc) [Generated by Certify.SourceGenerators] /// /// OK /// A server side error occurred. - public virtual System.Threading.Tasks.Task> GetTargetServiceItemIdentifiersAsync(string serverType, string itemId) + public virtual System.Threading.Tasks.Task> GetTargetServiceItemIdentifiersAsync(string instanceId, string serviceType, string itemId) { - return GetTargetServiceItemIdentifiersAsync(serverType, itemId, System.Threading.CancellationToken.None); + return GetTargetServiceItemIdentifiersAsync(instanceId, serviceType, itemId, System.Threading.CancellationToken.None); } /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Get list of known service items (e.g. websites etc) we may want to then check for domains etc to add to cert + /// Get Service item identifiers (domains on a website etc) present on instance (IIS, nginx etc) [Generated by Certify.SourceGenerators] /// /// OK /// A server side error occurred. - public virtual async System.Threading.Tasks.Task> GetTargetServiceItemIdentifiersAsync(string serverType, string itemId, System.Threading.CancellationToken cancellationToken) + public virtual async System.Threading.Tasks.Task> GetTargetServiceItemIdentifiersAsync(string instanceId, string serviceType, string itemId, System.Threading.CancellationToken cancellationToken) { - if (serverType == null) - throw new System.ArgumentNullException("serverType"); + if (instanceId == null) + throw new System.ArgumentNullException("instanceId"); + + if (serviceType == null) + throw new System.ArgumentNullException("serviceType"); if (itemId == null) throw new System.ArgumentNullException("itemId"); @@ -4691,9 +4695,11 @@ public virtual async System.Threading.Tasks.Task GetTargetServiceItemA var urlBuilder_ = new System.Text.StringBuilder(); if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); - // Operation Path: "internal/v1/target/{serverType}/item/{itemId}/identifiers" + // Operation Path: "internal/v1/target/{instanceId}/{serviceType}/item/{itemId}/identifiers" urlBuilder_.Append("internal/v1/target/"); - urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(serverType, System.Globalization.CultureInfo.InvariantCulture))); + urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(instanceId, System.Globalization.CultureInfo.InvariantCulture))); + urlBuilder_.Append('/'); + urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(serviceType, System.Globalization.CultureInfo.InvariantCulture))); urlBuilder_.Append("/item/"); urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(itemId, System.Globalization.CultureInfo.InvariantCulture))); urlBuilder_.Append("/identifiers"); @@ -4750,90 +4756,6 @@ public virtual async System.Threading.Tasks.Task GetTargetServiceItemA } } - /// - /// Get list of target services this server supports (e.g. IIS etc) - /// - /// OK - /// A server side error occurred. - public virtual System.Threading.Tasks.Task> GetTargetServiceTypesAsync() - { - return GetTargetServiceTypesAsync(System.Threading.CancellationToken.None); - } - - /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. - /// - /// Get list of target services this server supports (e.g. IIS etc) - /// - /// OK - /// A server side error occurred. - public virtual async System.Threading.Tasks.Task> GetTargetServiceTypesAsync(System.Threading.CancellationToken cancellationToken) - { - var client_ = _httpClient; - var disposeClient_ = false; - try - { - using (var request_ = new System.Net.Http.HttpRequestMessage()) - { - request_.Method = new System.Net.Http.HttpMethod("GET"); - request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); - - var urlBuilder_ = new System.Text.StringBuilder(); - if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); - // Operation Path: "internal/v1/target/services" - urlBuilder_.Append("internal/v1/target/services"); - - PrepareRequest(client_, request_, urlBuilder_); - - var url_ = urlBuilder_.ToString(); - request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); - - PrepareRequest(client_, request_, url_); - - var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); - var disposeResponse_ = true; - try - { - var headers_ = new System.Collections.Generic.Dictionary>(); - foreach (var item_ in response_.Headers) - headers_[item_.Key] = item_.Value; - if (response_.Content != null && response_.Content.Headers != null) - { - foreach (var item_ in response_.Content.Headers) - headers_[item_.Key] = item_.Value; - } - - ProcessResponse(client_, response_); - - var status_ = (int)response_.StatusCode; - if (status_ == 200) - { - var objectResponse_ = await ReadObjectResponseAsync>(response_, headers_, cancellationToken).ConfigureAwait(false); - if (objectResponse_.Object == null) - { - throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); - } - return objectResponse_.Object; - } - else - { - var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); - throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); - } - } - finally - { - if (disposeResponse_) - response_.Dispose(); - } - } - } - finally - { - if (disposeClient_) - client_.Dispose(); - } - } - /// /// get current challenge info for a given type/key /// @@ -5041,7 +4963,7 @@ private string ConvertToString(object value, System.Globalization.CultureInfo cu - [System.CodeDom.Compiler.GeneratedCode("NSwag", "14.1.0.0 (NJsonSchema v11.0.2.0 (Newtonsoft.Json v13.0.0.0))")] + [System.CodeDom.Compiler.GeneratedCode("NSwag", "14.2.0.0 (NJsonSchema v11.1.0.0 (Newtonsoft.Json v13.0.0.0))")] public partial class FileResponse : System.IDisposable { private System.IDisposable _client; @@ -5078,7 +5000,7 @@ public void Dispose() } - [System.CodeDom.Compiler.GeneratedCode("NSwag", "14.1.0.0 (NJsonSchema v11.0.2.0 (Newtonsoft.Json v13.0.0.0))")] + [System.CodeDom.Compiler.GeneratedCode("NSwag", "14.2.0.0 (NJsonSchema v11.1.0.0 (Newtonsoft.Json v13.0.0.0))")] public partial class ApiException : System.Exception { public int StatusCode { get; private set; } @@ -5101,7 +5023,7 @@ public override string ToString() } } - [System.CodeDom.Compiler.GeneratedCode("NSwag", "14.1.0.0 (NJsonSchema v11.0.2.0 (Newtonsoft.Json v13.0.0.0))")] + [System.CodeDom.Compiler.GeneratedCode("NSwag", "14.2.0.0 (NJsonSchema v11.1.0.0 (Newtonsoft.Json v13.0.0.0))")] public partial class ApiException : ApiException { public TResult Result { get; private set; } diff --git a/src/Certify.Server/Certify.Server.Api.Public.Client/nswag.json b/src/Certify.Server/Certify.Server.Api.Public.Client/nswag.json index 12e2953f1..9c857d408 100644 --- a/src/Certify.Server/Certify.Server.Api.Public.Client/nswag.json +++ b/src/Certify.Server/Certify.Server.Api.Public.Client/nswag.json @@ -1,5 +1,5 @@ { - "runtime": "Net80", + "runtime": "Net90", "defaultVariables": null, "documentGenerator": { "fromDocument": { diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/HubController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/HubController.cs index b969a09e3..a3cdefc3c 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/HubController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/HubController.cs @@ -64,20 +64,28 @@ public async Task GetHubManagedItems(string? instanceId, string? list.AddRange( remote.Items .Where(i => string.IsNullOrWhiteSpace(keyword) || (!string.IsNullOrWhiteSpace(keyword) && i.Name?.Contains(keyword) == true)) - .Select(i => new ManagedCertificateSummary + .Select(i => { - InstanceId = remote.InstanceId, - InstanceTitle = instances.FirstOrDefault(i => i.InstanceId == remote.InstanceId)?.Title, - Id = i.Id ?? "", - Title = $"{i.Name}" ?? "", - PrimaryIdentifier = i.GetCertificateIdentifiers().FirstOrDefault(p => p.Value == i.RequestConfig.PrimaryDomain) ?? i.GetCertificateIdentifiers().FirstOrDefault(), - Identifiers = i.GetCertificateIdentifiers(), - DateRenewed = i.DateRenewed, - DateExpiry = i.DateExpiry, - Comments = i.Comments ?? "", - Status = i.LastRenewalStatus?.ToString() ?? "", - HasCertificate = !string.IsNullOrEmpty(i.CertificatePath) - }) + var instance = instances.FirstOrDefault(i => i.InstanceId == remote.InstanceId); + + return new ManagedCertificateSummary + { + InstanceId = remote.InstanceId, + InstanceTitle = instance?.Title, + Id = i.Id ?? "", + Title = $"{i.Name}" ?? "", + OS = instance?.OS, + ClientDetails = instance?.ClientName, + PrimaryIdentifier = i.GetCertificateIdentifiers().FirstOrDefault(p => p.Value == i.RequestConfig.PrimaryDomain) ?? i.GetCertificateIdentifiers().FirstOrDefault(), + Identifiers = i.GetCertificateIdentifiers(), + DateRenewed = i.DateRenewed, + DateExpiry = i.DateExpiry, + Comments = i.Comments ?? "", + Status = i.LastRenewalStatus?.ToString() ?? "", + HasCertificate = !string.IsNullOrEmpty(i.CertificatePath) + }; + } + ) ); } } diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/TargetController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/TargetController.cs index 3f1d8148e..edfc943b8 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/TargetController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/TargetController.cs @@ -1,5 +1,6 @@ using Certify.Client; using Certify.Models; +using Certify.Server.Api.Public.Services; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -18,42 +19,18 @@ public partial class TargetController : ApiControllerBase private readonly ICertifyInternalApiClient _client; + private readonly ManagementAPI _mgmtAPI; + /// /// Constructor /// /// /// - public TargetController(ILogger logger, ICertifyInternalApiClient client) + public TargetController(ILogger logger, ICertifyInternalApiClient client, ManagementAPI mgmtAPI) { _logger = logger; _client = client; - } - - /// - /// Get list of known service items (e.g. websites etc) we may want to then check for domains etc to add to cert. May return many items (e.g. thousands of sites) - /// - /// - [HttpGet] - [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] - [Route("{serverType}/items")] - [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(List))] - - public async Task GetTargetServiceItems(string serverType) - { - var knownServerType = GetServerTypeFromString(serverType); - if (knownServerType == null) - { - return new NotFoundResult(); - } - - var targetList = new List(); - - if (await _client.IsServerAvailable((StandardServerTypes)knownServerType)) - { - targetList.AddRange(await _client.GetServerSiteList((StandardServerTypes)knownServerType)); - } - - return new OkObjectResult(targetList); + _mgmtAPI = mgmtAPI; } private static StandardServerTypes? GetServerTypeFromString(string value) @@ -67,90 +44,5 @@ public async Task GetTargetServiceItems(string serverType) return null; } } - - /// - /// Return details of single target server item (e.g. 1 site) - /// - /// - /// - /// - [HttpGet] - [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] - [Route("{serverType}/item/{itemId}")] - [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(SiteInfo))] - - public async Task GetTargetServiceItem(string serverType, string itemId) - { - if (string.IsNullOrEmpty(itemId)) - { - return new BadRequestResult(); - } - - var knownServerType = GetServerTypeFromString(serverType); - if (knownServerType == null) - { - return new NotFoundResult(); - } - - var results = await _client.GetServerSiteList((StandardServerTypes)knownServerType, itemId); - - if (results.Count == 0) - { - return new NotFoundResult(); - } - else - { - return new OkObjectResult(results.First()); - } - } - - /// - /// Get list of known service items (e.g. websites etc) we may want to then check for domains etc to add to cert - /// - /// - [HttpGet] - [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] - [Route("{serverType}/item/{itemId}/identifiers")] - [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(List))] - - public async Task GetTargetServiceItemIdentifiers(string serverType, string itemId) - { - var targetList = new List(); - - var knownServerType = GetServerTypeFromString(serverType); - if (knownServerType == null) - { - return new NotFoundResult(); - } - - targetList.AddRange(await _client.GetServerSiteDomains((StandardServerTypes)knownServerType, itemId)); - - return new OkObjectResult(targetList); - - } - /// - /// Get list of target services this server supports (e.g. IIS etc) - /// - /// - [HttpGet] - [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] - [Route("services")] - [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(string[]))] - public async Task GetTargetServiceTypes() - { - var list = new List(); - - // TODO: make dynamic from service - if (await _client.IsServerAvailable(StandardServerTypes.IIS)) - { - list.Add(StandardServerTypes.IIS.ToString()); - }; - - if (await _client.IsServerAvailable(StandardServerTypes.Nginx)) - { - list.Add(StandardServerTypes.Nginx.ToString()); - }; - return new OkObjectResult(list); - } } } diff --git a/src/Certify.SourceGenerators/ApiMethods.cs b/src/Certify.SourceGenerators/ApiMethods.cs index bfe920bd9..6fac3c8cc 100644 --- a/src/Certify.SourceGenerators/ApiMethods.cs +++ b/src/Certify.SourceGenerators/ApiMethods.cs @@ -309,6 +309,48 @@ public static List GetApiDefinitions() { "instanceId", "string" } } }, + new GeneratedAPI { + OperationName = "GetTargetServiceTypes", + OperationMethod = HttpGet, + Comment = "Get Service Types present on instance (IIS, nginx etc)", + UseManagementAPI = true, + ManagementHubCommandType = Models.Hub.ManagementHubCommands.GetTargetServiceTypes, + PublicAPIController = "Target", + PublicAPIRoute = "{instanceId}/types", + ReturnType = "ICollection", + Params =new Dictionary{ + { "instanceId", "string" } + } + }, + new GeneratedAPI { + OperationName = "GetTargetServiceItems", + OperationMethod = HttpGet, + Comment = "Get Service items (sites) present on instance (IIS, nginx etc).", + UseManagementAPI = true, + ManagementHubCommandType = Models.Hub.ManagementHubCommands.GetTargetServiceItems, + PublicAPIController = "Target", + PublicAPIRoute = "{instanceId}/{serviceType}/items", + ReturnType = "ICollection", + Params =new Dictionary{ + { "instanceId", "string" }, + { "serviceType", "string" } + } + }, + new GeneratedAPI { + OperationName = "GetTargetServiceItemIdentifiers", + OperationMethod = HttpGet, + Comment = "Get Service item identifiers (domains on a website etc) present on instance (IIS, nginx etc)", + UseManagementAPI = true, + ManagementHubCommandType = Models.Hub.ManagementHubCommands.GetTargetServiceItemIdentifiers, + PublicAPIController = "Target", + PublicAPIRoute = "{instanceId}/{serviceType}/item/{itemId}/identifiers", + ReturnType = "ICollection", + Params =new Dictionary{ + { "instanceId", "string" }, + { "serviceType", "string" }, + { "itemId", "string" } + } + }, new GeneratedAPI { OperationName = "GetChallengeProviders", OperationMethod = HttpGet, diff --git a/src/Certify.SourceGenerators/Certify.SourceGenerators.csproj b/src/Certify.SourceGenerators/Certify.SourceGenerators.csproj index 6c8720d2d..c8eaa676e 100644 --- a/src/Certify.SourceGenerators/Certify.SourceGenerators.csproj +++ b/src/Certify.SourceGenerators/Certify.SourceGenerators.csproj @@ -17,4 +17,7 @@ + + + diff --git a/src/Certify.SourceGenerators/PublicAPISourceGenerator.cs b/src/Certify.SourceGenerators/PublicAPISourceGenerator.cs index f330c2308..59f2ea499 100644 --- a/src/Certify.SourceGenerators/PublicAPISourceGenerator.cs +++ b/src/Certify.SourceGenerators/PublicAPISourceGenerator.cs @@ -2,6 +2,7 @@ using System.Diagnostics; using System.Linq; using System.Text; +using System.Threading.Tasks; using Certify.SourceGenerators; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Text; @@ -17,6 +18,7 @@ public class GeneratedAPI public string PublicAPIRoute { get; set; } = string.Empty; public bool UseManagementAPI { get; set; } = false; + public string ManagementHubCommandType { get; set; } = string.Empty; public string ServiceAPIRoute { get; set; } = string.Empty; public string ReturnType { get; set; } = string.Empty; public Dictionary Params { get; set; } = new Dictionary(); @@ -42,9 +44,9 @@ public void Execute(GeneratorExecutionContext context) var apiParamCall = paramSet.Any() ? string.Join(", ", paramSet.Select(p => $"{p.Key}")) : ""; var apiParamCallWithoutAuthContext = config.Params.Any() ? string.Join(", ", config.Params.Select(p => $"{p.Key}")) : ""; - if (context.Compilation.AssemblyName.EndsWith("Api.Public") && config.PublicAPIController!=null) + if (context.Compilation.AssemblyName.EndsWith("Api.Public") && config.PublicAPIController != null) { - ImplementPublicAPI(context, config, apiParamDeclWithoutAuthContext, apiParamCall); + ImplementPublicAPI(context, config, apiParamDeclWithoutAuthContext, apiParamDecl, apiParamCall); } if (context.Compilation.AssemblyName.EndsWith("Certify.UI.Blazor")) @@ -63,39 +65,40 @@ public void Execute(GeneratorExecutionContext context) private static void ImplementAppModel(GeneratorExecutionContext context, GeneratedAPI config, string apiParamDeclWithoutAuthContext, string apiParamCallWithoutAuthContext) { context.AddSource($"AppModel.{config.OperationName}.g.cs", SourceText.From($@" -using System.Collections.Generic; -using System.Threading.Tasks; -using Certify.Models; -using Certify.Models.Providers; -using Certify.Models.Hub; - - namespace Certify.UI.Client.Core - {{ - public partial class AppModel - {{ - public async Task<{config.ReturnType}> {config.OperationName}({apiParamDeclWithoutAuthContext}) - {{ - return await _api.{config.OperationName}Async({apiParamCallWithoutAuthContext}); - }} - }} - }}", Encoding.UTF8)); + using System.Collections.Generic; + using System.Threading.Tasks; + using Certify.Models; + using Certify.Models.Providers; + using Certify.Models.Hub; + + namespace Certify.UI.Client.Core + {{ + public partial class AppModel + {{ + public async Task<{config.ReturnType}> {config.OperationName}({apiParamDeclWithoutAuthContext}) + {{ + return await _api.{config.OperationName}Async({apiParamCallWithoutAuthContext}); + }} + }} + }} + ", Encoding.UTF8)); } - private static void ImplementPublicAPI(GeneratorExecutionContext context, GeneratedAPI config, string apiParamDeclWithoutAuthContext, string apiParamCall) + private static void ImplementPublicAPI(GeneratorExecutionContext context, GeneratedAPI config, string apiParamDeclWithoutAuthContext, string apiParamDecl, string apiParamCall) { context.AddSource($"{config.PublicAPIController}Controller.{config.OperationName}.g.cs", SourceText.From($@" -using Certify.Client; -using Certify.Server.Api.Public.Controllers; -using Microsoft.AspNetCore.Authentication.JwtBearer; -using Microsoft.AspNetCore.Authorization; -using System.Collections.Generic; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; -using Certify.Models; -using Certify.Models.Hub; + using Certify.Client; + using Certify.Server.Api.Public.Controllers; + using Microsoft.AspNetCore.Authentication.JwtBearer; + using Microsoft.AspNetCore.Authorization; + using System.Collections.Generic; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Http; + using Microsoft.AspNetCore.Mvc; + using Microsoft.Extensions.Logging; + using Certify.Models; + using Certify.Models.Hub; namespace Certify.Server.Api.Public.Controllers @@ -116,25 +119,61 @@ public partial class {config.PublicAPIController}Controller return new OkObjectResult(result); }} }} - }}", Encoding.UTF8)); + }} + ", Encoding.UTF8)); + + // Management API service + + if (!string.IsNullOrEmpty(config.ManagementHubCommandType)) + { + var src = $@" + + using Certify.Client; + using Certify.Models.Hub; + using Certify.Models; + using Certify.Models.Config; + using Certify.Models.Providers; + using Certify.Models.Reporting; + using Microsoft.AspNetCore.SignalR; + namespace Certify.Server.Api.Public.Services + {{ + public partial class ManagementAPI + {{ + /// + /// {config.Comment} [Generated by Certify.SourceGenerators] + /// + /// + internal async Task<{config.ReturnType}> {config.OperationName}({apiParamDecl}) + {{ + var args = new KeyValuePair[] {{ + {string.Join(",", config.Params.Select(s => $"new (\"{s.Key}\", {s.Key})").ToArray())} + }}; + + return await PerformInstanceCommandTaskWithResult<{config.ReturnType}>(instanceId, args, ""{config.ManagementHubCommandType}"") ?? []; + }} + }} + }} + "; + context.AddSource($"ManagementAPI.{config.OperationName}.g.cs", SourceText.From(src, Encoding.UTF8)); + } } private static void ImplementInternalAPIClient(GeneratorExecutionContext context, GeneratedAPI config, string apiParamDecl, string apiParamCall) { var template = @" -using Certify.Models; -using Certify.Models.Config.Migration; -using Certify.Models.Providers; -using Certify.Models.Hub; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Certify.Client -{ - MethodTemplate -} -"; + using Certify.Models; + using Certify.Models.Config.Migration; + using Certify.Models.Providers; + using Certify.Models.Hub; + using System.Collections.Generic; + using System.Threading.Tasks; + + namespace Certify.Client + { + MethodTemplate + } + "; if (config.OperationMethod == "HttpGet") { @@ -192,7 +231,6 @@ public partial interface ICertifyInternalApiClient }} - public partial class CertifyApiClient {{ /// @@ -204,7 +242,6 @@ public partial class CertifyApiClient var result = await PostAsync($""{postAPIRoute}"", {postApiCall}); return JsonToObject<{config.ReturnType}>(await result.Content.ReadAsStringAsync()); }} - }} "), Encoding.UTF8)); } @@ -220,10 +257,8 @@ public partial interface ICertifyInternalApiClient /// /// Task<{config.ReturnType}> {config.OperationName}({apiParamDecl}); - }} - public partial class CertifyApiClient {{ /// @@ -232,14 +267,10 @@ public partial class CertifyApiClient /// public async Task<{config.ReturnType}> {config.OperationName}({apiParamDecl}) {{ - var route = $""{config.ServiceAPIRoute}""; - var result = await DeleteAsync(route, authContext); return JsonToObject<{config.ReturnType}>(await result.Content.ReadAsStringAsync()); - }} - }} "), Encoding.UTF8)); } From f6da2f6cc66546488653d7c2eb085cfeb13107ec Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Tue, 3 Dec 2024 08:00:58 +0800 Subject: [PATCH 282/328] Package updates --- .../Certify.Server.Core/Certify.Server.Core.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj index 8b229ef55..f73d8a91e 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj @@ -18,6 +18,7 @@ + From adcbc13cad5bad36cfe68ecac287c3e02b56eb29 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Wed, 4 Dec 2024 17:51:22 +0800 Subject: [PATCH 283/328] Package updates --- .../Certify.Aspire.ServiceDefaults.csproj | 2 +- src/Certify.Models/Certify.Models.csproj | 4 ++-- .../DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj | 2 +- .../Certify.Server.Api.Public.csproj | 2 +- .../Certify.Server.Core/Certify.Server.Core.csproj | 2 +- src/Certify.SourceGenerators/Certify.SourceGenerators.csproj | 2 +- .../Certify.Core.Tests.Integration.csproj | 4 ++-- .../Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj | 4 ++-- .../Certify.Service.Tests.Integration.csproj | 4 ++-- .../Certify.UI.Tests.Integration.csproj | 4 ++-- src/Certify.UI.Shared/Certify.UI.Shared.csproj | 2 +- src/Certify.UI/Certify.UI.csproj | 2 +- 12 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/Certify.Aspire/Certify.Aspire.ServiceDefaults/Certify.Aspire.ServiceDefaults.csproj b/src/Certify.Aspire/Certify.Aspire.ServiceDefaults/Certify.Aspire.ServiceDefaults.csproj index dbe20fb09..d99239fdc 100644 --- a/src/Certify.Aspire/Certify.Aspire.ServiceDefaults/Certify.Aspire.ServiceDefaults.csproj +++ b/src/Certify.Aspire/Certify.Aspire.ServiceDefaults/Certify.Aspire.ServiceDefaults.csproj @@ -17,7 +17,7 @@ - + diff --git a/src/Certify.Models/Certify.Models.csproj b/src/Certify.Models/Certify.Models.csproj index 6a77d6930..1c4717acb 100644 --- a/src/Certify.Models/Certify.Models.csproj +++ b/src/Certify.Models/Certify.Models.csproj @@ -17,12 +17,12 @@ AnyCPU - + all runtime; build; native; contentfiles; analyzers - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj index a7a19502a..2e14507f1 100644 --- a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj +++ b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj index 396e92d47..02e532277 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj +++ b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj @@ -21,7 +21,7 @@ - + diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj index f73d8a91e..164cdddfa 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj @@ -13,7 +13,7 @@ - + diff --git a/src/Certify.SourceGenerators/Certify.SourceGenerators.csproj b/src/Certify.SourceGenerators/Certify.SourceGenerators.csproj index c8eaa676e..95ec81c7e 100644 --- a/src/Certify.SourceGenerators/Certify.SourceGenerators.csproj +++ b/src/Certify.SourceGenerators/Certify.SourceGenerators.csproj @@ -14,7 +14,7 @@ 1701;1702;RS1035 - + diff --git a/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj b/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj index e8fabc3ba..a006a5688 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj @@ -76,8 +76,8 @@ - - + + diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj index 44d0a5831..ea5e8a0c3 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj @@ -109,8 +109,8 @@ - - + + diff --git a/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj b/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj index fbb58524f..7bcc60646 100644 --- a/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj @@ -62,8 +62,8 @@ - - + + diff --git a/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj b/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj index 04387671d..ab142d368 100644 --- a/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj @@ -58,8 +58,8 @@ - - + + diff --git a/src/Certify.UI.Shared/Certify.UI.Shared.csproj b/src/Certify.UI.Shared/Certify.UI.Shared.csproj index 0d0a8a46f..8d602b413 100644 --- a/src/Certify.UI.Shared/Certify.UI.Shared.csproj +++ b/src/Certify.UI.Shared/Certify.UI.Shared.csproj @@ -34,7 +34,7 @@ - + NU1701 diff --git a/src/Certify.UI/Certify.UI.csproj b/src/Certify.UI/Certify.UI.csproj index ae33f1ffd..8cb760f66 100644 --- a/src/Certify.UI/Certify.UI.csproj +++ b/src/Certify.UI/Certify.UI.csproj @@ -133,7 +133,7 @@ - 6.8.2 + 6.9.1 runtime; build; native; contentfiles; analyzers all From 9e7fd3edcba0effc81df6009ce7f2ce9e9cf1bdd Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Mon, 9 Dec 2024 18:46:56 +0800 Subject: [PATCH 284/328] Package updates --- .../Certify.Aspire.ServiceDefaults.csproj | 4 ++-- src/Certify.Client/Certify.Client.csproj | 2 +- src/Certify.Models/Certify.Models.csproj | 2 +- src/Certify.Shared/Certify.Shared.Core.csproj | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Certify.Aspire/Certify.Aspire.ServiceDefaults/Certify.Aspire.ServiceDefaults.csproj b/src/Certify.Aspire/Certify.Aspire.ServiceDefaults/Certify.Aspire.ServiceDefaults.csproj index d99239fdc..d99a24208 100644 --- a/src/Certify.Aspire/Certify.Aspire.ServiceDefaults/Certify.Aspire.ServiceDefaults.csproj +++ b/src/Certify.Aspire/Certify.Aspire.ServiceDefaults/Certify.Aspire.ServiceDefaults.csproj @@ -15,10 +15,10 @@ - + - + diff --git a/src/Certify.Client/Certify.Client.csproj b/src/Certify.Client/Certify.Client.csproj index 1f44391a0..ca96e91dd 100644 --- a/src/Certify.Client/Certify.Client.csproj +++ b/src/Certify.Client/Certify.Client.csproj @@ -18,7 +18,7 @@ - + diff --git a/src/Certify.Models/Certify.Models.csproj b/src/Certify.Models/Certify.Models.csproj index 1c4717acb..b789ff0eb 100644 --- a/src/Certify.Models/Certify.Models.csproj +++ b/src/Certify.Models/Certify.Models.csproj @@ -21,7 +21,7 @@ all runtime; build; native; contentfiles; analyzers - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Certify.Shared/Certify.Shared.Core.csproj b/src/Certify.Shared/Certify.Shared.Core.csproj index 17b93737c..07dab5db4 100644 --- a/src/Certify.Shared/Certify.Shared.Core.csproj +++ b/src/Certify.Shared/Certify.Shared.Core.csproj @@ -9,7 +9,7 @@ - + From 3baa83278e4ef4827f7fe8bfb91a1d8a9a6faf2a Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Mon, 23 Dec 2024 09:40:25 +0800 Subject: [PATCH 285/328] Package updates --- .../Certify.Aspire.ServiceDefaults.csproj | 2 +- .../DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj | 2 +- .../Certify.Server.Api.Public.csproj | 2 +- .../Certify.Server.Core/Certify.Server.Core.csproj | 2 +- .../Certify.Core.Tests.Integration.csproj | 4 ++-- .../Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj | 6 +++--- .../Certify.Service.Tests.Integration.csproj | 4 ++-- .../Certify.UI.Tests.Integration.csproj | 4 ++-- 8 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Certify.Aspire/Certify.Aspire.ServiceDefaults/Certify.Aspire.ServiceDefaults.csproj b/src/Certify.Aspire/Certify.Aspire.ServiceDefaults/Certify.Aspire.ServiceDefaults.csproj index d99a24208..d97953b42 100644 --- a/src/Certify.Aspire/Certify.Aspire.ServiceDefaults/Certify.Aspire.ServiceDefaults.csproj +++ b/src/Certify.Aspire/Certify.Aspire.ServiceDefaults/Certify.Aspire.ServiceDefaults.csproj @@ -15,7 +15,7 @@ - + diff --git a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj index 2e14507f1..fea862736 100644 --- a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj +++ b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj index 02e532277..fd41d4c5f 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj +++ b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj @@ -21,7 +21,7 @@ - + diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj index 164cdddfa..4dd26dce2 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj @@ -13,7 +13,7 @@ - + diff --git a/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj b/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj index a006a5688..18d98cc87 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj @@ -76,8 +76,8 @@ - - + + diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj index ea5e8a0c3..86967504b 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj @@ -109,15 +109,15 @@ - - + + - + diff --git a/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj b/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj index 7bcc60646..2e170c298 100644 --- a/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj @@ -62,8 +62,8 @@ - - + + diff --git a/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj b/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj index ab142d368..4fc947945 100644 --- a/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj @@ -58,8 +58,8 @@ - - + + From 03448b6e09606b48d6b3372de4da5caea526ef2e Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Fri, 27 Dec 2024 14:31:33 +0800 Subject: [PATCH 286/328] Fix test - version test should be a future version --- .../Certify.Core.Tests.Unit/Tests/UpdateCheckTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/UpdateCheckTest.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/UpdateCheckTest.cs index efc82bd71..172741c2b 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/UpdateCheckTest.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/UpdateCheckTest.cs @@ -15,12 +15,12 @@ public void TestUpdateCheck() // current version is older than newer version Assert.IsTrue(result.IsNewerVersion); - result = updateChecker.CheckForUpdates("6.1.1").Result; + result = updateChecker.CheckForUpdates("10.1.1").Result; // current version is newer than update version Assert.IsFalse(result.IsNewerVersion); - result = updateChecker.CheckForUpdates("6.1.1").Result; + result = updateChecker.CheckForUpdates("10.1.1").Result; Assert.IsNotNull(result, "Update check result should not be null"); From 8507554e29cb9ab23a7e2aad9cacd4c3a54caa4d Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Tue, 31 Dec 2024 17:36:14 +0800 Subject: [PATCH 287/328] Package updates --- .../DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj | 2 +- .../Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj index fea862736..5316fee01 100644 --- a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj +++ b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj index 86967504b..b2b8e15d5 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj @@ -100,7 +100,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From 541c3cc407ad8ac253a911e1881cc99e8d83c222 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Wed, 1 Jan 2025 16:52:11 +0800 Subject: [PATCH 288/328] Access Control: Expand list of controlled actions --- src/Certify.Models/Hub/AccessControlConfig.cs | 52 ++++++++++++++++++- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/src/Certify.Models/Hub/AccessControlConfig.cs b/src/Certify.Models/Hub/AccessControlConfig.cs index 1d2f0145f..6e3dcaa99 100644 --- a/src/Certify.Models/Hub/AccessControlConfig.cs +++ b/src/Certify.Models/Hub/AccessControlConfig.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Diagnostics; namespace Certify.Models.Hub { @@ -21,6 +20,8 @@ public class StandardRoles public static Role Administrator { get; } = new Role("sysadmin", "Administrator", "Certify Server Administrator", policies: new List { StandardPolicies.ManagedItemAdmin, + StandardPolicies.CertificateAuthorityAdmin, + StandardPolicies.AcmeAccountAdmin, StandardPolicies.StoredCredentialAdmin, StandardPolicies.ManagedChallengeAdmin, StandardPolicies.AccessAdmin @@ -71,6 +72,7 @@ public class ResourceTypes public static string Certificate { get; } = "certificate"; public static string StoredCredential { get; } = "storedcredential"; public static string CertificateAuthority { get; } = "ca"; + public static string AcmeAccount { get; } = "acmeaccount"; public static string ManagedChallenge { get; } = "managedchallenge"; } @@ -91,6 +93,16 @@ public static class StandardResourceActions public const string ManagedItemTaskDelete = "manageditem_task_delete"; public const string ManagedItemLogView = "manageditem_log_view"; + public const string CertificateAuthorityAdd = "ca_add"; + public const string CertificateAuthorityUpdate = "ca_update"; + public const string CertificateAuthorityDelete = "ca_delete"; + public const string CertificateAuthorityList = "ca_list"; + + public const string AcmeAccountAdd = "acmeaccount_add"; + public const string AcmeAccountUpdate = "acmeaccount_update"; + public const string AcmeAccountDelete = "acmeaccount_delete"; + public const string AcmeAccountList = "acmeaccount_list"; + public const string StoredCredentialAdd = "storedcredential_add"; public const string StoredCredentialUpdate = "storedcredential_update"; public const string StoredCredentialDelete = "storedcredential_delete"; @@ -115,6 +127,8 @@ public class StandardPolicies public const string AccessAdmin = "access_admin"; public const string ManagedItemAdmin = "manageditem_admin"; public const string CertificateConsumer = "certificate_consumer"; + public const string CertificateAuthorityAdmin = "ca_admin"; + public const string AcmeAccountAdmin = "acmeaccount_admin"; public const string StoredCredentialAdmin = "storedcredential_admin"; public const string StoredCredentialConsumer = "storedcredential_consumer"; public const string ManagedChallengeConsumer = "managedchallenge_consumer"; @@ -142,6 +156,16 @@ public static List GetStandardResourceActions() new(StandardResourceActions.CertificateDownload, "Certificate Download", ResourceTypes.Certificate), new(StandardResourceActions.CertificateKeyDownload, "Certificate Private Key Download", ResourceTypes.Certificate), + new(StandardResourceActions.CertificateAuthorityAdd, "Add New Certificate Authority", ResourceTypes.CertificateAuthority), + new(StandardResourceActions.CertificateAuthorityUpdate, "Update Certificate Authority", ResourceTypes.CertificateAuthority), + new(StandardResourceActions.CertificateAuthorityDelete, "Delete Certificate Authority", ResourceTypes.CertificateAuthority), + new(StandardResourceActions.CertificateAuthorityList, "List Certificate Authority", ResourceTypes.CertificateAuthority), + + new(StandardResourceActions.AcmeAccountAdd, "Add New ACME Account", ResourceTypes.AcmeAccount), + new(StandardResourceActions.AcmeAccountUpdate, "Update ACME Account", ResourceTypes.AcmeAccount), + new(StandardResourceActions.AcmeAccountDelete, "Delete ACME Account", ResourceTypes.AcmeAccount), + new(StandardResourceActions.AcmeAccountList, "List ACME Accounts", ResourceTypes.AcmeAccount), + new(StandardResourceActions.StoredCredentialAdd, "Add New Stored Credential", ResourceTypes.StoredCredential), new(StandardResourceActions.StoredCredentialUpdate, "Update Stored Credential", ResourceTypes.StoredCredential), new(StandardResourceActions.StoredCredentialDelete, "Delete Stored Credential", ResourceTypes.StoredCredential), @@ -162,7 +186,8 @@ public static List GetStandardResourceActions() new(StandardResourceActions.ManagedItemDelete, "Delete Managed Items", ResourceTypes.ManagedItem), new(StandardResourceActions.ManagedItemTest, "Test Managed Item Renewal Checks", ResourceTypes.ManagedItem), - new(StandardResourceActions.ManagedItemRenew, "Request/Renew Managed Items", ResourceTypes.ManagedItem), + new(StandardResourceActions.ManagedItemRequest, "Request Managed Items", ResourceTypes.ManagedItem), + new(StandardResourceActions.ManagedItemRenew, "Renew Managed Items", ResourceTypes.ManagedItem), new(StandardResourceActions.ManagedItemTaskAdd, "Add Managed Item Tasks", ResourceTypes.ManagedItem), new(StandardResourceActions.ManagedItemTaskUpdate, "Update Managed Item Tasks", ResourceTypes.ManagedItem), @@ -190,6 +215,7 @@ public static List GetStandardPolicies() StandardResourceActions.ManagedItemUpdate, StandardResourceActions.ManagedItemDelete, StandardResourceActions.ManagedItemTest, + StandardResourceActions.ManagedItemRequest, StandardResourceActions.ManagedItemRenew, StandardResourceActions.ManagedItemTaskAdd, StandardResourceActions.ManagedItemTaskUpdate, @@ -218,6 +244,28 @@ public static List GetStandardPolicies() StandardResourceActions.CertificateKeyDownload } }, + new() { + Id=StandardPolicies.CertificateAuthorityAdmin, + Title="Certificate Authority Administration", + SecurityPermissionType= SecurityPermissionType.ALLOW, + ResourceActions= new List{ + StandardResourceActions.CertificateAuthorityAdd, + StandardResourceActions.CertificateAuthorityUpdate, + StandardResourceActions.CertificateAuthorityDelete, + StandardResourceActions.CertificateAuthorityList + } + }, + new() { + Id=StandardPolicies.AcmeAccountAdmin, + Title="ACME Account Administration", + SecurityPermissionType= SecurityPermissionType.ALLOW, + ResourceActions= new List{ + StandardResourceActions.AcmeAccountList, + StandardResourceActions.AcmeAccountAdd, + StandardResourceActions.AcmeAccountUpdate, + StandardResourceActions.AcmeAccountDelete + } + }, new() { Id=StandardPolicies.StoredCredentialAdmin, Title="Stored Credential Administration", From b2ed48a1d69a765278c3d03867a0df57fd8abf45 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Thu, 2 Jan 2025 20:41:42 +0800 Subject: [PATCH 289/328] Implement API resource action access checks --- .../Management/Access/AccessControl.cs | 2 +- src/Certify.Models/Hub/AccessControlConfig.cs | 159 +++++++++++------- .../Certify.API.Public.cs | 103 ++++++++++++ .../Controllers/internal/ApiControllerBase.cs | 6 + .../Controllers/internal/HubController.cs | 5 + .../Middleware/AuthenticationExtension.cs | 1 - .../Controllers/AccessController.cs | 12 +- src/Certify.SourceGenerators/ApiMethods.cs | 10 ++ 8 files changed, 229 insertions(+), 69 deletions(-) diff --git a/src/Certify.Core/Management/Access/AccessControl.cs b/src/Certify.Core/Management/Access/AccessControl.cs index 4f7a37e6a..09500136f 100644 --- a/src/Certify.Core/Management/Access/AccessControl.cs +++ b/src/Certify.Core/Management/Access/AccessControl.cs @@ -223,7 +223,7 @@ public async Task IsAuthorised(string contextUserId, string principleId, s { // if any of the service principles assigned roles are restricted by the type of resource type, // check for identifier matches (e.g. role assignment restricted on domains ) - if (spSpecificAssignedRoles.Any(a => a.IncludedResources.Any(r => r.ResourceType == resourceType))) + if (spSpecificAssignedRoles.Any(a => a.IncludedResources?.Any(r => r.ResourceType == resourceType) == true)) { var allIncludedResources = spSpecificAssignedRoles.SelectMany(a => a.IncludedResources).Distinct(); diff --git a/src/Certify.Models/Hub/AccessControlConfig.cs b/src/Certify.Models/Hub/AccessControlConfig.cs index 6e3dcaa99..d50645de0 100644 --- a/src/Certify.Models/Hub/AccessControlConfig.cs +++ b/src/Certify.Models/Hub/AccessControlConfig.cs @@ -19,6 +19,7 @@ public class StandardRoles { public static Role Administrator { get; } = new Role("sysadmin", "Administrator", "Certify Server Administrator", policies: new List { + StandardPolicies.ManagementHubAdmin, StandardPolicies.ManagedItemAdmin, StandardPolicies.CertificateAuthorityAdmin, StandardPolicies.AcmeAccountAdmin, @@ -29,8 +30,9 @@ public class StandardRoles public static Role CertificateManager { get; } = new Role("cert_manager", "Certificate Manager", "Can manage and administer all certificates", policies: new List { - StandardPolicies.ManagedItemAdmin, - StandardPolicies.StoredCredentialAdmin + StandardPolicies.ManagementHubReader, + StandardPolicies.ManagedItemAdmin, + StandardPolicies.StoredCredentialAdmin }); public static Role CertificateConsumer { get; } = new Role("cert_consumer", "Certificate Consumer", "User of a given certificate", policies: new List { StandardPolicies.CertificateConsumer }); @@ -74,6 +76,7 @@ public class ResourceTypes public static string CertificateAuthority { get; } = "ca"; public static string AcmeAccount { get; } = "acmeaccount"; public static string ManagedChallenge { get; } = "managedchallenge"; + public static string ManagedInstance { get; } = "managedinstance"; } public static class StandardResourceActions @@ -120,6 +123,8 @@ public static class StandardResourceActions public const string ManagedChallengeDelete = "managedchallenge_update"; public const string ManagedChallengeRequest = "managedchallenge_request"; + public const string ManagementHubInstancesList = "managementhub_instances_list"; + } public class StandardPolicies @@ -133,6 +138,8 @@ public class StandardPolicies public const string StoredCredentialConsumer = "storedcredential_consumer"; public const string ManagedChallengeConsumer = "managedchallenge_consumer"; public const string ManagedChallengeAdmin = "managedchallenge_admin"; + public const string ManagementHubAdmin = "managementhub_admin"; + public const string ManagementHubReader = "managementhub_reader"; } public static class Policies @@ -145,7 +152,7 @@ public static List GetStandardRoles() StandardRoles.CertificateManager, StandardRoles.CertificateConsumer, StandardRoles.StoredCredentialConsumer, - StandardRoles.ManagedChallengeConsumer + StandardRoles.ManagedChallengeConsumer, }; } @@ -199,6 +206,8 @@ public static List GetStandardResourceActions() new(StandardResourceActions.ManagedChallengeUpdate, "Update managed challenge", ResourceTypes.ManagedChallenge), new(StandardResourceActions.ManagedChallengeDelete, "Delete managed challenge", ResourceTypes.ManagedChallenge), new(StandardResourceActions.ManagedChallengeRequest, "Request to perform a managed challenge response", ResourceTypes.ManagedChallenge), + + new(StandardResourceActions.ManagementHubInstancesList, "List managed instances", ResourceTypes.ManagedInstance), }; } @@ -206,10 +215,10 @@ public static List GetStandardPolicies() { return new List { new() { - Id=StandardPolicies.ManagedItemAdmin, - Title="Managed Item Administration", - SecurityPermissionType= SecurityPermissionType.ALLOW, - ResourceActions= new List{ + Id = StandardPolicies.ManagedItemAdmin, + Title = "Managed Item Administration", + SecurityPermissionType = SecurityPermissionType.ALLOW, + ResourceActions = new List { StandardResourceActions.ManagedItemList, StandardResourceActions.ManagedItemAdd, StandardResourceActions.ManagedItemUpdate, @@ -224,87 +233,107 @@ public static List GetStandardPolicies() } }, new() { - Id=StandardPolicies.AccessAdmin, - Title="Access Control Administration", - SecurityPermissionType= SecurityPermissionType.ALLOW, - ResourceActions= new List{ - StandardResourceActions.SecurityPrincipleList, - StandardResourceActions.SecurityPrincipleAdd, - StandardResourceActions.SecurityPrincipleUpdate, - StandardResourceActions.SecurityPrincipleDelete, - StandardResourceActions.SecurityPrinciplePasswordUpdate + Id = StandardPolicies.AccessAdmin, + Title = "Access Control Administration", + SecurityPermissionType = SecurityPermissionType.ALLOW, + ResourceActions = new List { + StandardResourceActions.SecurityPrincipleList, + StandardResourceActions.SecurityPrincipleAdd, + StandardResourceActions.SecurityPrincipleUpdate, + StandardResourceActions.SecurityPrincipleDelete, + StandardResourceActions.SecurityPrinciplePasswordUpdate } }, new() { - Id=StandardPolicies.CertificateConsumer, - Title="Consume Certificates", - SecurityPermissionType= SecurityPermissionType.ALLOW, - ResourceActions= new List{ + Id = StandardPolicies.CertificateConsumer, + Title = "Consume Certificates", + SecurityPermissionType = SecurityPermissionType.ALLOW, + ResourceActions = new List { StandardResourceActions.CertificateDownload, StandardResourceActions.CertificateKeyDownload } }, - new() { - Id=StandardPolicies.CertificateAuthorityAdmin, - Title="Certificate Authority Administration", - SecurityPermissionType= SecurityPermissionType.ALLOW, - ResourceActions= new List{ - StandardResourceActions.CertificateAuthorityAdd, - StandardResourceActions.CertificateAuthorityUpdate, - StandardResourceActions.CertificateAuthorityDelete, - StandardResourceActions.CertificateAuthorityList - } - }, new() { - Id=StandardPolicies.AcmeAccountAdmin, - Title="ACME Account Administration", - SecurityPermissionType= SecurityPermissionType.ALLOW, - ResourceActions= new List{ + Id = StandardPolicies.CertificateAuthorityAdmin, + Title = "Certificate Authority Administration", + SecurityPermissionType = SecurityPermissionType.ALLOW, + ResourceActions = new List { + StandardResourceActions.CertificateAuthorityAdd, + StandardResourceActions.CertificateAuthorityUpdate, + StandardResourceActions.CertificateAuthorityDelete, + StandardResourceActions.CertificateAuthorityList + } + }, + new() { + Id = StandardPolicies.AcmeAccountAdmin, + Title = "ACME Account Administration", + SecurityPermissionType = SecurityPermissionType.ALLOW, + ResourceActions = new List { StandardResourceActions.AcmeAccountList, StandardResourceActions.AcmeAccountAdd, StandardResourceActions.AcmeAccountUpdate, StandardResourceActions.AcmeAccountDelete - } - }, + } + }, new() { - Id=StandardPolicies.StoredCredentialAdmin, - Title="Stored Credential Administration", - SecurityPermissionType= SecurityPermissionType.ALLOW, - ResourceActions= new List{ - StandardResourceActions.StoredCredentialList, - StandardResourceActions.StoredCredentialAdd, - StandardResourceActions.StoredCredentialUpdate, - StandardResourceActions.StoredCredentialDelete + Id = StandardPolicies.StoredCredentialAdmin, + Title = "Stored Credential Administration", + SecurityPermissionType = SecurityPermissionType.ALLOW, + ResourceActions = new List { + StandardResourceActions.StoredCredentialList, + StandardResourceActions.StoredCredentialAdd, + StandardResourceActions.StoredCredentialUpdate, + StandardResourceActions.StoredCredentialDelete } }, new() { - Id=StandardPolicies.StoredCredentialConsumer, - Title="Stored Credential Consumer", - Description="Provides access to fetch a decrypted stored credential.", - SecurityPermissionType= SecurityPermissionType.ALLOW, - IsResourceSpecific=true, - ResourceActions= new List{ - StandardResourceActions.StoredCredentialDownload + Id = StandardPolicies.StoredCredentialConsumer, + Title = "Stored Credential Consumer", + Description = "Provides access to fetch a decrypted stored credential.", + SecurityPermissionType = SecurityPermissionType.ALLOW, + IsResourceSpecific = true, + ResourceActions = new List { + StandardResourceActions.StoredCredentialDownload } }, - new() { - Id=StandardPolicies.ManagedChallengeAdmin, - Title="Managed Challenge Administration", - SecurityPermissionType= SecurityPermissionType.ALLOW, - ResourceActions= new List{ + new() { + Id = StandardPolicies.ManagedChallengeAdmin, + Title = "Managed Challenge Administration", + SecurityPermissionType = SecurityPermissionType.ALLOW, + ResourceActions = new List { StandardResourceActions.ManagedChallengeList, StandardResourceActions.ManagedChallengeUpdate, StandardResourceActions.ManagedChallengeDelete } }, - new() { - Id=StandardPolicies.ManagedChallengeConsumer, - Title="Managed Challenge Consumer", - Description="Allows consumer to request that a managed challenge be performed.", - SecurityPermissionType= SecurityPermissionType.ALLOW, - IsResourceSpecific=true, - ResourceActions= new List{ - StandardResourceActions.ManagedChallengeRequest + new() { + Id = StandardPolicies.ManagedChallengeConsumer, + Title = "Managed Challenge Consumer", + Description = "Allows consumer to request that a managed challenge be performed.", + SecurityPermissionType = SecurityPermissionType.ALLOW, + IsResourceSpecific = true, + ResourceActions = new List { + StandardResourceActions.ManagedChallengeRequest + } + }, + new() { + Id = StandardPolicies.ManagementHubAdmin, + Title = "Management Hub Admin", + Description = "Administer management hub.", + SecurityPermissionType = SecurityPermissionType.ALLOW, + IsResourceSpecific = true, + ResourceActions = new List { + StandardResourceActions.ManagementHubInstancesList + } + }, + new() { + Id = StandardPolicies.ManagementHubAdmin, + Title = "Management Hub Reader", + Description = "View management hub.", + SecurityPermissionType = SecurityPermissionType.ALLOW, + IsResourceSpecific = true, + ResourceActions = new List { + StandardResourceActions.ManagementHubInstancesList } } }; diff --git a/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs b/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs index 2b402d629..fb6b84397 100644 --- a/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs +++ b/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs @@ -78,6 +78,109 @@ public string BaseUrl partial void PrepareRequest(System.Net.Http.HttpClient client, System.Net.Http.HttpRequestMessage request, System.Text.StringBuilder urlBuilder); partial void ProcessResponse(System.Net.Http.HttpClient client, System.Net.Http.HttpResponseMessage response); + /// + /// Get list of Assigned Roles for a given security principle [Generated by Certify.SourceGenerators] + /// + /// OK + /// A server side error occurred. + public virtual System.Threading.Tasks.Task CheckSecurityPrincipleHasAccessAsync(string id, string resourceType, string resourceAction, string identifier) + { + return CheckSecurityPrincipleHasAccessAsync(id, resourceType, resourceAction, identifier, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Get list of Assigned Roles for a given security principle [Generated by Certify.SourceGenerators] + /// + /// OK + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task CheckSecurityPrincipleHasAccessAsync(string id, string resourceType, string resourceAction, string identifier, System.Threading.CancellationToken cancellationToken) + { + if (id == null) + throw new System.ArgumentNullException("id"); + + if (resourceType == null) + throw new System.ArgumentNullException("resourceType"); + + if (resourceAction == null) + throw new System.ArgumentNullException("resourceAction"); + + if (identifier == null) + throw new System.ArgumentNullException("identifier"); + + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); + // Operation Path: "internal/v1/access/securityprinciple/{id}/allowedaction/{resourceType}/{resourceAction}/{identifier}" + urlBuilder_.Append("internal/v1/access/securityprinciple/"); + urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(id, System.Globalization.CultureInfo.InvariantCulture))); + urlBuilder_.Append("/allowedaction/"); + urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(resourceType, System.Globalization.CultureInfo.InvariantCulture))); + urlBuilder_.Append('/'); + urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(resourceAction, System.Globalization.CultureInfo.InvariantCulture))); + urlBuilder_.Append('/'); + urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(identifier, System.Globalization.CultureInfo.InvariantCulture))); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + /// /// Get list of Assigned Roles for a given security principle [Generated by Certify.SourceGenerators] /// diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/ApiControllerBase.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/ApiControllerBase.cs index e6f366e25..4dddf737e 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/ApiControllerBase.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/ApiControllerBase.cs @@ -12,6 +12,12 @@ namespace Certify.Server.Api.Public.Controllers public partial class ApiControllerBase : ControllerBase { + internal async Task IsAuthorized(ICertifyInternalApiClient client, string resourceType, string resourceAction, string? identifier = null) + { + var isAuthorized = await client.CheckSecurityPrincipleHasAccess(CurrentAuthContext?.UserId, resourceType, resourceAction, identifier, CurrentAuthContext); + return isAuthorized; + } + /// /// Get the corresponding auth context to pass to the backend service /// diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/HubController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/HubController.cs index a3cdefc3c..b34e47121 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/HubController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/HubController.cs @@ -101,6 +101,11 @@ public async Task GetHubManagedItems(string? instanceId, string? [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(List))] public async Task GetHubManagedInstances() { + if (!await IsAuthorized(_client, ResourceTypes.ManagedInstance, StandardResourceActions.ManagementHubInstancesList)) + { + return Unauthorized(); + } + var managedInstances = _mgmtStateProvider.GetConnectedInstances(); return new OkObjectResult(managedInstances); } diff --git a/src/Certify.Server/Certify.Server.Api.Public/Middleware/AuthenticationExtension.cs b/src/Certify.Server/Certify.Server.Api.Public/Middleware/AuthenticationExtension.cs index 5f4a1b781..990ee518f 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Middleware/AuthenticationExtension.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Middleware/AuthenticationExtension.cs @@ -45,7 +45,6 @@ public static IServiceCollection AddTokenAuthentication(this IServiceCollection }; }); - return services; } } diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccessController.cs b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccessController.cs index d0b445726..dced47c8d 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccessController.cs +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccessController.cs @@ -1,5 +1,5 @@ -using Certify.Models.Hub; -using Certify.Management; +using Certify.Management; +using Certify.Models.Hub; using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Mvc; @@ -102,6 +102,14 @@ public async Task> GetRoles() return roles; } + [HttpGet, Route("securityprinciple/{id}/allowedaction/{resourceType}/{resourceAction}/{identifier?}")] + public async Task CheckSecurityPrincipleHasAccess(string id, string resourceType, string resourceAction, string? identifier) + { + var accessControl = await _certifyManager.GetCurrentAccessControl(); + + return await accessControl.IsAuthorised(GetContextUserId(), id, null, resourceType, actionId: resourceAction, identifier); + } + [HttpGet, Route("securityprinciple/{id}/assignedroles")] public async Task> GetSecurityPrincipleAssignedRoles(string id) { diff --git a/src/Certify.SourceGenerators/ApiMethods.cs b/src/Certify.SourceGenerators/ApiMethods.cs index 6fac3c8cc..fcafd0d65 100644 --- a/src/Certify.SourceGenerators/ApiMethods.cs +++ b/src/Certify.SourceGenerators/ApiMethods.cs @@ -33,6 +33,16 @@ public static List GetApiDefinitions() return new List { + new GeneratedAPI { + OperationName = "CheckSecurityPrincipleHasAccess", + OperationMethod = HttpGet, + Comment = "Get list of Assigned Roles for a given security principle", + PublicAPIController = "Access", + PublicAPIRoute = "securityprinciple/{id}/allowedaction/{resourceType}/{resourceAction}/{identifier}", + ServiceAPIRoute = "access/securityprinciple/{id}/allowedaction/{resourceType}/{resourceAction}/{identifier}", + ReturnType = "bool", + Params =new Dictionary{{"id","string"}, { "resourceType", "string" },{ "resourceAction", "string" }, { "identifier", "string" } } + }, new GeneratedAPI { OperationName = "GetSecurityPrincipleAssignedRoles", From 786fe974450e52ef8b736093f0c57d92677877c5 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Tue, 7 Jan 2025 14:55:02 +0800 Subject: [PATCH 290/328] Begin implementation of API permissions --- src/Certify.SourceGenerators/ApiMethods.cs | 14 ++++-- .../PublicAPISourceGenerator.cs | 46 +++++++++++++++++-- 2 files changed, 51 insertions(+), 9 deletions(-) diff --git a/src/Certify.SourceGenerators/ApiMethods.cs b/src/Certify.SourceGenerators/ApiMethods.cs index fcafd0d65..d8d8c1fba 100644 --- a/src/Certify.SourceGenerators/ApiMethods.cs +++ b/src/Certify.SourceGenerators/ApiMethods.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using SourceGenerator; @@ -162,7 +162,8 @@ public static List GetApiDefinitions() PublicAPIController = "ManagedChallenge", PublicAPIRoute = "list", ServiceAPIRoute = "managedchallenge", - ReturnType = "ICollection" + ReturnType = "ICollection", + RequiredPermissions = [new ("managedchallenge", "managedchallenge_list")] }, new GeneratedAPI { @@ -176,7 +177,8 @@ public static List GetApiDefinitions() ReturnType = "Models.Config.ActionResult", Params = new Dictionary{ { "update", "Certify.Models.Hub.ManagedChallenge" } - } + }, + RequiredPermissions = [new ("managedchallenge", "managedchallenge_update")] }, new GeneratedAPI { @@ -190,7 +192,8 @@ public static List GetApiDefinitions() ReturnType = "Models.Config.ActionResult", Params = new Dictionary{ { "id", "string" } - } + }, + RequiredPermissions = [new ("managedchallenge", "managedchallenge_delete")] }, new GeneratedAPI { @@ -202,7 +205,8 @@ public static List GetApiDefinitions() ReturnType = "Models.Config.ActionResult", Params = new Dictionary{ { "request", "Certify.Models.Hub.ManagedChallengeRequest" } - } + }, + RequiredPermissions = [new ("managedchallenge", "managedchallenge_request")] }, new GeneratedAPI { diff --git a/src/Certify.SourceGenerators/PublicAPISourceGenerator.cs b/src/Certify.SourceGenerators/PublicAPISourceGenerator.cs index 59f2ea499..1ee719ac8 100644 --- a/src/Certify.SourceGenerators/PublicAPISourceGenerator.cs +++ b/src/Certify.SourceGenerators/PublicAPISourceGenerator.cs @@ -2,7 +2,6 @@ using System.Diagnostics; using System.Linq; using System.Text; -using System.Threading.Tasks; using Certify.SourceGenerators; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Text; @@ -17,12 +16,24 @@ public class GeneratedAPI public string PublicAPIController { get; set; } = string.Empty; public string PublicAPIRoute { get; set; } = string.Empty; + public List RequiredPermissions { get; set; } = []; public bool UseManagementAPI { get; set; } = false; public string ManagementHubCommandType { get; set; } = string.Empty; public string ServiceAPIRoute { get; set; } = string.Empty; public string ReturnType { get; set; } = string.Empty; public Dictionary Params { get; set; } = new Dictionary(); } + + public class PermissionSpec + { + public string ResourceType { get; set; } + public string Action { get; set; } + public PermissionSpec(string resourceType, string action) + { + ResourceType = resourceType; + Action = action; + } + } [Generator] public class PublicAPISourceGenerator : ISourceGenerator { @@ -86,7 +97,7 @@ public partial class AppModel private static void ImplementPublicAPI(GeneratorExecutionContext context, GeneratedAPI config, string apiParamDeclWithoutAuthContext, string apiParamDecl, string apiParamCall) { - context.AddSource($"{config.PublicAPIController}Controller.{config.OperationName}.g.cs", SourceText.From($@" + var publicApiSrc = $@" using Certify.Client; using Certify.Server.Api.Public.Controllers; @@ -115,12 +126,39 @@ public partial class {config.PublicAPIController}Controller [Route(""""""{config.PublicAPIRoute}"""""")] public async Task {config.OperationName}({apiParamDeclWithoutAuthContext}) {{ + + [RequiredPermissions] + var result = await {(config.UseManagementAPI ? "_mgmtAPI" : "_client")}.{config.OperationName}({apiParamCall.Replace("authContext", "CurrentAuthContext")}); return new OkObjectResult(result); }} }} - }} - ", Encoding.UTF8)); + }}; + "; + + if (config.RequiredPermissions.Any()) + { + var fragment = ""; + foreach (var perm in config.RequiredPermissions) + { + fragment += $@" + if (!await IsAuthorized(_client, ""{perm.ResourceType}"" , ""{perm.Action}"")) + {{ + {{ + return Unauthorized(); + }} + }} + "; + } + + publicApiSrc = publicApiSrc.Replace("[RequiredPermissions]", fragment); + } + else + { + publicApiSrc = publicApiSrc.Replace("[RequiredPermissions]", ""); + } + + context.AddSource($"{config.PublicAPIController}Controller.{config.OperationName}.g.cs", SourceText.From(publicApiSrc, Encoding.UTF8)); // Management API service From 7e88853a3a4a0ae2749eb78fead73af3840f7e58 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Tue, 7 Jan 2025 14:55:17 +0800 Subject: [PATCH 291/328] Cleanup --- .../DNS/CertifyDns/DnsProviderCertifyDns.cs | 2 +- .../Certify.Server.Api.Public.csproj | 2 +- src/Certify.SourceGenerators/ApiMethods.cs | 182 ++++++++---------- 3 files changed, 87 insertions(+), 99 deletions(-) diff --git a/src/Certify.Providers/DNS/CertifyDns/DnsProviderCertifyDns.cs b/src/Certify.Providers/DNS/CertifyDns/DnsProviderCertifyDns.cs index c73211c18..030868ae6 100644 --- a/src/Certify.Providers/DNS/CertifyDns/DnsProviderCertifyDns.cs +++ b/src/Certify.Providers/DNS/CertifyDns/DnsProviderCertifyDns.cs @@ -164,7 +164,7 @@ public DnsProviderCertifyDns() : base() } else { - // registration is not a valid certify dns registration, must be annother acme-dns service} + // registration is not a valid certify dns registration, must be another acme-dns service _log?.Warning("Existing acme-dns registration found, new registration required for Certify DNS"); } } diff --git a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj index fd41d4c5f..477fb639f 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj +++ b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj @@ -28,6 +28,6 @@ - + \ No newline at end of file diff --git a/src/Certify.SourceGenerators/ApiMethods.cs b/src/Certify.SourceGenerators/ApiMethods.cs index d8d8c1fba..b20149b0c 100644 --- a/src/Certify.SourceGenerators/ApiMethods.cs +++ b/src/Certify.SourceGenerators/ApiMethods.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using SourceGenerator; @@ -33,18 +33,17 @@ public static List GetApiDefinitions() return new List { - new GeneratedAPI { + new() { OperationName = "CheckSecurityPrincipleHasAccess", OperationMethod = HttpGet, - Comment = "Get list of Assigned Roles for a given security principle", + Comment = "Check a given security principle has permissions to perform a specific action for a specific resource type", PublicAPIController = "Access", PublicAPIRoute = "securityprinciple/{id}/allowedaction/{resourceType}/{resourceAction}/{identifier}", ServiceAPIRoute = "access/securityprinciple/{id}/allowedaction/{resourceType}/{resourceAction}/{identifier}", ReturnType = "bool", Params =new Dictionary{{"id","string"}, { "resourceType", "string" },{ "resourceAction", "string" }, { "identifier", "string" } } }, - new GeneratedAPI { - + new() { OperationName = "GetSecurityPrincipleAssignedRoles", OperationMethod = HttpGet, Comment = "Get list of Assigned Roles for a given security principle", @@ -54,8 +53,7 @@ public static List GetApiDefinitions() ReturnType = "ICollection", Params =new Dictionary{{"id","string"}} }, - new GeneratedAPI { - + new() { OperationName = "GetSecurityPrincipleRoleStatus", OperationMethod = HttpGet, Comment = "Get list of Assigned Roles etc for a given security principle", @@ -65,8 +63,7 @@ public static List GetApiDefinitions() ReturnType = "RoleStatus", Params =new Dictionary{{"id","string"}} }, - new GeneratedAPI { - + new() { OperationName = "GetAccessRoles", OperationMethod = HttpGet, Comment = "Get list of available security Roles", @@ -75,7 +72,7 @@ public static List GetApiDefinitions() ServiceAPIRoute = "access/roles", ReturnType = "ICollection" }, - new GeneratedAPI { + new() { OperationName = "GetSecurityPrinciples", OperationMethod = HttpGet, @@ -85,7 +82,7 @@ public static List GetApiDefinitions() ServiceAPIRoute = "access/securityprinciples", ReturnType = "ICollection" }, - new GeneratedAPI { + new() { OperationName = "ValidateSecurityPrinciplePassword", OperationMethod = HttpPost, Comment = "Check password valid for security principle", @@ -95,7 +92,7 @@ public static List GetApiDefinitions() ReturnType = "Certify.Models.Hub.SecurityPrincipleCheckResponse", Params = new Dictionary{{"passwordCheck", "Certify.Models.Hub.SecurityPrinciplePasswordCheck" } } }, - new GeneratedAPI { + new() { OperationName = "UpdateSecurityPrinciplePassword", OperationMethod = HttpPost, @@ -106,7 +103,7 @@ public static List GetApiDefinitions() ReturnType = "Models.Config.ActionResult", Params = new Dictionary{{"passwordUpdate", "Certify.Models.Hub.SecurityPrinciplePasswordUpdate" } } }, - new GeneratedAPI { + new() { OperationName = "AddSecurityPrinciple", OperationMethod = HttpPost, @@ -117,7 +114,7 @@ public static List GetApiDefinitions() ReturnType = "Models.Config.ActionResult", Params = new Dictionary{{"principle", "Certify.Models.Hub.SecurityPrinciple" } } }, - new GeneratedAPI { + new() { OperationName = "UpdateSecurityPrinciple", OperationMethod = HttpPost, @@ -130,8 +127,7 @@ public static List GetApiDefinitions() { "principle", "Certify.Models.Hub.SecurityPrinciple" } } }, - new GeneratedAPI { - + new() { OperationName = "UpdateSecurityPrincipleAssignedRoles", OperationMethod = HttpPost, Comment = "Update assigned roles for a security principle", @@ -143,8 +139,7 @@ public static List GetApiDefinitions() { "update", "Certify.Models.Hub.SecurityPrincipleAssignedRoleUpdate" } } }, - new GeneratedAPI { - + new() { OperationName = "RemoveSecurityPrinciple", OperationMethod = HttpDelete, Comment = "Remove security principle", @@ -154,8 +149,7 @@ public static List GetApiDefinitions() ReturnType = "Models.Config.ActionResult", Params = new Dictionary{{"id","string"}} }, - new GeneratedAPI { - + new() { OperationName = "GetManagedChallenges", OperationMethod = HttpGet, Comment = "Get list of available managed challenges (DNS challenge delegation etc)", @@ -165,9 +159,7 @@ public static List GetApiDefinitions() ReturnType = "ICollection", RequiredPermissions = [new ("managedchallenge", "managedchallenge_list")] }, - - new GeneratedAPI { - + new() { OperationName = "UpdateManagedChallenge", OperationMethod = HttpPost, Comment = "Add/update a managed challenge (DNS challenge delegation etc)", @@ -180,9 +172,7 @@ public static List GetApiDefinitions() }, RequiredPermissions = [new ("managedchallenge", "managedchallenge_update")] }, - - new GeneratedAPI { - + new() { OperationName = "RemoveManagedChallenge", OperationMethod = HttpDelete, Comment = "Delete a managed challenge (DNS challenge delegation etc)", @@ -195,12 +185,11 @@ public static List GetApiDefinitions() }, RequiredPermissions = [new ("managedchallenge", "managedchallenge_delete")] }, - new GeneratedAPI { - + new() { OperationName = "PerformManagedChallenge", OperationMethod = HttpPost, Comment = "Perform a managed challenge (DNS challenge delegation etc)", - PublicAPIController=null, // skip public controller implementation + PublicAPIController = null, // skip public controller implementation ServiceAPIRoute = "managedchallenge/request", ReturnType = "Models.Config.ActionResult", Params = new Dictionary{ @@ -208,12 +197,11 @@ public static List GetApiDefinitions() }, RequiredPermissions = [new ("managedchallenge", "managedchallenge_request")] }, - new GeneratedAPI { - + new() { OperationName = "CleanupManagedChallenge", OperationMethod = HttpPost, Comment = "Perform cleanup for a previously managed challenge (DNS challenge delegation etc)", - PublicAPIController=null, // skip public controller implementation + PublicAPIController = null, // skip public controller implementation ServiceAPIRoute = "managedchallenge/cleanup", ReturnType = "Models.Config.ActionResult", Params = new Dictionary{ @@ -221,7 +209,7 @@ public static List GetApiDefinitions() } }, /* per instance API, via management hub */ - new GeneratedAPI { + new() { OperationName = "GetAcmeAccounts", OperationMethod = HttpGet, Comment = "Get All Acme Accounts", @@ -229,9 +217,9 @@ public static List GetApiDefinitions() PublicAPIController = "CertificateAuthority", PublicAPIRoute = "{instanceId}/accounts/", ReturnType = "ICollection", - Params =new Dictionary{ { "instanceId", "string" } } + Params = new Dictionary { { "instanceId", "string" } } }, - new GeneratedAPI { + new() { OperationName = "AddAcmeAccount", OperationMethod = HttpPost, Comment = "Add New Acme Account", @@ -239,9 +227,9 @@ public static List GetApiDefinitions() PublicAPIController = "CertificateAuthority", PublicAPIRoute = "{instanceId}/account/", ReturnType = "Models.Config.ActionResult", - Params =new Dictionary{ { "instanceId", "string" },{ "registration", "Certify.Models.ContactRegistration" } } + Params = new Dictionary { { "instanceId", "string" }, { "registration", "Certify.Models.ContactRegistration" } } }, - new GeneratedAPI { + new() { OperationName = "GetCertificateAuthorities", OperationMethod = HttpGet, Comment = "Get list of defined Certificate Authorities", @@ -249,9 +237,9 @@ public static List GetApiDefinitions() PublicAPIController = "CertificateAuthority", PublicAPIRoute = "{instanceId}/authority", ReturnType = "ICollection", - Params =new Dictionary{ { "instanceId", "string" } } + Params = new Dictionary { { "instanceId", "string" } } }, - new GeneratedAPI { + new() { OperationName = "UpdateCertificateAuthority", OperationMethod = HttpPost, Comment = "Add/Update Certificate Authority", @@ -259,9 +247,9 @@ public static List GetApiDefinitions() PublicAPIController = "CertificateAuthority", PublicAPIRoute = "{instanceId}/authority", ReturnType = "Models.Config.ActionResult", - Params =new Dictionary{ { "instanceId", "string" }, { "ca", "Certify.Models.CertificateAuthority" } } + Params = new Dictionary { { "instanceId", "string" }, { "ca", "Certify.Models.CertificateAuthority" } } }, - new GeneratedAPI { + new() { OperationName = "RemoveCertificateAuthority", OperationMethod = HttpDelete, Comment = "Remove Certificate Authority", @@ -269,9 +257,9 @@ public static List GetApiDefinitions() PublicAPIController = "CertificateAuthority", PublicAPIRoute = "{instanceId}/authority/{id}", ReturnType = "Models.Config.ActionResult", - Params =new Dictionary{ { "instanceId", "string" },{ "id", "string" } } + Params = new Dictionary { { "instanceId", "string" }, { "id", "string" } } }, - new GeneratedAPI { + new() { OperationName = "RemoveAcmeAccount", OperationMethod = HttpDelete, Comment = "Remove ACME Account", @@ -279,19 +267,19 @@ public static List GetApiDefinitions() PublicAPIController = "CertificateAuthority", PublicAPIRoute = "{instanceId}/accounts/{storageKey}/{deactivate}", ReturnType = "Models.Config.ActionResult", - Params =new Dictionary{ { "instanceId", "string" }, { "storageKey", "string" }, { "deactivate", "bool" } } - }, - new GeneratedAPI { - OperationName = "GetStoredCredentials", - OperationMethod = HttpGet, - Comment = "Get List of Stored Credentials", - UseManagementAPI = true, - PublicAPIController = "StoredCredential", - PublicAPIRoute = "{instanceId}", - ReturnType = "ICollection", - Params =new Dictionary{ { "instanceId", "string" } } - }, - new GeneratedAPI { + Params = new Dictionary { { "instanceId", "string" }, { "storageKey", "string" }, { "deactivate", "bool" } } + }, + new() { + OperationName = "GetStoredCredentials", + OperationMethod = HttpGet, + Comment = "Get List of Stored Credentials", + UseManagementAPI = true, + PublicAPIController = "StoredCredential", + PublicAPIRoute = "{instanceId}", + ReturnType = "ICollection", + Params = new Dictionary { { "instanceId", "string" } } + }, + new() { OperationName = "UpdateStoredCredential", OperationMethod = HttpPost, Comment = "Add/Update Stored Credential", @@ -299,9 +287,9 @@ public static List GetApiDefinitions() PublicAPIRoute = "{instanceId}", ReturnType = "Models.Config.ActionResult", UseManagementAPI = true, - Params =new Dictionary{ { "instanceId", "string" }, { "item", "Models.Config.StoredCredential" } } + Params = new Dictionary { { "instanceId", "string" }, { "item", "Models.Config.StoredCredential" } } }, - new GeneratedAPI { + new() { OperationName = "RemoveStoredCredential", OperationMethod = HttpDelete, Comment = "Remove Stored Credential", @@ -309,9 +297,9 @@ public static List GetApiDefinitions() PublicAPIController = "StoredCredential", PublicAPIRoute = "{instanceId}/{storageKey}", ReturnType = "Models.Config.ActionResult", - Params =new Dictionary{ { "instanceId", "string" },{ "storageKey", "string" } } + Params = new Dictionary { { "instanceId", "string" }, { "storageKey", "string" } } }, - new GeneratedAPI { + new() { OperationName = "GetDeploymentProviders", OperationMethod = HttpGet, Comment = "Get Deployment Task Providers", @@ -319,24 +307,24 @@ public static List GetApiDefinitions() PublicAPIController = "DeploymentTask", PublicAPIRoute = "{instanceId}", ReturnType = "ICollection", - Params =new Dictionary{ + Params = new Dictionary{ { "instanceId", "string" } } }, - new GeneratedAPI { + new() { OperationName = "GetTargetServiceTypes", OperationMethod = HttpGet, Comment = "Get Service Types present on instance (IIS, nginx etc)", UseManagementAPI = true, - ManagementHubCommandType = Models.Hub.ManagementHubCommands.GetTargetServiceTypes, + ManagementHubCommandType = Models.Hub.ManagementHubCommands.GetTargetServiceTypes, PublicAPIController = "Target", PublicAPIRoute = "{instanceId}/types", ReturnType = "ICollection", - Params =new Dictionary{ + Params = new Dictionary{ { "instanceId", "string" } } }, - new GeneratedAPI { + new() { OperationName = "GetTargetServiceItems", OperationMethod = HttpGet, Comment = "Get Service items (sites) present on instance (IIS, nginx etc).", @@ -345,12 +333,12 @@ public static List GetApiDefinitions() PublicAPIController = "Target", PublicAPIRoute = "{instanceId}/{serviceType}/items", ReturnType = "ICollection", - Params =new Dictionary{ + Params = new Dictionary{ { "instanceId", "string" }, { "serviceType", "string" } } }, - new GeneratedAPI { + new() { OperationName = "GetTargetServiceItemIdentifiers", OperationMethod = HttpGet, Comment = "Get Service item identifiers (domains on a website etc) present on instance (IIS, nginx etc)", @@ -359,13 +347,13 @@ public static List GetApiDefinitions() PublicAPIController = "Target", PublicAPIRoute = "{instanceId}/{serviceType}/item/{itemId}/identifiers", ReturnType = "ICollection", - Params =new Dictionary{ + Params = new Dictionary{ { "instanceId", "string" }, { "serviceType", "string" }, { "itemId", "string" } } }, - new GeneratedAPI { + new() { OperationName = "GetChallengeProviders", OperationMethod = HttpGet, Comment = "Get Dns Challenge Providers", @@ -373,25 +361,25 @@ public static List GetApiDefinitions() PublicAPIController = "ChallengeProvider", PublicAPIRoute = "{instanceId}", ReturnType = "ICollection", - Params =new Dictionary{ + Params = new Dictionary{ { "instanceId", "string" } } }, - new GeneratedAPI { - OperationName = "GetDnsZones", - OperationMethod = HttpGet, - Comment = "Get List of Zones with the current DNS provider and credential", - UseManagementAPI = true, - PublicAPIController = "ChallengeProvider", - PublicAPIRoute = "{instanceId}/dnszones/{providerTypeId}/{credentialId}", - ReturnType = "ICollection", - Params =new Dictionary{ + new() { + OperationName = "GetDnsZones", + OperationMethod = HttpGet, + Comment = "Get List of Zones with the current DNS provider and credential", + UseManagementAPI = true, + PublicAPIController = "ChallengeProvider", + PublicAPIRoute = "{instanceId}/dnszones/{providerTypeId}/{credentialId}", + ReturnType = "ICollection", + Params = new Dictionary{ { "instanceId", "string" } , { "providerTypeId", "string" }, { "credentialId", "string" } } - }, - new GeneratedAPI { + }, + new() { OperationName = "ExecuteDeploymentTask", OperationMethod = HttpGet, Comment = "Execute Deployment Task", @@ -399,13 +387,13 @@ public static List GetApiDefinitions() PublicAPIController = "DeploymentTask", PublicAPIRoute = "{instanceId}/execute/{managedCertificateId}/{taskId}", ReturnType = "ICollection", - Params =new Dictionary{ + Params = new Dictionary{ { "instanceId", "string" }, { "managedCertificateId", "string" }, { "taskId", "string" } } }, - new GeneratedAPI { + new() { OperationName = "RemoveManagedCertificate", OperationMethod = HttpDelete, Comment = "Remove Managed Certificate", @@ -413,10 +401,10 @@ public static List GetApiDefinitions() PublicAPIController = "Certificate", PublicAPIRoute = "{instanceId}/settings/{managedCertId}", ReturnType = "Models.Config.ActionResult", - Params =new Dictionary{ { "instanceId", "string" },{ "managedCertId", "string" } } + Params = new Dictionary { { "instanceId", "string" }, { "managedCertId", "string" } } }, // TODO - new GeneratedAPI { + new() { OperationName = "PerformExport", OperationMethod = HttpPost, Comment = "Perform an export of all settings", @@ -424,18 +412,18 @@ public static List GetApiDefinitions() PublicAPIRoute = "system/migration/export", ServiceAPIRoute = "system/migration/export", ReturnType = "Models.Config.Migration.ImportExportPackage", - Params =new Dictionary{{ "exportRequest", "Certify.Models.Config.Migration.ExportRequest" } } - }, - new GeneratedAPI { - OperationName = "PerformImport", - OperationMethod = HttpPost, - Comment = "Perform an import of all settings", - PublicAPIController = "System", - PublicAPIRoute = "system/migration/import", - ServiceAPIRoute = "system/migration/import", - ReturnType = "ICollection", - Params =new Dictionary{{ "importRequest", "Certify.Models.Config.Migration.ImportRequest" } } - }, + Params = new Dictionary { { "exportRequest", "Certify.Models.Config.Migration.ExportRequest" } } + }, + new() { + OperationName = "PerformImport", + OperationMethod = HttpPost, + Comment = "Perform an import of all settings", + PublicAPIController = "System", + PublicAPIRoute = "system/migration/import", + ServiceAPIRoute = "system/migration/import", + ReturnType = "ICollection", + Params = new Dictionary { { "importRequest", "Certify.Models.Config.Migration.ImportRequest" } } + }, }; } } From 1780730b115608c47b04a8757ebaf982debb6378 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Mon, 13 Jan 2025 18:27:03 +0800 Subject: [PATCH 292/328] Hub: provide method to change API url for mgtm hub instance reports to --- src/Certify.Client/CertifyApiClient.cs | 8 +- src/Certify.Client/ICertifyClient.cs | 4 +- .../CertifyManager.Maintenance.cs | 8 +- .../CertifyManager.ManagementHub.cs | 26 ++++- .../CertifyManager/ICertifyManager.cs | 3 +- src/Certify.Models/Hub/HubInfo.cs | 9 ++ .../Certify.API.Public.cs | 94 +++++++++++++++++-- .../Controllers/internal/HubController.cs | 20 +++- .../Controllers/SystemController.cs | 3 + .../Properties/launchSettings.json | 11 ++- 10 files changed, 167 insertions(+), 19 deletions(-) create mode 100644 src/Certify.Models/Hub/HubInfo.cs diff --git a/src/Certify.Client/CertifyApiClient.cs b/src/Certify.Client/CertifyApiClient.cs index c393c3b1d..82fe10758 100644 --- a/src/Certify.Client/CertifyApiClient.cs +++ b/src/Certify.Client/CertifyApiClient.cs @@ -6,9 +6,9 @@ using System.Net.Http.Headers; using System.Threading; using System.Threading.Tasks; -using Certify.Models.Hub; using Certify.Models; using Certify.Models.Config; +using Certify.Models.Hub; using Certify.Models.Reporting; using Certify.Models.Utils; using Certify.Shared; @@ -287,6 +287,12 @@ public async Task> PerformServiceDiagnostics(AuthContext auth return JsonConvert.DeserializeObject>(result); } + public async Task UpdateManagementHub(string url, string joiningKey, AuthContext authContext = null) + { + var result = await PostAsync($"system/hub/update/", new { url, joiningKey }, authContext); + return JsonConvert.DeserializeObject(await result.Content.ReadAsStringAsync()); + } + public async Task> SetDefaultDataStore(string dataStoreId, AuthContext authContext = null) { var result = await PostAsync($"system/datastores/setdefault/{dataStoreId}", null, authContext); diff --git a/src/Certify.Client/ICertifyClient.cs b/src/Certify.Client/ICertifyClient.cs index cbac95bd8..c5c2f1537 100644 --- a/src/Certify.Client/ICertifyClient.cs +++ b/src/Certify.Client/ICertifyClient.cs @@ -3,9 +3,9 @@ using System.Threading.Tasks; using Certify.Models; using Certify.Models.Config; +using Certify.Models.Hub; using Certify.Models.Reporting; using Certify.Models.Utils; -using Certify.Models.Hub; using Certify.Shared; namespace Certify.Client @@ -27,6 +27,8 @@ public partial interface ICertifyInternalApiClient Task> CopyDataStore(string sourceId, string targetId, AuthContext authContext = null); Task> UpdateDataStoreConnection(DataStoreConnection dataStoreConnection, AuthContext authContext = null); Task> TestDataStoreConnection(DataStoreConnection dataStoreConnection, AuthContext authContext = null); + + Task UpdateManagementHub(string url, string joiningKey, AuthContext authContext = null); #endregion System #region Server diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.Maintenance.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.Maintenance.cs index 831c7124d..148a386b8 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.Maintenance.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.Maintenance.cs @@ -6,9 +6,9 @@ using System.Threading; using System.Threading.Tasks; using Certify.Core.Management.Access; -using Certify.Models.Hub; using Certify.Models; using Certify.Models.Config; +using Certify.Models.Hub; using Certify.Models.Shared; namespace Certify.Management @@ -36,6 +36,12 @@ private async Task UpgradeSettings() await PerformServiceUpgrades(); CoreAppSettings.Current.CurrentServiceVersion = systemVersion; + + if (Environment.GetEnvironmentVariable("CERTIFY_ENABLE_MANAGEMENT_HUB")?.Equals("true", StringComparison.InvariantCultureIgnoreCase) == true) + { + CoreAppSettings.Current.IsManagementHub = true; + } + SettingsManager.SaveAppSettings(); var accessControl = await GetCurrentAccessControl(); diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs index faa7e22b4..d7fbaaa4d 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs @@ -4,12 +4,11 @@ using System.Text.Json; using System.Threading.Tasks; using Certify.Client; -using Certify.Models.Hub; +using Certify.Locales; using Certify.Models; using Certify.Models.Config; +using Certify.Models.Hub; using Certify.Shared.Core.Utils; -using System.Runtime.InteropServices; -using Certify.Locales; namespace Certify.Management { @@ -18,6 +17,27 @@ public partial class CertifyManager private ManagementServerClient _managementServerClient; private string _managementServerConnectionId = string.Empty; + public async Task UpdateManagementHub(string url, string joiningKey) + { + + _serverConfig = SharedUtils.ServiceConfigManager.GetAppServiceConfig(); + _serverConfig.ManagementServerHubUri = url; + SharedUtils.ServiceConfigManager.StoreUpdatedAppServiceConfig(_serverConfig); + + _managementServerClient = null; + + try + { + await EnsureMgmtHubConnection(); + } + catch + { + return new ActionStep("Update Management Hub Failed", "A problem occurred when connecting to the management hub. Check URL.", hasError: true); + } + + return new ActionStep("Updated Management Hub", "OK", false); + } + private async Task EnsureMgmtHubConnection() { // connect/reconnect to management hub if enabled diff --git a/src/Certify.Core/Management/CertifyManager/ICertifyManager.cs b/src/Certify.Core/Management/CertifyManager/ICertifyManager.cs index 730c6b605..6d30723cf 100644 --- a/src/Certify.Core/Management/CertifyManager/ICertifyManager.cs +++ b/src/Certify.Core/Management/CertifyManager/ICertifyManager.cs @@ -3,10 +3,10 @@ using System.Collections.Generic; using System.Threading.Tasks; using Certify.Config; -using Certify.Models.Hub; using Certify.Models; using Certify.Models.Config; using Certify.Models.Config.Migration; +using Certify.Models.Hub; using Certify.Models.Providers; using Certify.Providers; using Certify.Shared; @@ -117,5 +117,6 @@ public interface ICertifyManager Task DeleteManagedChallenge(string id); Task PerformManagedChallengeRequest(ManagedChallengeRequest request); Task CleanupManagedChallengeRequest(ManagedChallengeRequest request); + Task UpdateManagementHub(string url, string joiningKey); } } diff --git a/src/Certify.Models/Hub/HubInfo.cs b/src/Certify.Models/Hub/HubInfo.cs new file mode 100644 index 000000000..99ce73f28 --- /dev/null +++ b/src/Certify.Models/Hub/HubInfo.cs @@ -0,0 +1,9 @@ +namespace Certify.Models.Hub +{ + public class HubInfo + { + public string InstanceId { get; set; } + + public VersionInfo Version { get; set; } + } +} diff --git a/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs b/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs index fb6b84397..d5d042ed8 100644 --- a/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs +++ b/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs @@ -79,7 +79,7 @@ public string BaseUrl partial void ProcessResponse(System.Net.Http.HttpClient client, System.Net.Http.HttpResponseMessage response); /// - /// Get list of Assigned Roles for a given security principle [Generated by Certify.SourceGenerators] + /// Check a given security principle has permissions to perform a specific action for a specific resource type [Generated by Certify.SourceGenerators] /// /// OK /// A server side error occurred. @@ -90,7 +90,7 @@ public virtual System.Threading.Tasks.Task CheckSecurityPrincipleHasAccess /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Get list of Assigned Roles for a given security principle [Generated by Certify.SourceGenerators] + /// Check a given security principle has permissions to perform a specific action for a specific resource type [Generated by Certify.SourceGenerators] /// /// OK /// A server side error occurred. @@ -3211,8 +3211,8 @@ public virtual async System.Threading.Tasks.TaskOK + /// A server side error occurred. + public virtual System.Threading.Tasks.Task GetHubInfoAsync() + { + return GetHubInfoAsync(System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// OK + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task GetHubInfoAsync(System.Threading.CancellationToken cancellationToken) + { + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); + // Operation Path: "internal/v1/hub/info" + urlBuilder_.Append("internal/v1/hub/info"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + /// /// Request a challenge response /// diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/HubController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/HubController.cs index b34e47121..a6b816b49 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/HubController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/HubController.cs @@ -12,7 +12,7 @@ namespace Certify.Server.Api.Public.Controllers /// Provides managed certificate related operations /// [ApiController] - [Route("api/v1/[controller]")] + [Route("internal/v1/[controller]")] public partial class HubController : ApiControllerBase { @@ -120,5 +120,23 @@ public async Task FlushHubManagedInstances() await _mgmtHubContext.Clients.All.SendCommandRequest(new InstanceCommandRequest(ManagementHubCommands.Reconnect)); return new OkResult(); } + + [HttpGet] + [Route("info")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(HubInfo))] + public async Task GetHubInfo() + { + var hubInfo = new HubInfo(); + + var hubprefs = await _client.GetPreferences(); + + hubInfo.InstanceId = hubprefs.InstanceId; + + var versionInfo = await _client.GetAppVersion(); + + hubInfo.Version = new Models.Hub.VersionInfo { Version = versionInfo, Product = "Certify Management Hub" }; + + return new OkObjectResult(hubInfo); + } } } diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/SystemController.cs b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/SystemController.cs index b0e165664..19cfbd2ab 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/SystemController.cs +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/SystemController.cs @@ -96,5 +96,8 @@ public async Task> TestDataStore(DataStoreConnection dataStore) [HttpPost, Route("datastores/delete")] public async Task> RemoveDataStore(string dataStoreId) => await _certifyManager.RemoveDataStoreConnection(dataStoreId); + + [HttpPost, Route("hub/update")] + public async Task UpdateManagementHub(string url, string joiningKey) => await _certifyManager.UpdateManagementHub(url, joiningKey); } } diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Properties/launchSettings.json b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Properties/launchSettings.json index 31fcdf982..ede1494dd 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Properties/launchSettings.json +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Properties/launchSettings.json @@ -16,7 +16,8 @@ "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development", "ASPNETCORE_URLS": "http://127.0.0.2:9695", - "CERTIFY_MANAGEMENT_HUB": "https://localhost:44361/api/internal/managementhub" + "CERTIFY_MANAGEMENT_HUB": "https://localhost:44361/api/internal/managementhub", + "CERTIFY_ENABLE_MANAGEMENT_HUB": "true" } }, "IIS Express": { @@ -35,7 +36,8 @@ "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/docs", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development", - "ASPNETCORE_URLS": "http://0.0.0.0:9695" + "ASPNETCORE_URLS": "http://0.0.0.0:9695", + "CERTIFY_ENABLE_MANAGEMENT_HUB": "true" }, "publishAllPorts": true, "httpPort": 9695 @@ -45,7 +47,10 @@ "launchBrowser": false, "launchUrl": "http://localhost:9695/docs", "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" + "ASPNETCORE_ENVIRONMENT": "Development", + "ASPNETCORE_URLS": "http://0.0.0.0:9695", + "CERTIFY_ENABLE_MANAGEMENT_HUB": "true", + "CERTIFY_MANAGEMENT_HUB": "https://localhost:44361/api/internal/managementhub" } } } From 75d1bec7aa954c22dd836ac3a533279d61f080c4 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Mon, 13 Jan 2025 18:28:04 +0800 Subject: [PATCH 293/328] Core: on non-windows generate all common cert components, including PFX --- .../CertifyManager/CertifyManager.Account.cs | 2 +- src/Certify.Shared/Utils/PKI/CertUtils.cs | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.Account.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.Account.cs index fa25508eb..bc7cf9347 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.Account.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.Account.cs @@ -87,7 +87,7 @@ private async Task GetACMEProvider(string storageKey, strin if (!_useWindowsNativeFeatures) { - newProvider.DefaultCertificateFormat = "pem"; + newProvider.DefaultCertificateFormat = "all"; } await newProvider.InitProvider(_serviceLog, account); diff --git a/src/Certify.Shared/Utils/PKI/CertUtils.cs b/src/Certify.Shared/Utils/PKI/CertUtils.cs index 9963876f3..8ba0009d7 100644 --- a/src/Certify.Shared/Utils/PKI/CertUtils.cs +++ b/src/Certify.Shared/Utils/PKI/CertUtils.cs @@ -1,6 +1,7 @@ using System; using System.IO; using System.Linq; +using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using Certify.Management; using Org.BouncyCastle.Asn1.X509; @@ -41,7 +42,21 @@ public static string GetCertComponentsAsPEMString(byte[] pfxData, string pwd, Ex { // See also https://www.digicert.com/ssl-support/pem-ssl-creation.htm - var cert = new X509Certificate2(pfxData, pwd); + X509Certificate2 cert = null; +#if NET9_0_OR_GREATER + try + { + cert = X509CertificateLoader.LoadPkcs12(pfxData, pwd, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable); + } + catch (CryptographicException) + { + // try again using blank pwd + cert = X509CertificateLoader.LoadPkcs12(pfxData, "", X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable); + } +#else + cert = new X509Certificate2(pfxData, pwd); +#endif + var chain = new X509Chain(); chain.Build(cert); From 06021004816dffad594803dcd3033767a85e1be5 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Mon, 13 Jan 2025 18:28:41 +0800 Subject: [PATCH 294/328] Cleanup --- .../Management/SettingsManager.cs | 5 - src/Certify.Locales/SR.Designer.cs | 9 - src/Certify.Locales/SR.es-ES.resx | 377 +++++++++--------- src/Certify.Locales/SR.ja-JP.resx | 19 +- src/Certify.Locales/SR.nb-NO.resx | 3 - src/Certify.Locales/SR.resx | 3 - src/Certify.Locales/SR.tr-TR.resx | 3 - src/Certify.Locales/SR.zh-Hans.resx | 3 - src/Certify.Models/Config/Preferences.cs | 2 - .../v1/ManagedChallengeController.cs | 1 - .../Certify.Server.Api.Public/Startup.cs | 4 - .../Certify.Server.Core/Startup.cs | 12 +- 12 files changed, 201 insertions(+), 240 deletions(-) diff --git a/src/Certify.Core/Management/SettingsManager.cs b/src/Certify.Core/Management/SettingsManager.cs index 4047aa40d..77a81df9c 100644 --- a/src/Certify.Core/Management/SettingsManager.cs +++ b/src/Certify.Core/Management/SettingsManager.cs @@ -19,7 +19,6 @@ private CoreAppSettings() IgnoreStoppedSites = true; EnableValidationProxyAPI = true; EnableAppTelematics = true; - EnableEFS = false; EnableDNSValidationChecks = false; RenewalIntervalDays = 75; RenewalIntervalMode = RenewalIntervalModes.PercentageLifetime; @@ -76,8 +75,6 @@ public static CoreAppSettings Current public bool EnableValidationProxyAPI { get; set; } - public bool EnableEFS { get; set; } - public bool EnableDNSValidationChecks { get; set; } /// @@ -204,7 +201,6 @@ public static bool FromPreferences(Models.Preferences prefs) CoreAppSettings.Current.MaxRenewalRequests = prefs.MaxRenewalRequests; CoreAppSettings.Current.RenewalIntervalMode = prefs.RenewalIntervalMode; CoreAppSettings.Current.RenewalIntervalDays = prefs.RenewalIntervalDays; - CoreAppSettings.Current.EnableEFS = prefs.EnableEFS; CoreAppSettings.Current.IsInstanceRegistered = prefs.IsInstanceRegistered; CoreAppSettings.Current.Language = prefs.Language; CoreAppSettings.Current.EnableHttpChallengeServer = prefs.EnableHttpChallengeServer; @@ -261,7 +257,6 @@ public static Models.Preferences ToPreferences() MaxRenewalRequests = CoreAppSettings.Current.MaxRenewalRequests, RenewalIntervalMode = CoreAppSettings.Current.RenewalIntervalMode, RenewalIntervalDays = CoreAppSettings.Current.RenewalIntervalDays, - EnableEFS = CoreAppSettings.Current.EnableEFS, InstanceId = CoreAppSettings.Current.InstanceId, IsInstanceRegistered = CoreAppSettings.Current.IsInstanceRegistered, Language = CoreAppSettings.Current.Language, diff --git a/src/Certify.Locales/SR.Designer.cs b/src/Certify.Locales/SR.Designer.cs index ae8a942ce..33e33a1bf 100644 --- a/src/Certify.Locales/SR.Designer.cs +++ b/src/Certify.Locales/SR.Designer.cs @@ -1709,15 +1709,6 @@ public static string Settings_EnableDnsValidation { } } - /// - /// Looks up a localized string similar to Enable Encrypted File System (EFS) for sensitive files. This option does not work on all versions of Windows.. - /// - public static string Settings_EnableEFS { - get { - return ResourceManager.GetString("Settings_EnableEFS", resourceCulture); - } - } - /// /// Looks up a localized string similar to Enable Http Challenge Server. /// diff --git a/src/Certify.Locales/SR.es-ES.resx b/src/Certify.Locales/SR.es-ES.resx index 0357c9c4b..3d50cced8 100644 --- a/src/Certify.Locales/SR.es-ES.resx +++ b/src/Certify.Locales/SR.es-ES.resx @@ -165,6 +165,9 @@ Registrar Contacto + + Para solicitar certificados, debe registrarse en cada una de las Autoridades de certificación que desee utilizar. + La dirección de correo electrónico provista se puede ser utilizada para notificarle acerca de la próxima renovación de certificados si es necesario. Las direcciones de correo electrónico inválidas serán rechazadas por la Autoridad de Certificación. @@ -195,60 +198,27 @@ Combinar múltiples dominios/certificados por sitio en un Sitio Administrado - - Importar - Omitir Importar + + Importar + ¡Huy! Algo salió mal. Por favor cuéntanos más detalles al respecto. - - Información - - - Comentarios: - - - Url: - - - Método: - - - Cuerpo: - - - ContentType: - - - Revocar Certificado - - - [Edición de la comunidad] - - - Certificado Revocado. - - - Alerta - - - Error - - - OK - - - Contraseña - Gracias, tus comentarios han sido enviados. Lo sentimos, hubo un problema al enviar sus comentarios. + + Hubo un problema al registrarse con la Autoridad de Certificación utilizando este correo electrónico. Verifique que el correo electrónico sea válido y que esta computadora tenga una conexión abierta a Internet (se requiere https saliente para las llamadas API). + + + Para continuar, confirme que acepta los términos y condiciones actuales de esta Autoridad de Certificación. + Ingrese el correo electrónico que utilizó al registrar su clave. @@ -282,6 +252,12 @@ Para realizar la renovación automática de certificados, Certify creará una tarea en el Programador de Tareas de Windows. Esta tarea debe ejecutarse como un usuario de nivel de Administrador para realizar tareas de administración de certificados y administración de IIS. + + OK + + + Contraseña + Esta versión es gratuita para evaluación y gestionará un número limitado de certificados. Para eliminar esta limitación, compre una clave de registro en https://certifytheweb.com/register. Los usuarios registrados pueden obtener asistencia enviando un correo electrónico a support@certifytheweb.com @@ -324,105 +300,36 @@ Intervalo de renovación automática (días) - - Caduca en {0} días - - - Sin certificado actual. - - - Ruta del certificado: [establecer] - - - Trigger de Webhook: - - - Usar enlaces de IP/Puerto específicos - - - Creación/ ctualización automática de enlaces de IIS (utiliza SNI) - - - Post-solicitud PS Script: - - - Traductor de idiomas actual: - - - Colaboradores de Certify Certificate Manager https://certifytheweb.com - - - ¿Desea visitar la página de descarga ahora? - - - Error al Revocar Certificado: {0} - - - ¿Está seguro de que desea revocar este certificado? - - - Estás usando la versión de prueba de esta aplicación. Por favor, compre una clave de registro para actualizar. Vea la opción Registrar en la pestaña Acerca de. - - - Esto renovará los certificados para todos los elementos de auto-renovación. ¿Proceder? - - - No se detectó IIS en este servidor, una serie de características importantes no estarán disponible. Si IIS está instalado y ejecutándose en este servidor, informe este error a support@certifytheweb.com proporcionando detalles de la versión del sistema operativo del servidor y las versiones de IIS. - - - Comience registrando un nuevo contacto, luego puede comenzar a solicitar certificados. - Número máximo de solicitudes de renovación automática por sesión (0 = Ilimitado) Buscar actualizaciones automáticamente - - ... - - - Eliminar - - - Guardar - - - Guardar Cambios - Habilitar la aplicación de telemetría (informes de uso de características) Habilitar proxy API para las comprobaciones de configuración de dominio - - exitoso - - - fallido - - - Prueba de Webhook - - - Ver Certificado - - - Error al Guardar - - - Habilite el Sistema de Cifrados de Archivos (EFS) para archivos confidenciales. Esta opción no funciona en todas las versiones de Windows. - Ignorar sitios IIS detenidos para nuevos certificados y renovaciones Habilitar comprobaciones de validación de DNS (Resolution, CAA, DNSSEC) + + Guardar Cambios + + + Guardar + Descartar Cambios + + Eliminar + Solicitar Certificado @@ -432,6 +339,9 @@ Notificar al contacto principal sobre la falla de renovación + + Se requiere al menos un nombre de host completo (por ejemplo, 'github.com') o comodín (por ejemplo, '*.github.com) para crear un certificado. + Opciones @@ -495,15 +405,24 @@ Prueba + + ... + Nota: Para exportar este certificado, use la opción Administrar Certificados en Windows. brir archivo de registro + + Ver Certificado + ADVERTENCIA: este certificado ha sido revocado. + + Error al Guardar + Seleccione el sitio web para crear un certificado @@ -559,109 +478,187 @@ Prueba de Webhook Fallida + + Prueba de Webhook + + + exitoso + + + fallido + Solicitud de Webhook {0}: HTTP {1} Error de solicitud de Webhook: {0} - - Para solicitar certificados, debe registrarse en cada una de las Autoridades de certificación que desee utilizar. - - - Hubo un problema al registrarse con la Autoridad de Certificación utilizando este correo electrónico. Verifique que el correo electrónico sea válido y que esta computadora tenga una conexión abierta a Internet (se requiere https saliente para las llamadas API). - - - Para continuar, confirme que acepta los términos y condiciones actuales de esta Autoridad de Certificación. - - - Se requiere al menos un nombre de host completo (por ejemplo, 'github.com') o comodín (por ejemplo, '*.github.com) para crear un certificado. - + + Alerta + + + Error + + + Certificado Revocado. + + + Error al Revocar Certificado: {0} + + + ¿Está seguro de que desea revocar este certificado? + + + Estás usando la versión de prueba de esta aplicación. Por favor, compre una clave de registro para actualizar. Vea la opción Registrar en la pestaña Acerca de. + + + Esto renovará los certificados para todos los elementos de auto-renovación. ¿Proceder? + + + No se detectó IIS en este servidor, una serie de características importantes no estarán disponible. Si IIS está instalado y ejecutándose en este servidor, informe este error a support@certifytheweb.com proporcionando detalles de la versión del sistema operativo del servidor y las versiones de IIS. + + + Comience registrando un nuevo contacto, luego puede comenzar a solicitar certificados. + + + ¿Desea visitar la página de descarga ahora? + + + [Edición de la comunidad] + + + Colaboradores de Certify Certificate Manager https://certifytheweb.com + + + Traductor de idiomas actual: + + + Información + + + Comentarios: + + + Revocar Certificado + + + Url: + + + Método: + + + Cuerpo: + + + ContentType: + + + Post-solicitud PS Script: + + + Creación/ ctualización automática de enlaces de IIS (utiliza SNI) + + + Usar enlaces de IP/Puerto específicos + + + Trigger de Webhook: + + + Ruta del certificado: [establecer] + + + Sin certificado actual. + + + Caduca en {0} días + - Cargando... - + Cargando... + - La versión actual de la aplicación ya no es compatible. Por favor actualize para continuar. - + La versión actual de la aplicación ya no es compatible. Por favor actualize para continuar. + - Nota: esta configuración solo se aplica a los nuevos enlaces https, los enlaces existentes solo se actualizan con el nuevo certificado. El uso de una IP fija para varios certificados provocará un conflicto de vinculación en Windows, utilícelo con precaución. - + Nota: esta configuración solo se aplica a los nuevos enlaces https, los enlaces existentes solo se actualizan con el nuevo certificado. El uso de una IP fija para varios certificados provocará un conflicto de vinculación en Windows, utilícelo con precaución. + - Vuelva a aplicar el certificado a los enlaces - + Vuelva a aplicar el certificado a los enlaces + - Agregar - + Agregar + - Contraseña - + Contraseña + - Agregar al panel de informes - + Agregar al panel de informes + - Panel de informes - + Panel de informes + - Puede utilizar el panel de informes para ver fácilmente el estado de renovación del certificado en uno o más servidores. - + Puede utilizar el panel de informes para ver fácilmente el estado de renovación del certificado en uno o más servidores. + - Ver panel de informes - + Ver panel de informes + - Crear una nueva cuenta - + Crear una nueva cuenta + - Para agregar este servidor a su panel, proporcione sus https://certifytheweb.com/profile detalles de inicio de sesión o registre una nueva cuenta: - + Para agregar este servidor a su panel, proporcione sus https://certifytheweb.com/profile detalles de inicio de sesión o registre una nueva cuenta: + - Usar servicio en segundo plano (se ejecuta como sistema local) - + Usar servicio en segundo plano (se ejecuta como sistema local) + - Usar una tarea programada (se ejecuta como usuario especificado) - + Usar una tarea programada (se ejecuta como usuario especificado) + - Actualizar - + Actualizar + - ¿Le gustaría descargar automáticamente la actualización? Se le notificará cuando esté listo para aplicar. - + ¿Le gustaría descargar automáticamente la actualización? Se le notificará cuando esté listo para aplicar. + - Una nueva actualización está lista para aplicarse. ¿Le gustaría instalarlo ahora? - + Una nueva actualización está lista para aplicarse. ¿Le gustaría instalarlo ahora? + - Está intentando crear un enlace SNI que también tiene una dirección IP enlazada específica. En su lugar, se recomienda que los enlaces SNI utilicen Todos sin asignar. ¿Desea continuar? - + Está intentando crear un enlace SNI que también tiene una dirección IP enlazada específica. En su lugar, se recomienda que los enlaces SNI utilicen Todos sin asignar. ¿Desea continuar? + - Agregar / actualizar credencial almacenada - + Agregar / actualizar credencial almacenada + - Lo sentimos, no se pudo descargar la actualización. Por favor, inténtelo de nuevo más tarde. - + Lo sentimos, no se pudo descargar la actualización. Por favor, inténtelo de nuevo más tarde. + - Administrar Certificados - + Administrar Certificados + - Nuevo certificado administrado - + Nuevo certificado administrado + - Agregue dominios al certificado: - + Agregue dominios al certificado: + - p.ej. test.com, www.test.com o * .test.com - + p.ej. test.com, www.test.com o * .test.com + - Habilitar el servidor de desafío Http - + Habilitar el servidor de desafío Http + - Habilitar la limpieza de certificados - + Habilitar la limpieza de certificados + - Habilitar informes de estado en el panel - + Habilitar informes de estado en el panel + - Autoridad de Certificación preferida - + Autoridad de Certificación preferida + - Su clave de licencia ha caducado o ya no está activa. - + Su clave de licencia ha caducado o ya no está activa. + \ No newline at end of file diff --git a/src/Certify.Locales/SR.ja-JP.resx b/src/Certify.Locales/SR.ja-JP.resx index 0f9023092..882925dd7 100644 --- a/src/Certify.Locales/SR.ja-JP.resx +++ b/src/Certify.Locales/SR.ja-JP.resx @@ -123,9 +123,6 @@ 登録したメールアドレス - - はい、同意します - キャンセル @@ -159,13 +156,13 @@ 電子メール アドレス - + はい、同意します 登録連絡先 - + 提供された電子メールアドレスは、必要に応じて今後の証明書の更新を通知するために使用することができます。 無効な電子メールアドレスはLet's Encrypt によって拒否されます。 @@ -303,9 +300,6 @@ ドメイン設定の確認にプロキシAPIを有効にする - - 機密ファイル用の暗号化ファイルシステム(EFS)を有効にする。 このオプションは、すべてのバージョンのWindowsでは動作しません。 - 新しい証明書と更新のために停止した IIS サイトを無視する @@ -606,9 +600,6 @@ バック グラウンド サービスを利用 (ローカル システムとして実行) - - 提供された電子メールアドレスは、必要に応じて今後の証明書の更新を通知するために使用することができます。 無効な電子メールアドレスはLet's Encrypt によって拒否されます。 - 更新 @@ -642,4 +633,10 @@ Http チャレンジサーバーを有効にする + + はい、同意します + + + 提供された電子メールアドレスは、必要に応じて今後の証明書の更新を通知するために使用することができます。 無効な電子メールアドレスはLet's Encrypt によって拒否されます。 + \ No newline at end of file diff --git a/src/Certify.Locales/SR.nb-NO.resx b/src/Certify.Locales/SR.nb-NO.resx index 5c23de207..044bf5774 100644 --- a/src/Certify.Locales/SR.nb-NO.resx +++ b/src/Certify.Locales/SR.nb-NO.resx @@ -297,9 +297,6 @@ Aktiver proxy API for kontroller av domener - - Aktiver kryptert filsystem (EFS) for sensitive filer. Dette alternativet virker ikke på alle versjoner av Windows. - Ignorer inaktive/stoppede IIS-nettsteder for nye sertifikater og fornyelser diff --git a/src/Certify.Locales/SR.resx b/src/Certify.Locales/SR.resx index e11a87baa..424757df1 100644 --- a/src/Certify.Locales/SR.resx +++ b/src/Certify.Locales/SR.resx @@ -312,9 +312,6 @@ Enable Proxy API for Domain Config Checks - - Enable Encrypted File System (EFS) for sensitive files. This option does not work on all versions of Windows. - Ignore stopped IIS sites for new certificates and renewals diff --git a/src/Certify.Locales/SR.tr-TR.resx b/src/Certify.Locales/SR.tr-TR.resx index 9dc9b1051..7037634ca 100644 --- a/src/Certify.Locales/SR.tr-TR.resx +++ b/src/Certify.Locales/SR.tr-TR.resx @@ -312,9 +312,6 @@ Alan adı yapılandırma kontrolleri için proxy API'sini etkinleştir - - Hassas dosyalar için Şifreli Dosya Sistemini (EFS) etkinleştirin. Bu seçenek, Windows'un bazı sürümlerinde çalışmaz. - Yeni sertifikalar ve yenilemeler için durdurulan IIS sitelerini yoksay diff --git a/src/Certify.Locales/SR.zh-Hans.resx b/src/Certify.Locales/SR.zh-Hans.resx index 003f09308..508e8b032 100644 --- a/src/Certify.Locales/SR.zh-Hans.resx +++ b/src/Certify.Locales/SR.zh-Hans.resx @@ -303,9 +303,6 @@ 启用域名检测配置的代理API - - 启用敏感文件保存在EFS中,这个选项并不支持所有Windows系统 - 申请证书和自动续期时忽略已停止站点 diff --git a/src/Certify.Models/Config/Preferences.cs b/src/Certify.Models/Config/Preferences.cs index 973cec099..afc4c5c36 100644 --- a/src/Certify.Models/Config/Preferences.cs +++ b/src/Certify.Models/Config/Preferences.cs @@ -46,8 +46,6 @@ public class Preferences : BindableBase public bool EnableValidationProxyAPI { get; set; } = true; - public bool EnableEFS { get; set; } - public bool EnableDNSValidationChecks { get; set; } public string? RenewalIntervalMode { get; set; } = RenewalIntervalModes.PercentageLifetime; diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/ManagedChallengeController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/ManagedChallengeController.cs index 13680e8b3..8c2db0d7a 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/ManagedChallengeController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/ManagedChallengeController.cs @@ -1,6 +1,5 @@ using Certify.Client; using Certify.Models.Hub; -using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; diff --git a/src/Certify.Server/Certify.Server.Api.Public/Startup.cs b/src/Certify.Server/Certify.Server.Api.Public/Startup.cs index d335c8730..9e48e70bd 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Startup.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Startup.cs @@ -64,7 +64,6 @@ public void ConfigureServices(IServiceCollection services) services .AddSignalR(opt => opt.MaximumReceiveMessageSize = null) - .AddMessagePackProtocol(); services.AddResponseCompression(opts => @@ -137,9 +136,6 @@ public void ConfigureServices(IServiceCollection services) Format = "binary", }; }); - - - }); // connect to primary certify service diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Startup.cs b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Startup.cs index cad4c1d97..5ecad003a 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Startup.cs +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Startup.cs @@ -88,12 +88,6 @@ public void ConfigureServices(IServiceCollection services) }); #endif - // inject instance of certify manager - var certifyManager = new Management.CertifyManager(); - certifyManager.Init().Wait(); - - services.AddSingleton(certifyManager); - var useHttps = bool.Parse(Configuration["API:Service:UseHttps"]); if (useHttps) @@ -104,6 +98,12 @@ public void ConfigureServices(IServiceCollection services) options.HttpsPort = 443; }); } + + // inject instance of certify manager + var certifyManager = new Management.CertifyManager(); + certifyManager.Init().Wait(); + + services.AddSingleton(certifyManager); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. From 8ca8d175ce2f4f7629d7d8e03db763b4ee1fedd2 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Fri, 17 Jan 2025 11:58:13 +0800 Subject: [PATCH 295/328] Package update Package updates Package updates --- .../Certify.Aspire.ServiceDefaults.csproj | 6 +++--- src/Certify.Client/Certify.Client.csproj | 6 +++--- src/Certify.Core/Certify.Core.csproj | 2 +- src/Certify.Models/Certify.Models.csproj | 2 +- .../DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj | 2 +- .../DNS/Azure/Plugin.DNS.Azure.csproj | 4 ++-- .../Certify.Server.Api.Public.csproj | 4 ++-- .../Certify.Server.Core.csproj | 16 ++++++++-------- src/Certify.Service/App.config | 8 ++++---- src/Certify.Service/Certify.Service.csproj | 12 ++++++------ src/Certify.Shared/Certify.Shared.Core.csproj | 8 ++++---- .../Certify.Core.Tests.Integration.csproj | 8 ++++---- .../Certify.Core.Tests.Unit.csproj | 14 +++++++------- .../Certify.Service.Tests.Integration.csproj | 8 ++++---- .../Certify.UI.Tests.Integration.csproj | 4 ++-- src/Certify.UI.Shared/Certify.UI.Shared.csproj | 4 ++-- src/Certify.UI/App.config | 2 +- src/Certify.UI/Certify.UI.csproj | 2 +- 18 files changed, 56 insertions(+), 56 deletions(-) diff --git a/src/Certify.Aspire/Certify.Aspire.ServiceDefaults/Certify.Aspire.ServiceDefaults.csproj b/src/Certify.Aspire/Certify.Aspire.ServiceDefaults/Certify.Aspire.ServiceDefaults.csproj index d97953b42..e50a4253d 100644 --- a/src/Certify.Aspire/Certify.Aspire.ServiceDefaults/Certify.Aspire.ServiceDefaults.csproj +++ b/src/Certify.Aspire/Certify.Aspire.ServiceDefaults/Certify.Aspire.ServiceDefaults.csproj @@ -11,10 +11,10 @@ - + - - + + diff --git a/src/Certify.Client/Certify.Client.csproj b/src/Certify.Client/Certify.Client.csproj index ca96e91dd..bb63790e9 100644 --- a/src/Certify.Client/Certify.Client.csproj +++ b/src/Certify.Client/Certify.Client.csproj @@ -16,11 +16,11 @@ - - + + - + diff --git a/src/Certify.Core/Certify.Core.csproj b/src/Certify.Core/Certify.Core.csproj index 87331d8d0..e9a382c5d 100644 --- a/src/Certify.Core/Certify.Core.csproj +++ b/src/Certify.Core/Certify.Core.csproj @@ -52,7 +52,7 @@ 1701;1702;CA1068 - + diff --git a/src/Certify.Models/Certify.Models.csproj b/src/Certify.Models/Certify.Models.csproj index b789ff0eb..991b55dac 100644 --- a/src/Certify.Models/Certify.Models.csproj +++ b/src/Certify.Models/Certify.Models.csproj @@ -30,7 +30,7 @@ all - + diff --git a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj index 5316fee01..caace9741 100644 --- a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj +++ b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj b/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj index ce1a334b3..26e5fcf0e 100644 --- a/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj +++ b/src/Certify.Providers/DNS/Azure/Plugin.DNS.Azure.csproj @@ -8,9 +8,9 @@ - + - + diff --git a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj index 477fb639f..18f761684 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj +++ b/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj @@ -18,8 +18,8 @@ - - + + diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj index 4dd26dce2..a9ef0addd 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj @@ -9,16 +9,16 @@ - - - + + + - - - - - + + + + + diff --git a/src/Certify.Service/App.config b/src/Certify.Service/App.config index 25d314f33..244bbe26f 100644 --- a/src/Certify.Service/App.config +++ b/src/Certify.Service/App.config @@ -8,7 +8,7 @@ - + @@ -19,7 +19,7 @@ - - + - + diff --git a/src/Certify.Service/Certify.Service.csproj b/src/Certify.Service/Certify.Service.csproj index d3584d24f..79b00b4ed 100644 --- a/src/Certify.Service/Certify.Service.csproj +++ b/src/Certify.Service/Certify.Service.csproj @@ -37,8 +37,8 @@ - - + + @@ -49,13 +49,13 @@ - - - + + + - + diff --git a/src/Certify.Shared/Certify.Shared.Core.csproj b/src/Certify.Shared/Certify.Shared.Core.csproj index 07dab5db4..fc3524813 100644 --- a/src/Certify.Shared/Certify.Shared.Core.csproj +++ b/src/Certify.Shared/Certify.Shared.Core.csproj @@ -6,9 +6,9 @@ - - - + + + @@ -20,7 +20,7 @@ - + diff --git a/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj b/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj index 18d98cc87..802464715 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj @@ -76,18 +76,18 @@ - - + + - + - + diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj index b2b8e15d5..62524eccf 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj @@ -105,18 +105,18 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + - - + + - - + + - - + + diff --git a/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj b/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj index 2e170c298..9a1bbd784 100644 --- a/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj @@ -62,10 +62,10 @@ - - - - + + + + diff --git a/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj b/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj index 4fc947945..b452c1e2e 100644 --- a/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj @@ -58,8 +58,8 @@ - - + + diff --git a/src/Certify.UI.Shared/Certify.UI.Shared.csproj b/src/Certify.UI.Shared/Certify.UI.Shared.csproj index 8d602b413..293ad3a88 100644 --- a/src/Certify.UI.Shared/Certify.UI.Shared.csproj +++ b/src/Certify.UI.Shared/Certify.UI.Shared.csproj @@ -40,9 +40,9 @@ - + - + NU1701 diff --git a/src/Certify.UI/App.config b/src/Certify.UI/App.config index bacd4db00..496e2a49f 100644 --- a/src/Certify.UI/App.config +++ b/src/Certify.UI/App.config @@ -8,7 +8,7 @@ - + diff --git a/src/Certify.UI/Certify.UI.csproj b/src/Certify.UI/Certify.UI.csproj index 8cb760f66..21be93571 100644 --- a/src/Certify.UI/Certify.UI.csproj +++ b/src/Certify.UI/Certify.UI.csproj @@ -160,7 +160,7 @@ all - 9.0.0 + 9.0.1 4.3.4 From b4f157ec65255e23f34fae37fe1315867b61d0f0 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Fri, 17 Jan 2025 11:59:37 +0800 Subject: [PATCH 296/328] Implement ACME Profiles (draft) --- src/Certify.Models/Config/CertRequestConfig.cs | 7 ++++++- .../ACME/Anvil/AnvilACMEProvider.cs | 16 +++++++++++++++- .../ManagedCertificate/AdvancedOptions.xaml | 16 ++++++++++++++++ 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/Certify.Models/Config/CertRequestConfig.cs b/src/Certify.Models/Config/CertRequestConfig.cs index d57cb32c4..bf8374c7e 100644 --- a/src/Certify.Models/Config/CertRequestConfig.cs +++ b/src/Certify.Models/Config/CertRequestConfig.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; @@ -301,6 +301,11 @@ public CertRequestConfig() /// public float? PreferredExpiryDays { get; set; } + /// + /// If set, specifies the preferred ACME profile to request (if the selected CA offers a profile with this name) + /// + public string? AcmeProfile { get; set; } + public void ApplyDeploymentOptionDefaults() { // if the selected mode is auto, discard settings which do not apply diff --git a/src/Certify.Providers/ACME/Anvil/AnvilACMEProvider.cs b/src/Certify.Providers/ACME/Anvil/AnvilACMEProvider.cs index 8190cf84a..b1f23f330 100644 --- a/src/Certify.Providers/ACME/Anvil/AnvilACMEProvider.cs +++ b/src/Certify.Providers/ACME/Anvil/AnvilACMEProvider.cs @@ -754,8 +754,12 @@ public async Task BeginCertificateOrder(ILog log, ManagedCertifica var orderCreated = false; var orderAttemptAbandoned = false; object lastException = null; + var caSupportsARI = false; + var caSupportsRequestedProfile = false; + var profile = managedCertificate.RequestConfig.AcmeProfile?.Trim(); + try { // first check we can access the ACME API @@ -766,6 +770,16 @@ public async Task BeginCertificateOrder(ILog log, ManagedCertifica { caSupportsARI = true; } + + if (!string.IsNullOrWhiteSpace(profile) && dir.Meta?.Profiles.ContainsKey(profile) == true) + { + caSupportsRequestedProfile = true; + log?.Information($"The CA supports the specified ACME Profile [{profile}]."); + } + else + { + log?.Error($"CA does not support the specified ACME Profile [{profile}]. The order will continue without a specific profile."); + } } catch (Exception exp) { @@ -828,7 +842,7 @@ public async Task BeginCertificateOrder(ILog log, ManagedCertifica ariReplacesCertId = managedCertificate.ARICertificateId; } - order = await _acme.NewOrder(identifiers: certificateIdentifiers, notAfter: notAfter, ariReplacesCertId: ariReplacesCertId); + order = await _acme.NewOrder(identifiers: certificateIdentifiers, notAfter: notAfter, ariReplacesCertId: ariReplacesCertId, profile: caSupportsRequestedProfile ? profile : null); } if (order != null) diff --git a/src/Certify.UI.Shared/Controls/ManagedCertificate/AdvancedOptions.xaml b/src/Certify.UI.Shared/Controls/ManagedCertificate/AdvancedOptions.xaml index a0b9eebdb..58cd6d115 100644 --- a/src/Certify.UI.Shared/Controls/ManagedCertificate/AdvancedOptions.xaml +++ b/src/Certify.UI.Shared/Controls/ManagedCertificate/AdvancedOptions.xaml @@ -88,7 +88,23 @@ Width="200" HorizontalAlignment="left" Controls:TextBoxHelper.Watermark="e.g. DST Root CA X3" + DockPanel.Dock="Top" Text="{Binding SelectedItem.RequestConfig.PreferredChain}" /> + + ACME Profile (Draft) + + + The certificate authority may allow you to specify a preset "profile" name for your certificate order. + + From d1b8c0390675a38ef519a17ab52314f14bc7ab99 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Mon, 20 Jan 2025 16:10:41 +0800 Subject: [PATCH 297/328] Access control: implement scoped expiring/revokable access tokens --- .../Management/Access/AccessControl.cs | 79 +++++++++++++++-- .../Management/Access/IAccessControl.cs | 2 +- src/Certify.Models/Certify.Models.csproj | 86 +++++++++---------- src/Certify.Models/Hub/AccessControl.cs | 48 +++++++++-- src/Certify.Models/Hub/AccessControlConfig.cs | 13 --- .../Controllers/AccessController.cs | 2 +- .../Tests/AccessControlTests.cs | 86 ++++++++++++++++--- 7 files changed, 230 insertions(+), 86 deletions(-) diff --git a/src/Certify.Core/Management/Access/AccessControl.cs b/src/Certify.Core/Management/Access/AccessControl.cs index 09500136f..9299ca1a8 100644 --- a/src/Certify.Core/Management/Access/AccessControl.cs +++ b/src/Certify.Core/Management/Access/AccessControl.cs @@ -4,6 +4,7 @@ using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; +using Certify.Models.Config; using Certify.Models.Hub; using Certify.Models.Providers; using Certify.Providers; @@ -198,31 +199,56 @@ public async Task GetSecurityPrincipleByUsername(string conte return list?.SingleOrDefault(sp => sp.Username?.ToLowerInvariant() == username.ToLowerInvariant()); } - public async Task IsAuthorised(string contextUserId, string principleId, string roleId, string resourceType, string actionId, string identifier) + /// + /// Check if a security principle has access to the given resource action + /// + /// Security principle performing access check + /// Security principle to check access for + /// resource type being accessed + /// resource action required + /// optional resource identifier, if access is limited by specific resource + /// optional scoped assigned roles to limit access to (for scoped access token checks etc) + /// + public async Task IsAuthorised(string contextUserId, string principleId, string resourceType, string actionId, string identifier = null, List scopedAssignedRoles = null) { // to determine is a principle has access to perform a particular action // for each group the principle is part of - // TODO: cache results for performance + // TODO: cache results for performance based on last update of access control config, which will be largely static + // get all assigned roles (all users) var allAssignedRoles = await _store.GetItems(nameof(AssignedRole)); - var spAssigned = allAssignedRoles.Where(a => a.SecurityPrincipleId == principleId); - + // get all defined roles var allRoles = await _store.GetItems(nameof(Role)); - var spAssignedRoles = allRoles.Where(r => spAssigned.Any(t => t.RoleId == r.Id)); + // get all defined policies + var allPolicies = await _store.GetItems(nameof(ResourcePolicy)); - var spSpecificAssignedRoles = spAssigned.Where(a => spAssignedRoles.Any(r => r.Id == a.RoleId)); + // get the assigned roles for this specific security principle + var spAssignedRoles = allAssignedRoles.Where(a => a.SecurityPrincipleId == principleId); - var allPolicies = await _store.GetItems(nameof(ResourcePolicy)); + // if scoped assigned role ID specified (access token check etc), reduce scope of assigned roles to check + if (scopedAssignedRoles?.Any() == true) + { + spAssignedRoles = spAssignedRoles.Where(a => scopedAssignedRoles.Contains(a.Id)); + } - var spAssignedPolicies = allPolicies.Where(r => spAssignedRoles.Any(p => p.Policies.Contains(r.Id))); + // get all role definitions included in the principles assigned roles + var spAssignedRoleDefinitions = allRoles.Where(r => spAssignedRoles.Any(t => t.RoleId == r.Id)); + var spSpecificAssignedRoles = spAssignedRoles.Where(a => spAssignedRoleDefinitions.Any(r => r.Id == a.RoleId)); + + // get all resource policies included in the principles assigned roles + var spAssignedPolicies = allPolicies.Where(r => spAssignedRoleDefinitions.Any(p => p.Policies.Contains(r.Id))); + + // check an assigned policy allows the required resource action if (spAssignedPolicies.Any(a => a.ResourceActions.Contains(actionId))) { - // if any of the service principles assigned roles are restricted by the type of resource type, + + // if any of the service principles assigned roles are restricted by resource type, // check for identifier matches (e.g. role assignment restricted on domains ) + if (spSpecificAssignedRoles.Any(a => a.IncludedResources?.Any(r => r.ResourceType == resourceType) == true)) { var allIncludedResources = spSpecificAssignedRoles.SelectMany(a => a.IncludedResources).Distinct(); @@ -263,6 +289,36 @@ public async Task IsAuthorised(string contextUserId, string principleId, s } } + public async Task IsAccessTokenAuthorised(string contextUserId, AccessToken accessToken, string resourceType, string actionId, string identifier) + { + // resolve security principle from access token + + var assignedTokens = await _store.GetItems(nameof(AssignedAccessToken)); + + // check if a non-expired/non-revoked access token exists matching the given client ID + var knownAssignedToken = assignedTokens.SingleOrDefault(t => t.AccessTokens.Any(a => a.ClientId == accessToken.ClientId && a.Secret == accessToken.Secret && a.DateRevoked == null && (a.DateExpiry == null || a.DateExpiry >= DateTimeOffset.UtcNow))); + + if (knownAssignedToken == null) + { + return new ActionResult("Access token unknown, expired or revoked.", false); + } + + // check related principle has access + + var isAuthorised = await IsAuthorised(contextUserId, knownAssignedToken.SecurityPrincipleId, resourceType, actionId, identifier, knownAssignedToken.ScopedAssignedRoles); + + if (isAuthorised) + { + // TODO: check token scope restrictions + + return new ActionResult("OK", true); + } + else + { + return new ActionResult("Access token not authorized or invalid for action, resource or identifier", false); + } + } + /// /// Check security principle is in a given role at the system level /// @@ -398,6 +454,11 @@ public async Task AddAssignedRole(AssignedRole r) await _store.Add(nameof(AssignedRole), r); } + public async Task AddAssignedAccessToken(AssignedAccessToken t) + { + await _store.Add(nameof(AssignedAccessToken), t); + } + public async Task AddResourceAction(ResourceAction action) { await _store.Add(nameof(ResourceAction), action); diff --git a/src/Certify.Core/Management/Access/IAccessControl.cs b/src/Certify.Core/Management/Access/IAccessControl.cs index f1f711820..0c23ac01e 100644 --- a/src/Certify.Core/Management/Access/IAccessControl.cs +++ b/src/Certify.Core/Management/Access/IAccessControl.cs @@ -17,7 +17,7 @@ public interface IAccessControl /// /// Task> GetRoles(); - Task IsAuthorised(string contextUserId, string principleId, string roleId, string resourceType, string actionId, string identifier); + Task IsAuthorised(string contextUserId, string principleId, string resourceType, string actionId, string identifier = null, List scopedAssignedRoles = null); Task IsPrincipleInRole(string contextUserId, string id, string roleId); Task> GetAssignedRoles(string contextUserId, string id); Task GetSecurityPrincipleRoleStatus(string contextUserId, string id); diff --git a/src/Certify.Models/Certify.Models.csproj b/src/Certify.Models/Certify.Models.csproj index 991b55dac..a0ea409b2 100644 --- a/src/Certify.Models/Certify.Models.csproj +++ b/src/Certify.Models/Certify.Models.csproj @@ -1,48 +1,48 @@ - - netstandard2.0;net462;net9.0 - Certify - AnyCPU - 10.0 - enable - False - Certify Certificate Manager API Models - True - True - latest-recommended - Certify The Web - Certify Certificate Manager - readme.md - - - AnyCPU - - - - all - runtime; build; native; contentfiles; analyzers - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - all - - - - + + netstandard2.0;net462;net9.0 + Certify + AnyCPU + latest + enable + False + Certify Certificate Manager API Models + True + True + latest-recommended + Certify The Web - Certify Certificate Manager + readme.md + + + AnyCPU + + + + all + runtime; build; native; contentfiles; analyzers + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + all + + + + - - - + + + - - - True - \ - - + + + True + \ + + \ No newline at end of file diff --git a/src/Certify.Models/Hub/AccessControl.cs b/src/Certify.Models/Hub/AccessControl.cs index eacea4dc7..308338404 100644 --- a/src/Certify.Models/Hub/AccessControl.cs +++ b/src/Certify.Models/Hub/AccessControl.cs @@ -3,6 +3,18 @@ namespace Certify.Models.Hub { + public enum SecurityPrincipleType + { + User = 1, + Application = 2, + Group + } + + public enum SecurityPermissionType + { + ALLOW = 1, + DENY = 0 + } /// /// A Security Principle is a user or service account which can be assigned roles and other permissions @@ -24,7 +36,7 @@ public class SecurityPrinciple : ConfigurationStoreItem /// public string? ExternalIdentifier { get; set; } - public SecurityPrincipleType? PrincipleType { get; set; } + public SecurityPrincipleType PrincipleType { get; set; } = SecurityPrincipleType.User; public string? AuthKey { get; set; } @@ -58,14 +70,38 @@ public class AssignedRole : ConfigurationStoreItem /// /// Defines the role to be assigned /// - public string? RoleId { get; set; } + public string RoleId { get; set; } = default!; /// /// Specific security principle assigned to the role /// - public string? SecurityPrincipleId { get; set; } + public string SecurityPrincipleId { get; set; } = default!; - public List? IncludedResources { get; set; } + public List? IncludedResources { get; set; } = []; + } + + public class AccessToken : ConfigurationStoreItem + { + public string TokenType { get; set; } + public string Secret { get; set; } + public string ClientId { get; set; } + public DateTimeOffset? DateCreated { get; set; } + public DateTimeOffset? DateExpiry { get; set; } + public DateTimeOffset? DateRevoked { get; set; } + } + public class AssignedAccessToken : ConfigurationStoreItem + { + public string SecurityPrincipleId { get; set; } = default!; + + /// + /// Optional list of Assigned Roles this access token is scoped to + /// + public List ScopedAssignedRoles { get; set; } = []; + + /// + /// List of access tokens assigned + /// + public List AccessTokens { get; set; } = []; } /// @@ -76,12 +112,12 @@ public class Resource : ConfigurationStoreItem /// /// Type of this resource /// - public string? ResourceType { get; set; } + public string ResourceType { get; set; } = default!; /// /// Identifier for this resource, can include wildcards for domains etc /// - public string? Identifier { get; set; } + public string Identifier { get; set; } = default!; } public class ResourcePolicy : ConfigurationStoreItem diff --git a/src/Certify.Models/Hub/AccessControlConfig.cs b/src/Certify.Models/Hub/AccessControlConfig.cs index d50645de0..6e231d232 100644 --- a/src/Certify.Models/Hub/AccessControlConfig.cs +++ b/src/Certify.Models/Hub/AccessControlConfig.cs @@ -2,19 +2,6 @@ namespace Certify.Models.Hub { - public enum SecurityPrincipleType - { - User = 1, - Application = 2, - Group - } - - public enum SecurityPermissionType - { - ALLOW = 1, - DENY = 0 - } - public class StandardRoles { public static Role Administrator { get; } = new Role("sysadmin", "Administrator", "Certify Server Administrator", diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccessController.cs b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccessController.cs index dced47c8d..a38b98c8e 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccessController.cs +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccessController.cs @@ -107,7 +107,7 @@ public async Task CheckSecurityPrincipleHasAccess(string id, string resour { var accessControl = await _certifyManager.GetCurrentAccessControl(); - return await accessControl.IsAuthorised(GetContextUserId(), id, null, resourceType, actionId: resourceAction, identifier); + return await accessControl.IsAuthorised(GetContextUserId(), id, resourceType, actionId: resourceAction, identifier); } [HttpGet, Route("securityprinciple/{id}/assignedroles")] diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/AccessControlTests.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/AccessControlTests.cs index 9d4c31cc0..16e3a44d6 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/AccessControlTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/AccessControlTests.cs @@ -4,8 +4,8 @@ using System.Linq; using System.Threading.Tasks; using Certify.Core.Management.Access; -using Certify.Models.Hub; using Certify.Models; +using Certify.Models.Hub; using Certify.Providers; using Microsoft.Extensions.Logging; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -77,19 +77,19 @@ public class TestAssignedRoles { // test administrator RoleId = StandardRoles.Administrator.Id, - SecurityPrincipleId = "[test]" + SecurityPrincipleId = TestSecurityPrinciples.TestAdmin.Id }; public static AssignedRole Admin { get; } = new AssignedRole { // administrator RoleId = StandardRoles.Administrator.Id, - SecurityPrincipleId = "admin_01" + SecurityPrincipleId = TestSecurityPrinciples.Admin.Id }; public static AssignedRole DevopsUserDomainConsumer { get; } = new AssignedRole { // devops user in consumer role for a specific domain RoleId = StandardRoles.CertificateConsumer.Id, - SecurityPrincipleId = "devops_user_01", + SecurityPrincipleId = TestSecurityPrinciples.DevopsAppDomainConsumer.Id, IncludedResources = new List{ new Resource{ ResourceType=ResourceTypes.Domain, Identifier="www.example.com" }, } @@ -98,7 +98,7 @@ public class TestAssignedRoles { // devops user in consumer role for a wildcard domain RoleId = StandardRoles.CertificateConsumer.Id, - SecurityPrincipleId = "devops_user_01", + SecurityPrincipleId = TestSecurityPrinciples.DevopsUser.Id, IncludedResources = new List{ new Resource{ ResourceType=ResourceTypes.Domain, Identifier="*.microsoft.com" }, } @@ -162,7 +162,7 @@ public class AccessControlTests private const string contextUserId = "[test]"; [TestInitialize] - public void TestInitialize() + public async Task TestInitialize() { this.loggy = new Loggy(LoggerFactory.Create(builder => builder.AddDebug()).CreateLogger()); @@ -654,7 +654,7 @@ public async Task TestDomainAuth() _ = await access.AddSecurityPrinciple(contextUserId, TestSecurityPrinciples.DevopsUser, bypassIntegrityCheck: true); // Setup security principle actions - await access.AddResourceAction(Policies.GetStandardResourceActions().Find(r => r.Id == "certificate_download")); + await access.AddResourceAction(Policies.GetStandardResourceActions().Find(r => r.Id == StandardResourceActions.CertificateDownload)); // Setup policy with actions and add policy to store var policy = Policies.GetStandardPolicies().Find(p => p.Id == StandardPolicies.CertificateConsumer); @@ -668,11 +668,11 @@ public async Task TestDomainAuth() await access.AddAssignedRole(TestAssignedRoles.DevopsUserDomainConsumer); // devops user in consumer role for a specific domain // Validate user can consume a cert for a given domain - var isAuthorised = await access.IsAuthorised(contextUserId, "devops_user_01", StandardRoles.CertificateConsumer.Id, ResourceTypes.Domain, "certificate_download", "www.example.com"); + var isAuthorised = await access.IsAuthorised(contextUserId, TestSecurityPrinciples.DevopsAppDomainConsumer.Id, ResourceTypes.Domain, StandardResourceActions.CertificateDownload, identifier: "www.example.com"); Assert.IsTrue(isAuthorised, "User should be a cert consumer for this domain"); // Validate user can't consume a cert for a subdomain they haven't been granted - isAuthorised = await access.IsAuthorised(contextUserId, "devops_user_01", StandardRoles.CertificateConsumer.Id, ResourceTypes.Domain, "certificate_download", "secure.example.com"); + isAuthorised = await access.IsAuthorised(contextUserId, TestSecurityPrinciples.DevopsAppDomainConsumer.Id, ResourceTypes.Domain, StandardResourceActions.CertificateDownload, identifier: "secure.example.com"); Assert.IsFalse(isAuthorised, "User should not be a cert consumer for this domain"); } @@ -697,15 +697,15 @@ public async Task TestWildcardDomainAuth() await access.AddAssignedRole(TestAssignedRoles.DevopsUserWildcardDomainConsumer); // devops user in consumer role for a wildcard domain // Validate user can consume any subdomain via a granted wildcard - var isAuthorised = await access.IsAuthorised(contextUserId, "devops_user_01", StandardRoles.CertificateConsumer.Id, ResourceTypes.Domain, "certificate_download", "random.microsoft.com"); + var isAuthorised = await access.IsAuthorised(contextUserId, TestSecurityPrinciples.DevopsUser.Id, ResourceTypes.Domain, StandardResourceActions.CertificateDownload, identifier: "random.microsoft.com"); Assert.IsTrue(isAuthorised, "User should be a cert consumer for this subdomain via wildcard"); // Validate user can't consume a random wildcard - isAuthorised = await access.IsAuthorised(contextUserId, "devops_user_01", StandardRoles.CertificateConsumer.Id, ResourceTypes.Domain, "certificate_download", "* lkjhasdf98862364"); + isAuthorised = await access.IsAuthorised(contextUserId, TestSecurityPrinciples.DevopsUser.Id, ResourceTypes.Domain, StandardResourceActions.CertificateDownload, identifier: "* lkjhasdf98862364"); Assert.IsFalse(isAuthorised, "User should not be a cert consumer for random wildcard"); // Validate user can't consume a random wildcard - isAuthorised = await access.IsAuthorised(contextUserId, "devops_user_01", StandardRoles.CertificateConsumer.Id, ResourceTypes.Domain, "certificate_download", "lkjhasdf98862364.*.microsoft.com"); + isAuthorised = await access.IsAuthorised(contextUserId, TestSecurityPrinciples.DevopsUser.Id, ResourceTypes.Domain, StandardResourceActions.CertificateDownload, identifier: "lkjhasdf98862364.*.microsoft.com"); Assert.IsFalse(isAuthorised, "User should not be a cert consumer for random wildcard"); } @@ -730,7 +730,7 @@ public async Task TestRandomUserAuth() await access.AddAssignedRole(TestAssignedRoles.DevopsUserWildcardDomainConsumer); // devops user in consumer role for a wildcard domain // Validate that random user should not be authorised - var isAuthorised = await access.IsAuthorised(contextUserId, "randomuser", StandardRoles.CertificateConsumer.Id, ResourceTypes.Domain, "certificate_download", "random.microsoft.com"); + var isAuthorised = await access.IsAuthorised(contextUserId, "randomuser", ResourceTypes.Domain, StandardResourceActions.CertificateDownload, identifier: "random.microsoft.com"); Assert.IsFalse(isAuthorised, "Unknown user should not be a cert consumer for this subdomain via wildcard"); } @@ -753,5 +753,65 @@ public async Task TestSecurityPrinciplePwdInvalid() Assert.IsFalse(check.IsSuccess, "Password should not be valid"); } + + [TestMethod] + public async Task TestUserAPIToken() + { + // setup a test security principle, add them to the certificate consumer role, assign an API token then test if they are authorized based on the API token + + // allow test admin to perform access checks + var assignedRoles = new List { TestAssignedRoles.TestAdmin }; + assignedRoles.ForEach(async r => await access.AddAssignedRole(r)); + + // Add test devops user security principle + _ = await access.AddSecurityPrinciple(contextUserId, TestSecurityPrinciples.DevopsUser, bypassIntegrityCheck: true); + + // Setup security principle actions + await access.AddResourceAction(Policies.GetStandardResourceActions().Find(r => r.Id == StandardResourceActions.CertificateDownload)); + + // Setup policy with actions and add policy to store + var policy = Policies.GetStandardPolicies().Find(p => p.Id == StandardPolicies.CertificateConsumer); + _ = await access.AddResourcePolicy(contextUserId, policy, bypassIntegrityCheck: true); + + // Setup and add roles and policy assignments to store + var role = Policies.GetStandardRoles().Find(r => r.Id == StandardRoles.CertificateConsumer.Id); + await access.AddRole(role); + + // Assign security principles to roles and add roles and policy assignments to store + await access.AddAssignedRole(TestAssignedRoles.DevopsUserWildcardDomainConsumer); // devops user in consumer role for a wildcard domain + + var assignedRolesForDevopsUser = await access.GetAssignedRoles(contextUserId, TestSecurityPrinciples.DevopsUser.Id); + + // create and assign a new API token + var apiToken = new AccessToken { ClientId = TestSecurityPrinciples.DevopsUser.Id, Secret = Guid.NewGuid().ToString(), TokenType = "Simple", Description = "An example API token" }; + var apiExpiredToken = new AccessToken { ClientId = TestSecurityPrinciples.DevopsUser.Id, Secret = Guid.NewGuid().ToString(), TokenType = "Simple", Description = "An example expired API token", DateExpiry = DateTimeOffset.UtcNow.AddDays(-1) }; + var apiRevokedToken = new AccessToken { ClientId = TestSecurityPrinciples.DevopsUser.Id, Secret = Guid.NewGuid().ToString(), TokenType = "Simple", Description = "An example revoked API token", DateRevoked = DateTimeOffset.UtcNow.AddDays(-1) }; + var apiTokenBad = new AccessToken { ClientId = TestSecurityPrinciples.DomainOwner.Id, Secret = Guid.NewGuid().ToString(), TokenType = "Simple", Description = "An example bad API token (invalid client id)" }; + var assignedToken = new AssignedAccessToken + { + AccessTokens = [apiToken, apiExpiredToken, apiRevokedToken], + SecurityPrincipleId = TestSecurityPrinciples.DevopsUser.Id, + Title = "test token", + ScopedAssignedRoles = [assignedRolesForDevopsUser.First(r => r.RoleId == StandardRoles.CertificateConsumer.Id).Id] + }; + + await access.AddAssignedAccessToken(assignedToken); + + var isAuthorized = await access.IsAccessTokenAuthorised(contextUserId, apiToken, ResourceTypes.Domain, StandardResourceActions.CertificateDownload, identifier: "random.microsoft.com"); + Assert.IsTrue(isAuthorized.IsSuccess, "Token should have access"); + + isAuthorized = await access.IsAccessTokenAuthorised(contextUserId, apiToken, ResourceTypes.Domain, StandardResourceActions.CertificateDownload, identifier: "random.test.com"); + Assert.IsFalse(isAuthorized.IsSuccess, "Token should not have access (wrong domain identifier resource)"); + + isAuthorized = await access.IsAccessTokenAuthorised(contextUserId, apiTokenBad, ResourceTypes.Domain, StandardResourceActions.CertificateDownload, identifier: "random.microsoft.com"); + Assert.IsFalse(isAuthorized.IsSuccess, "Token should not have access (bad token)"); + + isAuthorized = await access.IsAccessTokenAuthorised(contextUserId, apiExpiredToken, ResourceTypes.Domain, StandardResourceActions.CertificateDownload, identifier: "random.microsoft.com"); + Assert.IsFalse(isAuthorized.IsSuccess, "Token should not have access (expired)"); + + isAuthorized = await access.IsAccessTokenAuthorised(contextUserId, apiRevokedToken, ResourceTypes.Domain, StandardResourceActions.CertificateDownload, identifier: "random.microsoft.com"); + Assert.IsFalse(isAuthorized.IsSuccess, "Token should not have access (revoked)"); + + } } } From 021a1c72e7b1885863e34ecabcf7c2d02fbd7478 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Tue, 21 Jan 2025 18:18:26 +0800 Subject: [PATCH 298/328] Implement API token access checks and refactor access checks --- src/Certify.Client/CertifyApiClient.cs | 7 +- src/Certify.Client/ICertifyClient.cs | 1 + .../Management/Access/AccessControl.cs | 33 ++-- .../Management/Access/IAccessControl.cs | 3 +- src/Certify.Models/Hub/AccessControl.cs | 30 ++- .../Certify.API.Public.cs | 184 +++++++++--------- .../Controllers/internal/ApiControllerBase.cs | 44 ++++- .../Controllers/internal/HubController.cs | 2 +- .../Controllers/v1/CertificateController.cs | 82 +++++++- .../Controllers/AccessController.cs | 14 +- src/Certify.SourceGenerators/ApiMethods.cs | 10 +- .../PublicAPISourceGenerator.cs | 16 +- .../Tests/AccessControlTests.cs | 22 +-- 13 files changed, 294 insertions(+), 154 deletions(-) diff --git a/src/Certify.Client/CertifyApiClient.cs b/src/Certify.Client/CertifyApiClient.cs index 82fe10758..31ce32b12 100644 --- a/src/Certify.Client/CertifyApiClient.cs +++ b/src/Certify.Client/CertifyApiClient.cs @@ -749,8 +749,13 @@ public async Task> GetAccessSecurityPrinciples(AuthConte return JsonToObject>(result); } - #endregion + public async Task CheckApiTokenHasAccess(AccessToken token, AccessCheck check, AuthContext authContext = null) + { + var result = await PostAsync("access/checkapitoken", new AccessTokenCheck { Check = check, Token = token }, authContext); + return JsonConvert.DeserializeObject(await result.Content.ReadAsStringAsync()); + } + #endregion private T JsonToObject(string json) { return JsonConvert.DeserializeObject(json); diff --git a/src/Certify.Client/ICertifyClient.cs b/src/Certify.Client/ICertifyClient.cs index c5c2f1537..6f19571c4 100644 --- a/src/Certify.Client/ICertifyClient.cs +++ b/src/Certify.Client/ICertifyClient.cs @@ -29,6 +29,7 @@ public partial interface ICertifyInternalApiClient Task> TestDataStoreConnection(DataStoreConnection dataStoreConnection, AuthContext authContext = null); Task UpdateManagementHub(string url, string joiningKey, AuthContext authContext = null); + Task CheckApiTokenHasAccess(AccessToken token, AccessCheck check, AuthContext authContext = null); #endregion System #region Server diff --git a/src/Certify.Core/Management/Access/AccessControl.cs b/src/Certify.Core/Management/Access/AccessControl.cs index 9299ca1a8..e341a4c0b 100644 --- a/src/Certify.Core/Management/Access/AccessControl.cs +++ b/src/Certify.Core/Management/Access/AccessControl.cs @@ -209,7 +209,7 @@ public async Task GetSecurityPrincipleByUsername(string conte /// optional resource identifier, if access is limited by specific resource /// optional scoped assigned roles to limit access to (for scoped access token checks etc) /// - public async Task IsAuthorised(string contextUserId, string principleId, string resourceType, string actionId, string identifier = null, List scopedAssignedRoles = null) + public async Task IsSecurityPrincipleAuthorised(string contextUserId, AccessCheck check) { // to determine is a principle has access to perform a particular action // for each group the principle is part of @@ -226,12 +226,12 @@ public async Task IsAuthorised(string contextUserId, string principleId, s var allPolicies = await _store.GetItems(nameof(ResourcePolicy)); // get the assigned roles for this specific security principle - var spAssignedRoles = allAssignedRoles.Where(a => a.SecurityPrincipleId == principleId); + var spAssignedRoles = allAssignedRoles.Where(a => a.SecurityPrincipleId == check.SecurityPrincipleId); // if scoped assigned role ID specified (access token check etc), reduce scope of assigned roles to check - if (scopedAssignedRoles?.Any() == true) + if (check.ScopedAssignedRoles?.Any() == true) { - spAssignedRoles = spAssignedRoles.Where(a => scopedAssignedRoles.Contains(a.Id)); + spAssignedRoles = spAssignedRoles.Where(a => check.ScopedAssignedRoles.Contains(a.Id)); } // get all role definitions included in the principles assigned roles @@ -243,20 +243,20 @@ public async Task IsAuthorised(string contextUserId, string principleId, s var spAssignedPolicies = allPolicies.Where(r => spAssignedRoleDefinitions.Any(p => p.Policies.Contains(r.Id))); // check an assigned policy allows the required resource action - if (spAssignedPolicies.Any(a => a.ResourceActions.Contains(actionId))) + if (spAssignedPolicies.Any(a => a.ResourceActions.Contains(check.ResourceActionId))) { // if any of the service principles assigned roles are restricted by resource type, // check for identifier matches (e.g. role assignment restricted on domains ) - if (spSpecificAssignedRoles.Any(a => a.IncludedResources?.Any(r => r.ResourceType == resourceType) == true)) + if (spSpecificAssignedRoles.Any(a => a.IncludedResources?.Any(r => r.ResourceType == check.ResourceType) == true)) { var allIncludedResources = spSpecificAssignedRoles.SelectMany(a => a.IncludedResources).Distinct(); - if (resourceType == ResourceTypes.Domain && !identifier.Trim().StartsWith("*") && identifier.Contains(".")) + if (check.ResourceType == ResourceTypes.Domain && !check.Identifier.Trim().StartsWith("*") && check.Identifier.Contains(".")) { // get wildcard for respective domain identifier - var identifierComponents = identifier.Split('.'); + var identifierComponents = check.Identifier.Split('.'); var wildcard = "*." + string.Join(".", identifierComponents.Skip(1)); @@ -264,11 +264,11 @@ public async Task IsAuthorised(string contextUserId, string principleId, s foreach (var includedResource in allIncludedResources) { - if (includedResource.ResourceType == resourceType && includedResource.Identifier == wildcard) + if (includedResource.ResourceType == check.ResourceType && includedResource.Identifier == wildcard) { return true; } - else if (includedResource.ResourceType == resourceType && includedResource.Identifier == identifier) + else if (includedResource.ResourceType == check.ResourceType && includedResource.Identifier == check.Identifier) { return true; } @@ -289,7 +289,7 @@ public async Task IsAuthorised(string contextUserId, string principleId, s } } - public async Task IsAccessTokenAuthorised(string contextUserId, AccessToken accessToken, string resourceType, string actionId, string identifier) + public async Task IsAccessTokenAuthorised(string contextUserId, AccessToken accessToken, AccessCheck check) { // resolve security principle from access token @@ -305,7 +305,16 @@ public async Task IsAccessTokenAuthorised(string contextUserId, Ac // check related principle has access - var isAuthorised = await IsAuthorised(contextUserId, knownAssignedToken.SecurityPrincipleId, resourceType, actionId, identifier, knownAssignedToken.ScopedAssignedRoles); + var scopedCheck = new AccessCheck + { + SecurityPrincipleId = knownAssignedToken.SecurityPrincipleId, + ResourceActionId = check.ResourceActionId, + Identifier = check.Identifier, + ResourceType = check.ResourceType, + ScopedAssignedRoles = knownAssignedToken.ScopedAssignedRoles + }; + + var isAuthorised = await IsSecurityPrincipleAuthorised(contextUserId, scopedCheck); if (isAuthorised) { diff --git a/src/Certify.Core/Management/Access/IAccessControl.cs b/src/Certify.Core/Management/Access/IAccessControl.cs index 0c23ac01e..db88abc22 100644 --- a/src/Certify.Core/Management/Access/IAccessControl.cs +++ b/src/Certify.Core/Management/Access/IAccessControl.cs @@ -17,7 +17,8 @@ public interface IAccessControl /// /// Task> GetRoles(); - Task IsAuthorised(string contextUserId, string principleId, string resourceType, string actionId, string identifier = null, List scopedAssignedRoles = null); + Task IsSecurityPrincipleAuthorised(string contextUserId, AccessCheck check); + Task IsAccessTokenAuthorised(string contextUserId, AccessToken accessToken, AccessCheck check); Task IsPrincipleInRole(string contextUserId, string id, string roleId); Task> GetAssignedRoles(string contextUserId, string id); Task GetSecurityPrincipleRoleStatus(string contextUserId, string id); diff --git a/src/Certify.Models/Hub/AccessControl.cs b/src/Certify.Models/Hub/AccessControl.cs index 308338404..52966cb1b 100644 --- a/src/Certify.Models/Hub/AccessControl.cs +++ b/src/Certify.Models/Hub/AccessControl.cs @@ -80,11 +80,35 @@ public class AssignedRole : ConfigurationStoreItem public List? IncludedResources { get; set; } = []; } + public class AccessCheck + { + public string? SecurityPrincipleId { get; set; } = default!; + public string ResourceType { get; set; } = default!; + public string ResourceActionId { get; set; } = default!; + public string? Identifier { get; set; } = default!; + + public List ScopedAssignedRoles { get; set; } = []; + + public AccessCheck() { } + public AccessCheck(string? securityPrincipleId, string resourceType, string resourceActionId, string? identifier = null) + { + SecurityPrincipleId = securityPrincipleId; + ResourceType = resourceType; + ResourceActionId = resourceActionId; + Identifier = identifier; + } + } + + public class AccessTokenCheck + { + public AccessToken Token { get; set; } + public AccessCheck Check { get; set; } + } public class AccessToken : ConfigurationStoreItem { - public string TokenType { get; set; } - public string Secret { get; set; } - public string ClientId { get; set; } + public string TokenType { get; set; } = default!; + public string Secret { get; set; } = default!; + public string ClientId { get; set; } = default!; public DateTimeOffset? DateCreated { get; set; } public DateTimeOffset? DateExpiry { get; set; } public DateTimeOffset? DateRevoked { get; set; } diff --git a/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs b/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs index d5d042ed8..59a70f3b9 100644 --- a/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs +++ b/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs @@ -79,55 +79,40 @@ public string BaseUrl partial void ProcessResponse(System.Net.Http.HttpClient client, System.Net.Http.HttpResponseMessage response); /// - /// Check a given security principle has permissions to perform a specific action for a specific resource type [Generated by Certify.SourceGenerators] + /// Check a given security principle has permissions to perform a specific action for a specific resource action [Generated] /// /// OK /// A server side error occurred. - public virtual System.Threading.Tasks.Task CheckSecurityPrincipleHasAccessAsync(string id, string resourceType, string resourceAction, string identifier) + public virtual System.Threading.Tasks.Task CheckSecurityPrincipleHasAccessAsync(AccessCheck body) { - return CheckSecurityPrincipleHasAccessAsync(id, resourceType, resourceAction, identifier, System.Threading.CancellationToken.None); + return CheckSecurityPrincipleHasAccessAsync(body, System.Threading.CancellationToken.None); } /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Check a given security principle has permissions to perform a specific action for a specific resource type [Generated by Certify.SourceGenerators] + /// Check a given security principle has permissions to perform a specific action for a specific resource action [Generated] /// /// OK /// A server side error occurred. - public virtual async System.Threading.Tasks.Task CheckSecurityPrincipleHasAccessAsync(string id, string resourceType, string resourceAction, string identifier, System.Threading.CancellationToken cancellationToken) + public virtual async System.Threading.Tasks.Task CheckSecurityPrincipleHasAccessAsync(AccessCheck body, System.Threading.CancellationToken cancellationToken) { - if (id == null) - throw new System.ArgumentNullException("id"); - - if (resourceType == null) - throw new System.ArgumentNullException("resourceType"); - - if (resourceAction == null) - throw new System.ArgumentNullException("resourceAction"); - - if (identifier == null) - throw new System.ArgumentNullException("identifier"); - var client_ = _httpClient; var disposeClient_ = false; try { using (var request_ = new System.Net.Http.HttpRequestMessage()) { - request_.Method = new System.Net.Http.HttpMethod("GET"); + var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(body, JsonSerializerSettings); + var content_ = new System.Net.Http.StringContent(json_); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); var urlBuilder_ = new System.Text.StringBuilder(); if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); - // Operation Path: "internal/v1/access/securityprinciple/{id}/allowedaction/{resourceType}/{resourceAction}/{identifier}" - urlBuilder_.Append("internal/v1/access/securityprinciple/"); - urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(id, System.Globalization.CultureInfo.InvariantCulture))); - urlBuilder_.Append("/allowedaction/"); - urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(resourceType, System.Globalization.CultureInfo.InvariantCulture))); - urlBuilder_.Append('/'); - urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(resourceAction, System.Globalization.CultureInfo.InvariantCulture))); - urlBuilder_.Append('/'); - urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(identifier, System.Globalization.CultureInfo.InvariantCulture))); + // Operation Path: "internal/v1/access/securityprinciple/allowedaction" + urlBuilder_.Append("internal/v1/access/securityprinciple/allowedaction"); PrepareRequest(client_, request_, urlBuilder_); @@ -182,7 +167,7 @@ public virtual async System.Threading.Tasks.Task CheckSecurityPrincipleHas } /// - /// Get list of Assigned Roles for a given security principle [Generated by Certify.SourceGenerators] + /// Get list of Assigned Roles for a given security principle [Generated] /// /// OK /// A server side error occurred. @@ -193,7 +178,7 @@ public virtual async System.Threading.Tasks.Task CheckSecurityPrincipleHas /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Get list of Assigned Roles for a given security principle [Generated by Certify.SourceGenerators] + /// Get list of Assigned Roles for a given security principle [Generated] /// /// OK /// A server side error occurred. @@ -271,7 +256,7 @@ public virtual async System.Threading.Tasks.Task CheckSecurityPrincipleHas } /// - /// Get list of Assigned Roles etc for a given security principle [Generated by Certify.SourceGenerators] + /// Get list of Assigned Roles etc for a given security principle [Generated] /// /// OK /// A server side error occurred. @@ -282,7 +267,7 @@ public virtual System.Threading.Tasks.Task GetSecurityPrincipleRoleS /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Get list of Assigned Roles etc for a given security principle [Generated by Certify.SourceGenerators] + /// Get list of Assigned Roles etc for a given security principle [Generated] /// /// OK /// A server side error occurred. @@ -360,7 +345,7 @@ public virtual async System.Threading.Tasks.Task GetSecurityPrincipl } /// - /// Get list of available security Roles [Generated by Certify.SourceGenerators] + /// Get list of available security Roles [Generated] /// /// OK /// A server side error occurred. @@ -371,7 +356,7 @@ public virtual async System.Threading.Tasks.Task GetSecurityPrincipl /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Get list of available security Roles [Generated by Certify.SourceGenerators] + /// Get list of available security Roles [Generated] /// /// OK /// A server side error occurred. @@ -444,7 +429,7 @@ public virtual async System.Threading.Tasks.Task GetSecurityPrincipl } /// - /// Get list of available security principles [Generated by Certify.SourceGenerators] + /// Get list of available security principles [Generated] /// /// OK /// A server side error occurred. @@ -455,7 +440,7 @@ public virtual async System.Threading.Tasks.Task GetSecurityPrincipl /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Get list of available security principles [Generated by Certify.SourceGenerators] + /// Get list of available security principles [Generated] /// /// OK /// A server side error occurred. @@ -528,7 +513,7 @@ public virtual async System.Threading.Tasks.Task GetSecurityPrincipl } /// - /// Check password valid for security principle [Generated by Certify.SourceGenerators] + /// Check password valid for security principle [Generated] /// /// OK /// A server side error occurred. @@ -539,7 +524,7 @@ public virtual System.Threading.Tasks.Task Valid /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Check password valid for security principle [Generated by Certify.SourceGenerators] + /// Check password valid for security principle [Generated] /// /// OK /// A server side error occurred. @@ -616,7 +601,7 @@ public virtual async System.Threading.Tasks.Task } /// - /// Update password for security principle [Generated by Certify.SourceGenerators] + /// Update password for security principle [Generated] /// /// OK /// A server side error occurred. @@ -627,7 +612,7 @@ public virtual System.Threading.Tasks.Task UpdateSecurityPrinciple /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Update password for security principle [Generated by Certify.SourceGenerators] + /// Update password for security principle [Generated] /// /// OK /// A server side error occurred. @@ -704,7 +689,7 @@ public virtual async System.Threading.Tasks.Task UpdateSecurityPri } /// - /// Add new security principle [Generated by Certify.SourceGenerators] + /// Add new security principle [Generated] /// /// OK /// A server side error occurred. @@ -715,7 +700,7 @@ public virtual System.Threading.Tasks.Task AddSecurityPrincipleAsy /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Add new security principle [Generated by Certify.SourceGenerators] + /// Add new security principle [Generated] /// /// OK /// A server side error occurred. @@ -792,7 +777,7 @@ public virtual async System.Threading.Tasks.Task AddSecurityPrinci } /// - /// Remove security principle [Generated by Certify.SourceGenerators] + /// Remove security principle [Generated] /// /// OK /// A server side error occurred. @@ -803,7 +788,7 @@ public virtual System.Threading.Tasks.Task RemoveSecurityPrinciple /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Remove security principle [Generated by Certify.SourceGenerators] + /// Remove security principle [Generated] /// /// OK /// A server side error occurred. @@ -882,7 +867,7 @@ public virtual async System.Threading.Tasks.Task RemoveSecurityPri } /// - /// Update existing security principle [Generated by Certify.SourceGenerators] + /// Update existing security principle [Generated] /// /// OK /// A server side error occurred. @@ -893,7 +878,7 @@ public virtual System.Threading.Tasks.Task UpdateSecurityPrinciple /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Update existing security principle [Generated by Certify.SourceGenerators] + /// Update existing security principle [Generated] /// /// OK /// A server side error occurred. @@ -970,7 +955,7 @@ public virtual async System.Threading.Tasks.Task UpdateSecurityPri } /// - /// Update assigned roles for a security principle [Generated by Certify.SourceGenerators] + /// Update assigned roles for a security principle [Generated] /// /// OK /// A server side error occurred. @@ -981,7 +966,7 @@ public virtual System.Threading.Tasks.Task UpdateSecurityPrinciple /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Update assigned roles for a security principle [Generated by Certify.SourceGenerators] + /// Update assigned roles for a security principle [Generated] /// /// OK /// A server side error occurred. @@ -1317,23 +1302,26 @@ public virtual async System.Threading.Tasks.Task RefreshAsync(stri } /// - /// Download the latest certificate for the given managed certificate + /// Download the latest certificate for the given managed certificate. For auth provide either a valid JWT via Authorization header or use an API token (using X-ClientID and X-Client-Secret HTTP headers) /// /// OK /// A server side error occurred. - public virtual System.Threading.Tasks.Task DownloadAsync(string managedCertId, string format, string mode) + public virtual System.Threading.Tasks.Task DownloadAsync(string instanceId, string managedCertId, string format, string mode) { - return DownloadAsync(managedCertId, format, mode, System.Threading.CancellationToken.None); + return DownloadAsync(instanceId, managedCertId, format, mode, System.Threading.CancellationToken.None); } /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Download the latest certificate for the given managed certificate + /// Download the latest certificate for the given managed certificate. For auth provide either a valid JWT via Authorization header or use an API token (using X-ClientID and X-Client-Secret HTTP headers) /// /// OK /// A server side error occurred. - public virtual async System.Threading.Tasks.Task DownloadAsync(string managedCertId, string format, string mode, System.Threading.CancellationToken cancellationToken) + public virtual async System.Threading.Tasks.Task DownloadAsync(string instanceId, string managedCertId, string format, string mode, System.Threading.CancellationToken cancellationToken) { + if (instanceId == null) + throw new System.ArgumentNullException("instanceId"); + if (managedCertId == null) throw new System.ArgumentNullException("managedCertId"); @@ -1351,8 +1339,10 @@ public virtual async System.Threading.Tasks.Task DownloadAsync(str var urlBuilder_ = new System.Text.StringBuilder(); if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); - // Operation Path: "api/v1/certificate/{managedCertId}/download/{format}" + // Operation Path: "api/v1/certificate/{instanceId}/{managedCertId}/download/{format}" urlBuilder_.Append("api/v1/certificate/"); + urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(instanceId, System.Globalization.CultureInfo.InvariantCulture))); + urlBuilder_.Append('/'); urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(managedCertId, System.Globalization.CultureInfo.InvariantCulture))); urlBuilder_.Append("/download/"); urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(format, System.Globalization.CultureInfo.InvariantCulture))); @@ -2163,7 +2153,7 @@ public virtual async System.Threading.Tasks.Task PerformRene } /// - /// Remove Managed Certificate [Generated by Certify.SourceGenerators] + /// Remove Managed Certificate [Generated] /// /// OK /// A server side error occurred. @@ -2174,7 +2164,7 @@ public virtual System.Threading.Tasks.Task RemoveManagedCertificat /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Remove Managed Certificate [Generated by Certify.SourceGenerators] + /// Remove Managed Certificate [Generated] /// /// OK /// A server side error occurred. @@ -2256,7 +2246,7 @@ public virtual async System.Threading.Tasks.Task RemoveManagedCert } /// - /// Get All Acme Accounts [Generated by Certify.SourceGenerators] + /// Get All Acme Accounts [Generated] /// /// OK /// A server side error occurred. @@ -2267,7 +2257,7 @@ public virtual async System.Threading.Tasks.Task RemoveManagedCert /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Get All Acme Accounts [Generated by Certify.SourceGenerators] + /// Get All Acme Accounts [Generated] /// /// OK /// A server side error occurred. @@ -2345,7 +2335,7 @@ public virtual async System.Threading.Tasks.Task RemoveManagedCert } /// - /// Add New Acme Account [Generated by Certify.SourceGenerators] + /// Add New Acme Account [Generated] /// /// OK /// A server side error occurred. @@ -2356,7 +2346,7 @@ public virtual System.Threading.Tasks.Task AddAcmeAccountAsync(str /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Add New Acme Account [Generated by Certify.SourceGenerators] + /// Add New Acme Account [Generated] /// /// OK /// A server side error occurred. @@ -2438,7 +2428,7 @@ public virtual async System.Threading.Tasks.Task AddAcmeAccountAsy } /// - /// Get list of defined Certificate Authorities [Generated by Certify.SourceGenerators] + /// Get list of defined Certificate Authorities [Generated] /// /// OK /// A server side error occurred. @@ -2449,7 +2439,7 @@ public virtual async System.Threading.Tasks.Task AddAcmeAccountAsy /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Get list of defined Certificate Authorities [Generated by Certify.SourceGenerators] + /// Get list of defined Certificate Authorities [Generated] /// /// OK /// A server side error occurred. @@ -2527,7 +2517,7 @@ public virtual async System.Threading.Tasks.Task AddAcmeAccountAsy } /// - /// Add/Update Certificate Authority [Generated by Certify.SourceGenerators] + /// Add/Update Certificate Authority [Generated] /// /// OK /// A server side error occurred. @@ -2538,7 +2528,7 @@ public virtual System.Threading.Tasks.Task UpdateCertificateAuthor /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Add/Update Certificate Authority [Generated by Certify.SourceGenerators] + /// Add/Update Certificate Authority [Generated] /// /// OK /// A server side error occurred. @@ -2620,7 +2610,7 @@ public virtual async System.Threading.Tasks.Task UpdateCertificate } /// - /// Remove Certificate Authority [Generated by Certify.SourceGenerators] + /// Remove Certificate Authority [Generated] /// /// OK /// A server side error occurred. @@ -2631,7 +2621,7 @@ public virtual System.Threading.Tasks.Task RemoveCertificateAuthor /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Remove Certificate Authority [Generated by Certify.SourceGenerators] + /// Remove Certificate Authority [Generated] /// /// OK /// A server side error occurred. @@ -2713,7 +2703,7 @@ public virtual async System.Threading.Tasks.Task RemoveCertificate } /// - /// Remove ACME Account [Generated by Certify.SourceGenerators] + /// Remove ACME Account [Generated] /// /// OK /// A server side error occurred. @@ -2724,7 +2714,7 @@ public virtual System.Threading.Tasks.Task RemoveAcmeAccountAsync( /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Remove ACME Account [Generated by Certify.SourceGenerators] + /// Remove ACME Account [Generated] /// /// OK /// A server side error occurred. @@ -2811,7 +2801,7 @@ public virtual async System.Threading.Tasks.Task RemoveAcmeAccount } /// - /// Get Dns Challenge Providers [Generated by Certify.SourceGenerators] + /// Get Dns Challenge Providers [Generated] /// /// OK /// A server side error occurred. @@ -2822,7 +2812,7 @@ public virtual async System.Threading.Tasks.Task RemoveAcmeAccount /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Get Dns Challenge Providers [Generated by Certify.SourceGenerators] + /// Get Dns Challenge Providers [Generated] /// /// OK /// A server side error occurred. @@ -2899,7 +2889,7 @@ public virtual async System.Threading.Tasks.Task RemoveAcmeAccount } /// - /// Get List of Zones with the current DNS provider and credential [Generated by Certify.SourceGenerators] + /// Get List of Zones with the current DNS provider and credential [Generated] /// /// OK /// A server side error occurred. @@ -2910,7 +2900,7 @@ public virtual async System.Threading.Tasks.Task RemoveAcmeAccount /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Get List of Zones with the current DNS provider and credential [Generated by Certify.SourceGenerators] + /// Get List of Zones with the current DNS provider and credential [Generated] /// /// OK /// A server side error occurred. @@ -2997,7 +2987,7 @@ public virtual async System.Threading.Tasks.Task RemoveAcmeAccount } /// - /// Get Deployment Task Providers [Generated by Certify.SourceGenerators] + /// Get Deployment Task Providers [Generated] /// /// OK /// A server side error occurred. @@ -3008,7 +2998,7 @@ public virtual async System.Threading.Tasks.Task RemoveAcmeAccount /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Get Deployment Task Providers [Generated by Certify.SourceGenerators] + /// Get Deployment Task Providers [Generated] /// /// OK /// A server side error occurred. @@ -3085,7 +3075,7 @@ public virtual async System.Threading.Tasks.Task RemoveAcmeAccount } /// - /// Execute Deployment Task [Generated by Certify.SourceGenerators] + /// Execute Deployment Task [Generated] /// /// OK /// A server side error occurred. @@ -3096,7 +3086,7 @@ public virtual async System.Threading.Tasks.Task RemoveAcmeAccount /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Execute Deployment Task [Generated by Certify.SourceGenerators] + /// Execute Deployment Task [Generated] /// /// OK /// A server side error occurred. @@ -3689,7 +3679,7 @@ public virtual async System.Threading.Tasks.Task CleanupManagedCha } /// - /// Get list of available managed challenges (DNS challenge delegation etc) [Generated by Certify.SourceGenerators] + /// Get list of available managed challenges (DNS challenge delegation etc) [Generated] /// /// OK /// A server side error occurred. @@ -3700,7 +3690,7 @@ public virtual async System.Threading.Tasks.Task CleanupManagedCha /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Get list of available managed challenges (DNS challenge delegation etc) [Generated by Certify.SourceGenerators] + /// Get list of available managed challenges (DNS challenge delegation etc) [Generated] /// /// OK /// A server side error occurred. @@ -3773,7 +3763,7 @@ public virtual async System.Threading.Tasks.Task CleanupManagedCha } /// - /// Add/update a managed challenge (DNS challenge delegation etc) [Generated by Certify.SourceGenerators] + /// Add/update a managed challenge (DNS challenge delegation etc) [Generated] /// /// OK /// A server side error occurred. @@ -3784,7 +3774,7 @@ public virtual System.Threading.Tasks.Task UpdateManagedChallengeA /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Add/update a managed challenge (DNS challenge delegation etc) [Generated by Certify.SourceGenerators] + /// Add/update a managed challenge (DNS challenge delegation etc) [Generated] /// /// OK /// A server side error occurred. @@ -3861,7 +3851,7 @@ public virtual async System.Threading.Tasks.Task UpdateManagedChal } /// - /// Delete a managed challenge (DNS challenge delegation etc) [Generated by Certify.SourceGenerators] + /// Delete a managed challenge (DNS challenge delegation etc) [Generated] /// /// OK /// A server side error occurred. @@ -3872,7 +3862,7 @@ public virtual System.Threading.Tasks.Task RemoveManagedChallengeA /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Delete a managed challenge (DNS challenge delegation etc) [Generated by Certify.SourceGenerators] + /// Delete a managed challenge (DNS challenge delegation etc) [Generated] /// /// OK /// A server side error occurred. @@ -4039,7 +4029,7 @@ public virtual async System.Threading.Tasks.Task RemoveManagedChal } /// - /// Get List of Stored Credentials [Generated by Certify.SourceGenerators] + /// Get List of Stored Credentials [Generated] /// /// OK /// A server side error occurred. @@ -4050,7 +4040,7 @@ public virtual async System.Threading.Tasks.Task RemoveManagedChal /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Get List of Stored Credentials [Generated by Certify.SourceGenerators] + /// Get List of Stored Credentials [Generated] /// /// OK /// A server side error occurred. @@ -4127,7 +4117,7 @@ public virtual async System.Threading.Tasks.Task RemoveManagedChal } /// - /// Add/Update Stored Credential [Generated by Certify.SourceGenerators] + /// Add/Update Stored Credential [Generated] /// /// OK /// A server side error occurred. @@ -4138,7 +4128,7 @@ public virtual System.Threading.Tasks.Task UpdateStoredCredentialA /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Add/Update Stored Credential [Generated by Certify.SourceGenerators] + /// Add/Update Stored Credential [Generated] /// /// OK /// A server side error occurred. @@ -4219,7 +4209,7 @@ public virtual async System.Threading.Tasks.Task UpdateStoredCrede } /// - /// Remove Stored Credential [Generated by Certify.SourceGenerators] + /// Remove Stored Credential [Generated] /// /// OK /// A server side error occurred. @@ -4230,7 +4220,7 @@ public virtual System.Threading.Tasks.Task RemoveStoredCredentialA /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Remove Stored Credential [Generated by Certify.SourceGenerators] + /// Remove Stored Credential [Generated] /// /// OK /// A server side error occurred. @@ -4480,7 +4470,7 @@ public virtual async System.Threading.Tasks.Task GetHealthAsync(System.T } /// - /// Perform an export of all settings [Generated by Certify.SourceGenerators] + /// Perform an export of all settings [Generated] /// /// OK /// A server side error occurred. @@ -4491,7 +4481,7 @@ public virtual System.Threading.Tasks.Task PerformExportAsy /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Perform an export of all settings [Generated by Certify.SourceGenerators] + /// Perform an export of all settings [Generated] /// /// OK /// A server side error occurred. @@ -4568,7 +4558,7 @@ public virtual async System.Threading.Tasks.Task PerformExp } /// - /// Perform an import of all settings [Generated by Certify.SourceGenerators] + /// Perform an import of all settings [Generated] /// /// OK /// A server side error occurred. @@ -4579,7 +4569,7 @@ public virtual async System.Threading.Tasks.Task PerformExp /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Perform an import of all settings [Generated by Certify.SourceGenerators] + /// Perform an import of all settings [Generated] /// /// OK /// A server side error occurred. @@ -4656,7 +4646,7 @@ public virtual async System.Threading.Tasks.Task PerformExp } /// - /// Get Service Types present on instance (IIS, nginx etc) [Generated by Certify.SourceGenerators] + /// Get Service Types present on instance (IIS, nginx etc) [Generated] /// /// OK /// A server side error occurred. @@ -4667,7 +4657,7 @@ public virtual async System.Threading.Tasks.Task PerformExp /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Get Service Types present on instance (IIS, nginx etc) [Generated by Certify.SourceGenerators] + /// Get Service Types present on instance (IIS, nginx etc) [Generated] /// /// OK /// A server side error occurred. @@ -4745,7 +4735,7 @@ public virtual async System.Threading.Tasks.Task PerformExp } /// - /// Get Service items (sites) present on instance (IIS, nginx etc). [Generated by Certify.SourceGenerators] + /// Get Service items (sites) present on instance (IIS, nginx etc). [Generated] /// /// OK /// A server side error occurred. @@ -4756,7 +4746,7 @@ public virtual async System.Threading.Tasks.Task PerformExp /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Get Service items (sites) present on instance (IIS, nginx etc). [Generated by Certify.SourceGenerators] + /// Get Service items (sites) present on instance (IIS, nginx etc). [Generated] /// /// OK /// A server side error occurred. @@ -4839,7 +4829,7 @@ public virtual async System.Threading.Tasks.Task PerformExp } /// - /// Get Service item identifiers (domains on a website etc) present on instance (IIS, nginx etc) [Generated by Certify.SourceGenerators] + /// Get Service item identifiers (domains on a website etc) present on instance (IIS, nginx etc) [Generated] /// /// OK /// A server side error occurred. @@ -4850,7 +4840,7 @@ public virtual async System.Threading.Tasks.Task PerformExp /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Get Service item identifiers (domains on a website etc) present on instance (IIS, nginx etc) [Generated by Certify.SourceGenerators] + /// Get Service item identifiers (domains on a website etc) present on instance (IIS, nginx etc) [Generated] /// /// OK /// A server side error occurred. diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/ApiControllerBase.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/ApiControllerBase.cs index 4dddf737e..e34d7d4a2 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/ApiControllerBase.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/ApiControllerBase.cs @@ -1,6 +1,7 @@ using System.Net.Http.Headers; using System.Security.Claims; using Certify.Client; +using Certify.Models.Hub; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Caching.Memory; @@ -11,13 +12,50 @@ namespace Certify.Server.Api.Public.Controllers /// public partial class ApiControllerBase : ControllerBase { + /// + /// Check resource action access for the current user + /// + /// + /// + /// + /// + internal async Task IsAuthorized(ICertifyInternalApiClient internalApiClient, string resourceType, string resourceAction) + { + if (string.IsNullOrWhiteSpace(CurrentAuthContext?.UserId)) + { + return false; + } + + return await IsAuthorized(internalApiClient, new AccessCheck(CurrentAuthContext?.UserId, resourceType, resourceAction)); + } - internal async Task IsAuthorized(ICertifyInternalApiClient client, string resourceType, string resourceAction, string? identifier = null) + /// + /// Check resource action access for the current user + /// + /// + /// + /// + internal async Task IsAuthorized(ICertifyInternalApiClient internalApiClient, AccessCheck check) { - var isAuthorized = await client.CheckSecurityPrincipleHasAccess(CurrentAuthContext?.UserId, resourceType, resourceAction, identifier, CurrentAuthContext); - return isAuthorized; + if (string.IsNullOrWhiteSpace(CurrentAuthContext?.UserId)) + { + return false; + } + + return await internalApiClient.CheckSecurityPrincipleHasAccess(check, CurrentAuthContext); } + /// + /// Check resource action access for the given API access token + /// + /// + /// + /// + /// + internal async Task IsAccessTokenAuthorized(ICertifyInternalApiClient internalApiClient, AccessToken token, AccessCheck check) + { + return await internalApiClient.CheckApiTokenHasAccess(token, check, CurrentAuthContext); + } /// /// Get the corresponding auth context to pass to the backend service /// diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/HubController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/HubController.cs index a6b816b49..3ec28cef0 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/HubController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/HubController.cs @@ -101,7 +101,7 @@ public async Task GetHubManagedItems(string? instanceId, string? [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(List))] public async Task GetHubManagedInstances() { - if (!await IsAuthorized(_client, ResourceTypes.ManagedInstance, StandardResourceActions.ManagementHubInstancesList)) + if (!await IsAuthorized(_client, new AccessCheck(CurrentAuthContext?.UserId, ResourceTypes.ManagedInstance, StandardResourceActions.ManagementHubInstancesList))) { return Unauthorized(); } diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs index 4ec2e7eab..3a60dac5b 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs @@ -1,4 +1,5 @@ -using Certify.Client; +using System.Net; +using Certify.Client; using Certify.Models.Hub; using Certify.Models.Reporting; using Certify.Server.Api.Public.Services; @@ -36,19 +37,64 @@ public CertificateController(ILogger logger, ICertifyInte } /// - /// Download the latest certificate for the given managed certificate + /// Download the latest certificate for the given managed certificate. For auth provide either a valid JWT via Authorization header or use an API token (using X-ClientID and X-Client-Secret HTTP headers) /// + /// /// /// /// /// The certificate file in the chosen format [HttpGet] - [Route("{managedCertId}/download/{format?}")] - [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] - + [Route("{instanceId}/{managedCertId}/download/{format?}")] + [AllowAnonymous] [ProducesResponseType(typeof(FileContentResult), 200)] - public async Task Download(string managedCertId, string format, string? mode = null) + public async Task Download(string instanceId, string managedCertId, string format, string? mode = null) { + var accessPermitted = false; + + if (CurrentAuthContext != null) + { + // auth based on JWT identity + var authCheckOK = await IsAuthorized(_client, new AccessCheck(CurrentAuthContext.UserId, default!, StandardResourceActions.CertificateDownload)); + if (!authCheckOK) + { + return Problem(detail: "Identity not authorized for this action", statusCode: (int)HttpStatusCode.Unauthorized); + } + else + { + accessPermitted = true; + } + } + else + { + // auth based on client id and client secret + // check token and access control before allowing download + var clientId = Request.Headers["X-Client-ID"]; + var secret = Request.Headers["X-Client-Secret"]; + + if (string.IsNullOrWhiteSpace(clientId) || string.IsNullOrWhiteSpace(secret)) + { + return Problem(detail: "X-Client-ID or X-Client-Secret HTTP header missing in request", statusCode: (int)HttpStatusCode.Unauthorized); + } + + var accessPermittedResult = await IsAccessTokenAuthorized(_client, new AccessToken { ClientId = clientId, Secret = secret, TokenType = "Simple" }, new AccessCheck(default!, ResourceTypes.Certificate, StandardResourceActions.CertificateDownload)); + + if (accessPermittedResult.IsSuccess) + { + accessPermitted = true; + } + else + { + return Problem(detail: accessPermittedResult.Message, statusCode: (int)HttpStatusCode.Unauthorized); + } + } + + if (!accessPermitted) + { + return Unauthorized(); + } + + // default to PFX output if (format == null) { format = "pfx"; @@ -60,17 +106,35 @@ public async Task Download(string managedCertId, string format, s } // TODO: certify manager to do all the cert conversion work, server may be on another machine - var managedCert = await _client.GetManagedCertificate(managedCertId, CurrentAuthContext); + var managedCert = await _mgmtAPI.GetManagedCertificate(instanceId, managedCertId, CurrentAuthContext); - if (managedCert?.CertificatePath == null) + if (managedCert == null) { return new NotFoundResult(); } + if (managedCert != null && managedCert.DateRenewed == null) + { + // item exists but a cert is not yet available, set Retry-After header in RC1123 date format + var nextAttempt = managedCert.DateNextScheduledRenewalAttempt ?? DateTimeOffset.UtcNow.AddHours(1); + Response.Headers.RetryAfter = nextAttempt.ToString("r"); + } + + var headers = Request.GetTypedHeaders(); + if (headers.IfModifiedSince.HasValue && headers.IfModifiedSince.Value > managedCert.DateRenewed) + { + // if item has not been modified since the provided date return 304 not modified + return StatusCode((int)HttpStatusCode.NotModified); + } + var content = await System.IO.File.ReadAllBytesAsync(managedCert.CertificatePath); - return new FileContentResult(content, "application/x-pkcs12") { FileDownloadName = "certificate.pfx" }; + if (!string.IsNullOrEmpty(managedCert.CertificateThumbprintHash)) + { + Response.Headers.Append("ETag", managedCert.CertificateThumbprintHash.ToLowerInvariant()); + } + return new FileContentResult(content, "application/x-pkcs12") { FileDownloadName = "certificate.pfx" }; } /// diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccessController.cs b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccessController.cs index a38b98c8e..d82c12cda 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccessController.cs +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccessController.cs @@ -102,12 +102,20 @@ public async Task> GetRoles() return roles; } - [HttpGet, Route("securityprinciple/{id}/allowedaction/{resourceType}/{resourceAction}/{identifier?}")] - public async Task CheckSecurityPrincipleHasAccess(string id, string resourceType, string resourceAction, string? identifier) + [HttpPost, Route("securityprinciple/allowedaction/")] + public async Task CheckSecurityPrincipleHasAccess(AccessCheck check) { var accessControl = await _certifyManager.GetCurrentAccessControl(); - return await accessControl.IsAuthorised(GetContextUserId(), id, resourceType, actionId: resourceAction, identifier); + return await accessControl.IsSecurityPrincipleAuthorised(GetContextUserId(), check); + } + + [HttpPost, Route("checkapitoken/")] + public async Task CheckApiTokenHasAccess(AccessTokenCheck tokenCheck) + { + var accessControl = await _certifyManager.GetCurrentAccessControl(); + + return await accessControl.IsAccessTokenAuthorised(GetContextUserId(), tokenCheck.Token, tokenCheck.Check); } [HttpGet, Route("securityprinciple/{id}/assignedroles")] diff --git a/src/Certify.SourceGenerators/ApiMethods.cs b/src/Certify.SourceGenerators/ApiMethods.cs index b20149b0c..ab0f4a2fa 100644 --- a/src/Certify.SourceGenerators/ApiMethods.cs +++ b/src/Certify.SourceGenerators/ApiMethods.cs @@ -35,13 +35,13 @@ public static List GetApiDefinitions() new() { OperationName = "CheckSecurityPrincipleHasAccess", - OperationMethod = HttpGet, - Comment = "Check a given security principle has permissions to perform a specific action for a specific resource type", + OperationMethod = HttpPost, + Comment = "Check a given security principle has permissions to perform a specific action for a specific resource action", PublicAPIController = "Access", - PublicAPIRoute = "securityprinciple/{id}/allowedaction/{resourceType}/{resourceAction}/{identifier}", - ServiceAPIRoute = "access/securityprinciple/{id}/allowedaction/{resourceType}/{resourceAction}/{identifier}", + PublicAPIRoute = "securityprinciple/allowedaction", + ServiceAPIRoute = "access/securityprinciple/allowedaction", ReturnType = "bool", - Params =new Dictionary{{"id","string"}, { "resourceType", "string" },{ "resourceAction", "string" }, { "identifier", "string" } } + Params =new Dictionary{{"check","Certify.Models.Hub.AccessCheck"} } }, new() { OperationName = "GetSecurityPrincipleAssignedRoles", diff --git a/src/Certify.SourceGenerators/PublicAPISourceGenerator.cs b/src/Certify.SourceGenerators/PublicAPISourceGenerator.cs index 1ee719ac8..fac0a6b4f 100644 --- a/src/Certify.SourceGenerators/PublicAPISourceGenerator.cs +++ b/src/Certify.SourceGenerators/PublicAPISourceGenerator.cs @@ -117,7 +117,7 @@ namespace Certify.Server.Api.Public.Controllers public partial class {config.PublicAPIController}Controller {{ /// - /// {config.Comment} [Generated by Certify.SourceGenerators] + /// {config.Comment} [Generated] /// /// [{config.OperationMethod}] @@ -179,7 +179,7 @@ namespace Certify.Server.Api.Public.Services public partial class ManagementAPI {{ /// - /// {config.Comment} [Generated by Certify.SourceGenerators] + /// {config.Comment} [Generated] /// /// internal async Task<{config.ReturnType}> {config.OperationName}({apiParamDecl}) @@ -220,7 +220,7 @@ namespace Certify.Client public partial interface ICertifyInternalApiClient {{ /// - /// {config.Comment} [Generated by Certify.SourceGenerators] + /// {config.Comment} [Generated] /// /// Task<{config.ReturnType}> {config.OperationName}({apiParamDecl}); @@ -230,7 +230,7 @@ public partial interface ICertifyInternalApiClient public partial class CertifyApiClient {{ /// - /// {config.Comment} [Generated by Certify.SourceGenerators] + /// {config.Comment} [Generated] /// /// public async Task<{config.ReturnType}> {config.OperationName}({apiParamDecl}) @@ -262,7 +262,7 @@ public partial class CertifyApiClient public partial interface ICertifyInternalApiClient {{ /// - /// {config.Comment} [Generated by Certify.SourceGenerators] + /// {config.Comment} [Generated] /// /// Task<{config.ReturnType}> {config.OperationName}({postApiParamDecl}); @@ -272,7 +272,7 @@ public partial interface ICertifyInternalApiClient public partial class CertifyApiClient {{ /// - /// {config.Comment} [Generated by Certify.SourceGenerators] + /// {config.Comment} [Generated] /// /// public async Task<{config.ReturnType}> {config.OperationName}({postApiParamDecl}) @@ -291,7 +291,7 @@ public partial class CertifyApiClient public partial interface ICertifyInternalApiClient {{ /// - /// {config.Comment} [Generated by Certify.SourceGenerators] + /// {config.Comment} [Generated] /// /// Task<{config.ReturnType}> {config.OperationName}({apiParamDecl}); @@ -300,7 +300,7 @@ public partial interface ICertifyInternalApiClient public partial class CertifyApiClient {{ /// - /// {config.Comment} [Generated by Certify.SourceGenerators] + /// {config.Comment} [Generated] /// /// public async Task<{config.ReturnType}> {config.OperationName}({apiParamDecl}) diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/AccessControlTests.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/AccessControlTests.cs index 16e3a44d6..4f985fd0f 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/AccessControlTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/AccessControlTests.cs @@ -668,11 +668,11 @@ public async Task TestDomainAuth() await access.AddAssignedRole(TestAssignedRoles.DevopsUserDomainConsumer); // devops user in consumer role for a specific domain // Validate user can consume a cert for a given domain - var isAuthorised = await access.IsAuthorised(contextUserId, TestSecurityPrinciples.DevopsAppDomainConsumer.Id, ResourceTypes.Domain, StandardResourceActions.CertificateDownload, identifier: "www.example.com"); + var isAuthorised = await access.IsSecurityPrincipleAuthorised(contextUserId, new AccessCheck(TestSecurityPrinciples.DevopsAppDomainConsumer.Id, ResourceTypes.Domain, StandardResourceActions.CertificateDownload, identifier: "www.example.com")); Assert.IsTrue(isAuthorised, "User should be a cert consumer for this domain"); // Validate user can't consume a cert for a subdomain they haven't been granted - isAuthorised = await access.IsAuthorised(contextUserId, TestSecurityPrinciples.DevopsAppDomainConsumer.Id, ResourceTypes.Domain, StandardResourceActions.CertificateDownload, identifier: "secure.example.com"); + isAuthorised = await access.IsSecurityPrincipleAuthorised(contextUserId, new AccessCheck(TestSecurityPrinciples.DevopsAppDomainConsumer.Id, ResourceTypes.Domain, StandardResourceActions.CertificateDownload, identifier: "secure.example.com")); Assert.IsFalse(isAuthorised, "User should not be a cert consumer for this domain"); } @@ -697,15 +697,15 @@ public async Task TestWildcardDomainAuth() await access.AddAssignedRole(TestAssignedRoles.DevopsUserWildcardDomainConsumer); // devops user in consumer role for a wildcard domain // Validate user can consume any subdomain via a granted wildcard - var isAuthorised = await access.IsAuthorised(contextUserId, TestSecurityPrinciples.DevopsUser.Id, ResourceTypes.Domain, StandardResourceActions.CertificateDownload, identifier: "random.microsoft.com"); + var isAuthorised = await access.IsSecurityPrincipleAuthorised(contextUserId, new AccessCheck(TestSecurityPrinciples.DevopsUser.Id, ResourceTypes.Domain, StandardResourceActions.CertificateDownload, identifier: "random.microsoft.com")); Assert.IsTrue(isAuthorised, "User should be a cert consumer for this subdomain via wildcard"); // Validate user can't consume a random wildcard - isAuthorised = await access.IsAuthorised(contextUserId, TestSecurityPrinciples.DevopsUser.Id, ResourceTypes.Domain, StandardResourceActions.CertificateDownload, identifier: "* lkjhasdf98862364"); + isAuthorised = await access.IsSecurityPrincipleAuthorised(contextUserId, new AccessCheck(TestSecurityPrinciples.DevopsUser.Id, ResourceTypes.Domain, StandardResourceActions.CertificateDownload, identifier: "* lkjhasdf98862364")); Assert.IsFalse(isAuthorised, "User should not be a cert consumer for random wildcard"); // Validate user can't consume a random wildcard - isAuthorised = await access.IsAuthorised(contextUserId, TestSecurityPrinciples.DevopsUser.Id, ResourceTypes.Domain, StandardResourceActions.CertificateDownload, identifier: "lkjhasdf98862364.*.microsoft.com"); + isAuthorised = await access.IsSecurityPrincipleAuthorised(contextUserId, new AccessCheck(TestSecurityPrinciples.DevopsUser.Id, ResourceTypes.Domain, StandardResourceActions.CertificateDownload, identifier: "lkjhasdf98862364.*.microsoft.com")); Assert.IsFalse(isAuthorised, "User should not be a cert consumer for random wildcard"); } @@ -730,7 +730,7 @@ public async Task TestRandomUserAuth() await access.AddAssignedRole(TestAssignedRoles.DevopsUserWildcardDomainConsumer); // devops user in consumer role for a wildcard domain // Validate that random user should not be authorised - var isAuthorised = await access.IsAuthorised(contextUserId, "randomuser", ResourceTypes.Domain, StandardResourceActions.CertificateDownload, identifier: "random.microsoft.com"); + var isAuthorised = await access.IsSecurityPrincipleAuthorised(contextUserId, new AccessCheck("randomuser", ResourceTypes.Domain, StandardResourceActions.CertificateDownload, identifier: "random.microsoft.com")); Assert.IsFalse(isAuthorised, "Unknown user should not be a cert consumer for this subdomain via wildcard"); } @@ -797,19 +797,19 @@ public async Task TestUserAPIToken() await access.AddAssignedAccessToken(assignedToken); - var isAuthorized = await access.IsAccessTokenAuthorised(contextUserId, apiToken, ResourceTypes.Domain, StandardResourceActions.CertificateDownload, identifier: "random.microsoft.com"); + var isAuthorized = await access.IsAccessTokenAuthorised(contextUserId, apiToken, new AccessCheck(null, ResourceTypes.Domain, StandardResourceActions.CertificateDownload, identifier: "random.microsoft.com")); Assert.IsTrue(isAuthorized.IsSuccess, "Token should have access"); - isAuthorized = await access.IsAccessTokenAuthorised(contextUserId, apiToken, ResourceTypes.Domain, StandardResourceActions.CertificateDownload, identifier: "random.test.com"); + isAuthorized = await access.IsAccessTokenAuthorised(contextUserId, apiToken, new AccessCheck(null, ResourceTypes.Domain, StandardResourceActions.CertificateDownload, identifier: "random.test.com")); Assert.IsFalse(isAuthorized.IsSuccess, "Token should not have access (wrong domain identifier resource)"); - isAuthorized = await access.IsAccessTokenAuthorised(contextUserId, apiTokenBad, ResourceTypes.Domain, StandardResourceActions.CertificateDownload, identifier: "random.microsoft.com"); + isAuthorized = await access.IsAccessTokenAuthorised(contextUserId, apiTokenBad, new AccessCheck(null, ResourceTypes.Domain, StandardResourceActions.CertificateDownload, identifier: "random.microsoft.com")); Assert.IsFalse(isAuthorized.IsSuccess, "Token should not have access (bad token)"); - isAuthorized = await access.IsAccessTokenAuthorised(contextUserId, apiExpiredToken, ResourceTypes.Domain, StandardResourceActions.CertificateDownload, identifier: "random.microsoft.com"); + isAuthorized = await access.IsAccessTokenAuthorised(contextUserId, apiExpiredToken, new AccessCheck(null, ResourceTypes.Domain, StandardResourceActions.CertificateDownload, identifier: "random.microsoft.com")); Assert.IsFalse(isAuthorized.IsSuccess, "Token should not have access (expired)"); - isAuthorized = await access.IsAccessTokenAuthorised(contextUserId, apiRevokedToken, ResourceTypes.Domain, StandardResourceActions.CertificateDownload, identifier: "random.microsoft.com"); + isAuthorized = await access.IsAccessTokenAuthorised(contextUserId, apiRevokedToken, new AccessCheck(null, ResourceTypes.Domain, StandardResourceActions.CertificateDownload, identifier: "random.microsoft.com")); Assert.IsFalse(isAuthorized.IsSuccess, "Token should not have access (revoked)"); } From 924851b4d141e2de423570580cb5fbb91abcbcd2 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Wed, 22 Jan 2025 17:11:52 +0800 Subject: [PATCH 299/328] Access control: add access token admin methods, update role checks --- src/Certify.Client/CertifyApiClient.cs | 2 +- .../Management/Access/AccessControl.cs | 63 ++++++- .../Management/Access/IAccessControl.cs | 11 +- .../CertifyManager.Maintenance.cs | 8 +- src/Certify.Models/Hub/AccessControl.cs | 1 + .../Certify.API.Public.cs | 172 ++++++++++++++++++ .../Controllers/AccessController.cs | 26 ++- src/Certify.SourceGenerators/ApiMethods.cs | 19 ++ .../DataStores/AccessControlDataStoreTests.cs | 2 +- .../Tests/AccessControlTests.cs | 84 ++++----- 10 files changed, 328 insertions(+), 60 deletions(-) diff --git a/src/Certify.Client/CertifyApiClient.cs b/src/Certify.Client/CertifyApiClient.cs index 31ce32b12..37b4671d3 100644 --- a/src/Certify.Client/CertifyApiClient.cs +++ b/src/Certify.Client/CertifyApiClient.cs @@ -751,7 +751,7 @@ public async Task> GetAccessSecurityPrinciples(AuthConte public async Task CheckApiTokenHasAccess(AccessToken token, AccessCheck check, AuthContext authContext = null) { - var result = await PostAsync("access/checkapitoken", new AccessTokenCheck { Check = check, Token = token }, authContext); + var result = await PostAsync("access/apitoken/check", new AccessTokenCheck { Check = check, Token = token }, authContext); return JsonConvert.DeserializeObject(await result.Content.ReadAsStringAsync()); } diff --git a/src/Certify.Core/Management/Access/AccessControl.cs b/src/Certify.Core/Management/Access/AccessControl.cs index e341a4c0b..aafb341bd 100644 --- a/src/Certify.Core/Management/Access/AccessControl.cs +++ b/src/Certify.Core/Management/Access/AccessControl.cs @@ -55,7 +55,7 @@ public async Task IsInitialized() } } - public async Task> GetRoles() + public async Task> GetRoles(string contextUserId) { return await _store.GetItems(nameof(Role)); } @@ -453,24 +453,52 @@ public string HashPassword(string password, string saltString = null) return hashed; } - public async Task AddRole(Role r) + public async Task AddRole(string contextUserId, Role r) { + if (!await IsPrincipleInRole(contextUserId, contextUserId, StandardRoles.Administrator.Id)) + { + await AuditWarning("User {contextUserId} attempted to add an role action without being in required role.", contextUserId); + return false; + } + await _store.Add(nameof(Role), r); + return true; } - public async Task AddAssignedRole(AssignedRole r) + public async Task AddAssignedRole(string contextUserId, AssignedRole r) { + if (!await IsPrincipleInRole(contextUserId, contextUserId, StandardRoles.Administrator.Id)) + { + await AuditWarning("User {contextUserId} attempted to add an assigned role without being in required role.", contextUserId); + return false; + } + await _store.Add(nameof(AssignedRole), r); + return true; } - public async Task AddAssignedAccessToken(AssignedAccessToken t) + public async Task AddAssignedAccessToken(string contextUserId, AssignedAccessToken t) { + if (!await IsPrincipleInRole(contextUserId, contextUserId, StandardRoles.Administrator.Id)) + { + await AuditWarning("User {contextUserId} attempted to add an assigned access token without being in required role.", contextUserId); + return false; + } + await _store.Add(nameof(AssignedAccessToken), t); + return true; } - public async Task AddResourceAction(ResourceAction action) + public async Task AddResourceAction(string contextUserId, ResourceAction action) { + if (!await IsPrincipleInRole(contextUserId, contextUserId, StandardRoles.Administrator.Id)) + { + await AuditWarning("User {contextUserId} attempted to add a resource action without being in required role.", contextUserId); + return false; + } + await _store.Add(nameof(ResourceAction), action); + return true; } public async Task> GetAssignedRoles(string contextUserId, string id) @@ -491,7 +519,6 @@ public async Task GetSecurityPrincipleRoleStatus(string contextUserI if (id != contextUserId && !await IsPrincipleInRole(contextUserId, contextUserId, StandardRoles.Administrator.Id)) { await AuditWarning("User {contextUserId} attempted to read role status role for [{id}] without being in required role.", contextUserId, id); - } var allAssignedRoles = await _store.GetItems(nameof(AssignedRole)); @@ -570,6 +597,30 @@ await GetSecurityPrincipleByUsername(contextUserId, passwordCheck.Username) : } } + public async Task> GetAccessTokens(string contextUserId) + { + if (!await IsPrincipleInRole(contextUserId, contextUserId, StandardRoles.Administrator.Id)) + { + await AuditWarning("User {contextUserId} attempted to list access tokens without being in required role.", contextUserId); + return []; + } + + return await _store.GetItems(nameof(AccessToken)); + } + + public async Task AddAccessToken(string contextUserId, AccessToken a) + { + if (!await IsPrincipleInRole(contextUserId, contextUserId, StandardRoles.Administrator.Id)) + { + await AuditWarning("User {contextUserId} attempted to add an access token without being in required role.", contextUserId); + return false; + } + + await _store.Add(nameof(AccessToken), a); + + return true; + } + public string GetSHA256Hash(string val) { using (var sha256Hash = SHA256.Create()) diff --git a/src/Certify.Core/Management/Access/IAccessControl.cs b/src/Certify.Core/Management/Access/IAccessControl.cs index db88abc22..fae63f52a 100644 --- a/src/Certify.Core/Management/Access/IAccessControl.cs +++ b/src/Certify.Core/Management/Access/IAccessControl.cs @@ -16,7 +16,7 @@ public interface IAccessControl /// Get the list of standard roles built-in to the system /// /// - Task> GetRoles(); + Task> GetRoles(string contextUserId); Task IsSecurityPrincipleAuthorised(string contextUserId, AccessCheck check); Task IsAccessTokenAuthorised(string contextUserId, AccessToken accessToken, AccessCheck check); Task IsPrincipleInRole(string contextUserId, string id, string roleId); @@ -27,9 +27,12 @@ public interface IAccessControl Task UpdateSecurityPrinciplePassword(string contextUserId, SecurityPrinciplePasswordUpdate passwordUpdate); Task CheckSecurityPrinciplePassword(string contextUserId, SecurityPrinciplePasswordCheck passwordCheck); - Task AddRole(Role role); - Task AddAssignedRole(AssignedRole assignedRole); - Task AddResourceAction(ResourceAction action); + Task AddRole(string contextUserId, Role role); + Task AddAssignedRole(string contextUserId, AssignedRole assignedRole); + Task AddResourceAction(string contextUserId, ResourceAction action); + + Task> GetAccessTokens(string contextUserId); + Task AddAccessToken(string contextUserId, AccessToken token); Task IsInitialized(); } } diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.Maintenance.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.Maintenance.cs index 148a386b8..12d626f0e 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.Maintenance.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.Maintenance.cs @@ -90,7 +90,7 @@ private static async Task BootstrapAdminUserAndRoles(IAccessControl access) foreach (var r in assignedRoles) { // add roles and policy assignments to store - await access.AddAssignedRole(r); + await access.AddAssignedRole(adminSp.Id, r); } } @@ -103,11 +103,13 @@ private static async Task UpdateStandardRoles(IAccessControl access) { // setup roles with policies + var adminSvcPrinciple = "admin_01"; + var actions = Policies.GetStandardResourceActions(); foreach (var action in actions) { - await access.AddResourceAction(action); + await access.AddResourceAction(adminSvcPrinciple, action); } // setup policies with actions @@ -126,7 +128,7 @@ private static async Task UpdateStandardRoles(IAccessControl access) foreach (var r in roles) { // add roles and policy assignments to store - await access.AddRole(r); + await access.AddRole(adminSvcPrinciple, r); } } diff --git a/src/Certify.Models/Hub/AccessControl.cs b/src/Certify.Models/Hub/AccessControl.cs index 52966cb1b..06537cfc0 100644 --- a/src/Certify.Models/Hub/AccessControl.cs +++ b/src/Certify.Models/Hub/AccessControl.cs @@ -109,6 +109,7 @@ public class AccessToken : ConfigurationStoreItem public string TokenType { get; set; } = default!; public string Secret { get; set; } = default!; public string ClientId { get; set; } = default!; + public DateTimeOffset? DateCreated { get; set; } public DateTimeOffset? DateExpiry { get; set; } public DateTimeOffset? DateRevoked { get; set; } diff --git a/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs b/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs index 59a70f3b9..6d515a797 100644 --- a/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs +++ b/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs @@ -428,6 +428,178 @@ public virtual async System.Threading.Tasks.Task GetSecurityPrincipl } } + /// + /// Get list of API access tokens [Generated] + /// + /// OK + /// A server side error occurred. + public virtual System.Threading.Tasks.Task> GetAccessTokensAsync() + { + return GetAccessTokensAsync(System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Get list of API access tokens [Generated] + /// + /// OK + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task> GetAccessTokensAsync(System.Threading.CancellationToken cancellationToken) + { + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); + // Operation Path: "internal/v1/access/token" + urlBuilder_.Append("internal/v1/access/token"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync>(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + + /// + /// Add new access token [Generated] + /// + /// OK + /// A server side error occurred. + public virtual System.Threading.Tasks.Task AddAccessTokenAsync(AccessToken body) + { + return AddAccessTokenAsync(body, System.Threading.CancellationToken.None); + } + + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// Add new access token [Generated] + /// + /// OK + /// A server side error occurred. + public virtual async System.Threading.Tasks.Task AddAccessTokenAsync(AccessToken body, System.Threading.CancellationToken cancellationToken) + { + var client_ = _httpClient; + var disposeClient_ = false; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(body, JsonSerializerSettings); + var content_ = new System.Net.Http.StringContent(json_); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); + + var urlBuilder_ = new System.Text.StringBuilder(); + if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); + // Operation Path: "internal/v1/access/token" + urlBuilder_.Append("internal/v1/access/token"); + + PrepareRequest(client_, request_, urlBuilder_); + + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + var disposeResponse_ = true; + try + { + var headers_ = new System.Collections.Generic.Dictionary>(); + foreach (var item_ in response_.Headers) + headers_[item_.Key] = item_.Value; + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = (int)response_.StatusCode; + if (status_ == 200) + { + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; + } + else + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); + } + } + finally + { + if (disposeResponse_) + response_.Dispose(); + } + } + } + finally + { + if (disposeClient_) + client_.Dispose(); + } + } + /// /// Get list of available security principles [Generated] /// diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccessController.cs b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccessController.cs index d82c12cda..aae40af7b 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccessController.cs +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccessController.cs @@ -98,8 +98,7 @@ public async Task> GetSecurityPrinciples() public async Task> GetRoles() { var accessControl = await _certifyManager.GetCurrentAccessControl(); - var roles = await accessControl.GetRoles(); - return roles; + return await accessControl.GetRoles(GetContextUserId()); } [HttpPost, Route("securityprinciple/allowedaction/")] @@ -110,7 +109,7 @@ public async Task CheckSecurityPrincipleHasAccess(AccessCheck check) return await accessControl.IsSecurityPrincipleAuthorised(GetContextUserId(), check); } - [HttpPost, Route("checkapitoken/")] + [HttpPost, Route("apitoken/check/")] public async Task CheckApiTokenHasAccess(AccessTokenCheck tokenCheck) { var accessControl = await _certifyManager.GetCurrentAccessControl(); @@ -118,6 +117,27 @@ public async Task CheckSecurityPrincipleHasAccess(AccessCheck check) return await accessControl.IsAccessTokenAuthorised(GetContextUserId(), tokenCheck.Token, tokenCheck.Check); } + [HttpGet, Route("apitoken/list/")] + public async Task> GetAccessTokens() + { + var accessControl = await _certifyManager.GetCurrentAccessControl(); + + return await accessControl.GetAccessTokens(GetContextUserId()); + } + + [HttpPost, Route("apitoken/")] + public async Task AddAccessToken([FromBody] AccessToken token) + { + var accessControl = await _certifyManager.GetCurrentAccessControl(); + var addResultOk = await accessControl.AddAccessToken(GetContextUserId(), token); + + return new Models.Config.ActionResult + { + IsSuccess = addResultOk, + Message = addResultOk ? "Added" : "Failed to add" + }; + } + [HttpGet, Route("securityprinciple/{id}/assignedroles")] public async Task> GetSecurityPrincipleAssignedRoles(string id) { diff --git a/src/Certify.SourceGenerators/ApiMethods.cs b/src/Certify.SourceGenerators/ApiMethods.cs index ab0f4a2fa..4329fce8a 100644 --- a/src/Certify.SourceGenerators/ApiMethods.cs +++ b/src/Certify.SourceGenerators/ApiMethods.cs @@ -72,6 +72,25 @@ public static List GetApiDefinitions() ServiceAPIRoute = "access/roles", ReturnType = "ICollection" }, + new() { + OperationName = "GetAccessTokens", + OperationMethod = HttpGet, + Comment = "Get list of API access tokens", + PublicAPIController = "Access", + PublicAPIRoute = "token", + ServiceAPIRoute = "access/apitoken/list", + ReturnType = "ICollection" + }, + new() { + OperationName = "AddAccessToken", + OperationMethod = HttpPost, + Comment = "Add new access token", + PublicAPIController = "Access", + PublicAPIRoute = "token", + ServiceAPIRoute = "access/apitoken", + ReturnType = "Models.Config.ActionResult", + Params = new Dictionary{{"token", "Certify.Models.Hub.AccessToken" } } + }, new() { OperationName = "GetSecurityPrinciples", diff --git a/src/Certify.Tests/Certify.Core.Tests.Integration/DataStores/AccessControlDataStoreTests.cs b/src/Certify.Tests/Certify.Core.Tests.Integration/DataStores/AccessControlDataStoreTests.cs index b5ec84d6d..697a14004 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Integration/DataStores/AccessControlDataStoreTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Integration/DataStores/AccessControlDataStoreTests.cs @@ -164,7 +164,7 @@ public async Task TestStoreGeneralAccessControl(string storeType) await access.AddSecurityPrinciple(adminSp.Id, adminSp, bypassIntegrityCheck: true); - await access.AddAssignedRole(new AssignedRole { Id = new Guid().ToString(), SecurityPrincipleId = adminSp.Id, RoleId = StandardRoles.Administrator.Id }); + await access.AddAssignedRole(adminSp.Id, new AssignedRole { Id = new Guid().ToString(), SecurityPrincipleId = adminSp.Id, RoleId = StandardRoles.Administrator.Id }); // add second security principle, bypass role check as this is just a data store test var added = await access.AddSecurityPrinciple(adminSp.Id, consumerSp, bypassIntegrityCheck: true); diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/AccessControlTests.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/AccessControlTests.cs index 4f985fd0f..2844c6899 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/AccessControlTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/AccessControlTests.cs @@ -225,7 +225,7 @@ public async Task TestAddGetAssignedRoles() // Setup security principle actions var actions = Policies.GetStandardResourceActions().FindAll(a => a.ResourceType == ResourceTypes.System); - actions.ForEach(async a => await access.AddResourceAction(a)); + actions.ForEach(async a => await access.AddResourceAction(contextUserId, a)); // Setup policy with actions and add policy to store var policy = Policies.GetStandardPolicies().Find(p => p.Id == StandardPolicies.AccessAdmin); @@ -233,11 +233,11 @@ public async Task TestAddGetAssignedRoles() // Setup and add roles and policy assignments to store var role = Policies.GetStandardRoles().Find(r => r.Id == StandardRoles.Administrator.Id); - await access.AddRole(role); + await access.AddRole(contextUserId, role); // Assign security principles to roles and add roles and policy assignments to store var assignedRoles = new List { TestAssignedRoles.Admin, TestAssignedRoles.TestAdmin }; - assignedRoles.ForEach(async r => await access.AddAssignedRole(r)); + assignedRoles.ForEach(async r => await access.AddAssignedRole(contextUserId, r)); // Validate AssignedRole list returned by AccessControl.GetAssignedRoles() foreach (var assignedRole in assignedRoles) @@ -271,7 +271,7 @@ public async Task TestAddResourcePolicyNoRoles() // Setup security principle actions var actions = Policies.GetStandardResourceActions().FindAll(a => a.ResourceType == ResourceTypes.System); - actions.ForEach(async a => await access.AddResourceAction(a)); + actions.ForEach(async a => await access.AddResourceAction(contextUserId, a)); // Setup policy with actions and add policy to store var policy = Policies.GetStandardPolicies().Find(p => p.Id == StandardPolicies.AccessAdmin); @@ -291,7 +291,7 @@ public async Task TestUpdateSecurityPrinciple() // Setup security principle actions var actions = Policies.GetStandardResourceActions().FindAll(a => a.ResourceType == ResourceTypes.System); - actions.ForEach(async a => await access.AddResourceAction(a)); + actions.ForEach(async a => await access.AddResourceAction(contextUserId, a)); // Setup policy with actions and add policy to store var policy = Policies.GetStandardPolicies().Find(p => p.Id == StandardPolicies.AccessAdmin); @@ -299,11 +299,11 @@ public async Task TestUpdateSecurityPrinciple() // Setup and add roles and policy assignments to store var role = Policies.GetStandardRoles().Find(r => r.Id == StandardRoles.Administrator.Id); - await access.AddRole(role); + await access.AddRole(contextUserId, role); // Assign security principles to roles and add roles and policy assignments to store var assignedRoles = new List { TestAssignedRoles.Admin, TestAssignedRoles.TestAdmin }; - assignedRoles.ForEach(async r => await access.AddAssignedRole(r)); + assignedRoles.ForEach(async r => await access.AddAssignedRole(contextUserId, r)); // Validate email of SecurityPrinciple object returned by AccessControl.GetSecurityPrinciple() before update var storedSecurityPrinciple = await access.GetSecurityPrinciple(contextUserId, adminSecurityPrinciples[0].Id); @@ -355,7 +355,7 @@ public async Task TestUpdateSecurityPrincipleBadUpdate() // Setup security principle actions var actions = Policies.GetStandardResourceActions().FindAll(a => a.ResourceType == ResourceTypes.System); - actions.ForEach(async a => await access.AddResourceAction(a)); + actions.ForEach(async a => await access.AddResourceAction(contextUserId, a)); // Setup policy with actions and add policy to store var policy = Policies.GetStandardPolicies().Find(p => p.Id == StandardPolicies.AccessAdmin); @@ -363,11 +363,11 @@ public async Task TestUpdateSecurityPrincipleBadUpdate() // Setup and add roles and policy assignments to store var role = Policies.GetStandardRoles().Find(r => r.Id == StandardRoles.Administrator.Id); - await access.AddRole(role); + await access.AddRole(contextUserId, role); // Assign security principles to roles and add roles and policy assignments to store var assignedRoles = new List { TestAssignedRoles.Admin, TestAssignedRoles.TestAdmin }; - assignedRoles.ForEach(async r => await access.AddAssignedRole(r)); + assignedRoles.ForEach(async r => await access.AddAssignedRole(contextUserId, r)); // Validate email of SecurityPrinciple object returned by AccessControl.GetSecurityPrinciple() before update var storedSecurityPrinciple = await access.GetSecurityPrinciple(contextUserId, adminSecurityPrinciples[0].Id); @@ -392,7 +392,7 @@ public async Task TestUpdateSecurityPrinciplePassword() // Setup security principle actions var actions = Policies.GetStandardResourceActions().FindAll(a => a.ResourceType == ResourceTypes.System); - actions.ForEach(async a => await access.AddResourceAction(a)); + actions.ForEach(async a => await access.AddResourceAction(contextUserId, a)); // Setup policy with actions and add policy to store var policy = Policies.GetStandardPolicies().Find(p => p.Id == StandardPolicies.AccessAdmin); @@ -400,11 +400,11 @@ public async Task TestUpdateSecurityPrinciplePassword() // Setup and add roles and policy assignments to store var role = Policies.GetStandardRoles().Find(r => r.Id == StandardRoles.Administrator.Id); - await access.AddRole(role); + await access.AddRole(contextUserId, role); // Assign security principles to roles and add roles and policy assignments to store var assignedRoles = new List { TestAssignedRoles.Admin, TestAssignedRoles.TestAdmin }; - assignedRoles.ForEach(async r => await access.AddAssignedRole(r)); + assignedRoles.ForEach(async r => await access.AddAssignedRole(contextUserId, r)); // Validate password of SecurityPrinciple object returned by AccessControl.GetSecurityPrinciple() before update var storedSecurityPrinciple = await access.GetSecurityPrinciple(contextUserId, adminSecurityPrinciples[0].Id); @@ -454,7 +454,7 @@ public async Task TestUpdateSecurityPrinciplePasswordBadPassword() // Setup security principle actions var actions = Policies.GetStandardResourceActions().FindAll(a => a.ResourceType == ResourceTypes.System); - actions.ForEach(async a => await access.AddResourceAction(a)); + actions.ForEach(async a => await access.AddResourceAction(contextUserId, a)); // Setup policy with actions and add policy to store var policy = Policies.GetStandardPolicies().Find(p => p.Id == StandardPolicies.AccessAdmin); @@ -462,11 +462,11 @@ public async Task TestUpdateSecurityPrinciplePasswordBadPassword() // Setup and add roles and policy assignments to store var role = Policies.GetStandardRoles().Find(r => r.Id == StandardRoles.Administrator.Id); - await access.AddRole(role); + await access.AddRole(contextUserId, role); // Assign security principles to roles and add roles and policy assignments to store var assignedRoles = new List { TestAssignedRoles.Admin, TestAssignedRoles.TestAdmin }; - assignedRoles.ForEach(async r => await access.AddAssignedRole(r)); + assignedRoles.ForEach(async r => await access.AddAssignedRole(contextUserId, r)); // Update security principle in AccessControl with a new password, but wrong original password var newPassword = "GFEDCBA"; @@ -488,7 +488,7 @@ public async Task TestDeleteSecurityPrinciple() // Setup security principle actions var actions = Policies.GetStandardResourceActions().FindAll(a => a.ResourceType == ResourceTypes.System); - actions.ForEach(async a => await access.AddResourceAction(a)); + actions.ForEach(async a => await access.AddResourceAction(contextUserId, a)); // Setup policy with actions and add policy to store var policy = Policies.GetStandardPolicies().Find(p => p.Id == StandardPolicies.AccessAdmin); @@ -496,11 +496,11 @@ public async Task TestDeleteSecurityPrinciple() // Setup and add roles and policy assignments to store var role = Policies.GetStandardRoles().Find(r => r.Id == StandardRoles.Administrator.Id); - await access.AddRole(role); + await access.AddRole(contextUserId, role); // Assign security principles to roles and add roles and policy assignments to store var assignedRoles = new List { TestAssignedRoles.Admin, TestAssignedRoles.TestAdmin }; - assignedRoles.ForEach(async r => await access.AddAssignedRole(r)); + assignedRoles.ForEach(async r => await access.AddAssignedRole(contextUserId, r)); // Validate SecurityPrinciple object returned by AccessControl.GetSecurityPrinciple() before delete is not null var storedSecurityPrinciple = await access.GetSecurityPrinciple(contextUserId, adminSecurityPrinciples[0].Id); @@ -546,7 +546,7 @@ public async Task TestDeleteSecurityPrincipleSelfDeletion() // Setup security principle actions var actions = Policies.GetStandardResourceActions().FindAll(a => a.ResourceType == ResourceTypes.System); - actions.ForEach(async a => await access.AddResourceAction(a)); + actions.ForEach(async a => await access.AddResourceAction(contextUserId, a)); // Setup policy with actions and add policy to store var policy = Policies.GetStandardPolicies().Find(p => p.Id == StandardPolicies.AccessAdmin); @@ -554,11 +554,11 @@ public async Task TestDeleteSecurityPrincipleSelfDeletion() // Setup and add roles and policy assignments to store var role = Policies.GetStandardRoles().Find(r => r.Id == StandardRoles.Administrator.Id); - await access.AddRole(role); + await access.AddRole(contextUserId, role); // Assign security principles to roles and add roles and policy assignments to store var assignedRoles = new List { TestAssignedRoles.Admin, TestAssignedRoles.TestAdmin }; - assignedRoles.ForEach(async r => await access.AddAssignedRole(r)); + assignedRoles.ForEach(async r => await access.AddAssignedRole(contextUserId, r)); // Validate SecurityPrinciple object returned by AccessControl.GetSecurityPrinciple() before delete is not null var storedSecurityPrinciple = await access.GetSecurityPrinciple(contextUserId, adminSecurityPrinciples[1].Id); @@ -583,7 +583,7 @@ public async Task TestDeleteSecurityPrincipleBadId() // Setup security principle actions var actions = Policies.GetStandardResourceActions().FindAll(a => a.ResourceType == ResourceTypes.System); - actions.ForEach(async a => await access.AddResourceAction(a)); + actions.ForEach(async a => await access.AddResourceAction(contextUserId, a)); // Setup policy with actions and add policy to store var policy = Policies.GetStandardPolicies().Find(p => p.Id == StandardPolicies.AccessAdmin); @@ -591,11 +591,11 @@ public async Task TestDeleteSecurityPrincipleBadId() // Setup and add roles and policy assignments to store var role = Policies.GetStandardRoles().Find(r => r.Id == StandardRoles.Administrator.Id); - await access.AddRole(role); + await access.AddRole(contextUserId, role); // Assign security principles to roles and add roles and policy assignments to store var assignedRoles = new List { TestAssignedRoles.Admin, TestAssignedRoles.TestAdmin }; - assignedRoles.ForEach(async r => await access.AddAssignedRole(r)); + assignedRoles.ForEach(async r => await access.AddAssignedRole(contextUserId, r)); // Validate SecurityPrinciple object returned by AccessControl.GetSecurityPrinciple() before delete is not null var storedSecurityPrinciple = await access.GetSecurityPrinciple(contextUserId, adminSecurityPrinciples[1].Id); @@ -620,7 +620,7 @@ public async Task TestIsPrincipleInRole() // Setup security principle actions var actions = Policies.GetStandardResourceActions().FindAll(a => a.ResourceType == ResourceTypes.System); - actions.ForEach(async a => await access.AddResourceAction(a)); + actions.ForEach(async a => await access.AddResourceAction(contextUserId, a)); // Setup policy with actions and add policy to store var policy = Policies.GetStandardPolicies().Find(p => p.Id == StandardPolicies.AccessAdmin); @@ -628,11 +628,11 @@ public async Task TestIsPrincipleInRole() // Setup and add roles and policy assignments to store var role = Policies.GetStandardRoles().Find(r => r.Id == StandardRoles.Administrator.Id); - await access.AddRole(role); + await access.AddRole(contextUserId, role); // Assign security principles to roles and add roles and policy assignments to store var assignedRoles = new List { TestAssignedRoles.Admin, TestAssignedRoles.TestAdmin }; - assignedRoles.ForEach(async r => await access.AddAssignedRole(r)); + assignedRoles.ForEach(async r => await access.AddAssignedRole(contextUserId, r)); // Validate specified admin user is a principle role bool hasAccess; @@ -654,7 +654,7 @@ public async Task TestDomainAuth() _ = await access.AddSecurityPrinciple(contextUserId, TestSecurityPrinciples.DevopsUser, bypassIntegrityCheck: true); // Setup security principle actions - await access.AddResourceAction(Policies.GetStandardResourceActions().Find(r => r.Id == StandardResourceActions.CertificateDownload)); + await access.AddResourceAction(contextUserId, Policies.GetStandardResourceActions().Find(r => r.Id == StandardResourceActions.CertificateDownload)); // Setup policy with actions and add policy to store var policy = Policies.GetStandardPolicies().Find(p => p.Id == StandardPolicies.CertificateConsumer); @@ -662,10 +662,10 @@ public async Task TestDomainAuth() // Setup and add roles and policy assignments to store var role = Policies.GetStandardRoles().Find(r => r.Id == StandardRoles.CertificateConsumer.Id); - await access.AddRole(role); + await access.AddRole(contextUserId, role); // Assign security principles to roles and add roles and policy assignments to store - await access.AddAssignedRole(TestAssignedRoles.DevopsUserDomainConsumer); // devops user in consumer role for a specific domain + await access.AddAssignedRole(contextUserId, TestAssignedRoles.DevopsUserDomainConsumer); // devops user in consumer role for a specific domain // Validate user can consume a cert for a given domain var isAuthorised = await access.IsSecurityPrincipleAuthorised(contextUserId, new AccessCheck(TestSecurityPrinciples.DevopsAppDomainConsumer.Id, ResourceTypes.Domain, StandardResourceActions.CertificateDownload, identifier: "www.example.com")); @@ -683,7 +683,7 @@ public async Task TestWildcardDomainAuth() _ = await access.AddSecurityPrinciple(contextUserId, TestSecurityPrinciples.DevopsUser, bypassIntegrityCheck: true); // Setup security principle actions - await access.AddResourceAction(Policies.GetStandardResourceActions().Find(r => r.Id == StandardResourceActions.CertificateDownload)); + await access.AddResourceAction(contextUserId, Policies.GetStandardResourceActions().Find(r => r.Id == StandardResourceActions.CertificateDownload)); // Setup policy with actions and add policy to store var policy = Policies.GetStandardPolicies().Find(p => p.Id == StandardPolicies.CertificateConsumer); @@ -691,10 +691,10 @@ public async Task TestWildcardDomainAuth() // Setup and add roles and policy assignments to store var role = Policies.GetStandardRoles().Find(r => r.Id == StandardRoles.CertificateConsumer.Id); - await access.AddRole(role); + await access.AddRole(contextUserId, role); // Assign security principles to roles and add roles and policy assignments to store - await access.AddAssignedRole(TestAssignedRoles.DevopsUserWildcardDomainConsumer); // devops user in consumer role for a wildcard domain + await access.AddAssignedRole(contextUserId, TestAssignedRoles.DevopsUserWildcardDomainConsumer); // devops user in consumer role for a wildcard domain // Validate user can consume any subdomain via a granted wildcard var isAuthorised = await access.IsSecurityPrincipleAuthorised(contextUserId, new AccessCheck(TestSecurityPrinciples.DevopsUser.Id, ResourceTypes.Domain, StandardResourceActions.CertificateDownload, identifier: "random.microsoft.com")); @@ -716,7 +716,7 @@ public async Task TestRandomUserAuth() _ = await access.AddSecurityPrinciple(contextUserId, TestSecurityPrinciples.DevopsUser, bypassIntegrityCheck: true); // Setup security principle actions - await access.AddResourceAction(Policies.GetStandardResourceActions().Find(r => r.Id == StandardResourceActions.CertificateDownload)); + await access.AddResourceAction(contextUserId, Policies.GetStandardResourceActions().Find(r => r.Id == StandardResourceActions.CertificateDownload)); // Setup policy with actions and add policy to store var policy = Policies.GetStandardPolicies().Find(p => p.Id == StandardPolicies.CertificateConsumer); @@ -724,10 +724,10 @@ public async Task TestRandomUserAuth() // Setup and add roles and policy assignments to store var role = Policies.GetStandardRoles().Find(r => r.Id == StandardRoles.CertificateConsumer.Id); - await access.AddRole(role); + await access.AddRole(contextUserId, role); // Assign security principles to roles and add roles and policy assignments to store - await access.AddAssignedRole(TestAssignedRoles.DevopsUserWildcardDomainConsumer); // devops user in consumer role for a wildcard domain + await access.AddAssignedRole(contextUserId, TestAssignedRoles.DevopsUserWildcardDomainConsumer); // devops user in consumer role for a wildcard domain // Validate that random user should not be authorised var isAuthorised = await access.IsSecurityPrincipleAuthorised(contextUserId, new AccessCheck("randomuser", ResourceTypes.Domain, StandardResourceActions.CertificateDownload, identifier: "random.microsoft.com")); @@ -761,13 +761,13 @@ public async Task TestUserAPIToken() // allow test admin to perform access checks var assignedRoles = new List { TestAssignedRoles.TestAdmin }; - assignedRoles.ForEach(async r => await access.AddAssignedRole(r)); + assignedRoles.ForEach(async r => await access.AddAssignedRole(contextUserId, r)); // Add test devops user security principle _ = await access.AddSecurityPrinciple(contextUserId, TestSecurityPrinciples.DevopsUser, bypassIntegrityCheck: true); // Setup security principle actions - await access.AddResourceAction(Policies.GetStandardResourceActions().Find(r => r.Id == StandardResourceActions.CertificateDownload)); + await access.AddResourceAction(contextUserId, Policies.GetStandardResourceActions().Find(r => r.Id == StandardResourceActions.CertificateDownload)); // Setup policy with actions and add policy to store var policy = Policies.GetStandardPolicies().Find(p => p.Id == StandardPolicies.CertificateConsumer); @@ -775,10 +775,10 @@ public async Task TestUserAPIToken() // Setup and add roles and policy assignments to store var role = Policies.GetStandardRoles().Find(r => r.Id == StandardRoles.CertificateConsumer.Id); - await access.AddRole(role); + await access.AddRole(contextUserId, role); // Assign security principles to roles and add roles and policy assignments to store - await access.AddAssignedRole(TestAssignedRoles.DevopsUserWildcardDomainConsumer); // devops user in consumer role for a wildcard domain + await access.AddAssignedRole(contextUserId, TestAssignedRoles.DevopsUserWildcardDomainConsumer); // devops user in consumer role for a wildcard domain var assignedRolesForDevopsUser = await access.GetAssignedRoles(contextUserId, TestSecurityPrinciples.DevopsUser.Id); @@ -795,7 +795,7 @@ public async Task TestUserAPIToken() ScopedAssignedRoles = [assignedRolesForDevopsUser.First(r => r.RoleId == StandardRoles.CertificateConsumer.Id).Id] }; - await access.AddAssignedAccessToken(assignedToken); + await access.AddAssignedAccessToken(contextUserId, assignedToken); var isAuthorized = await access.IsAccessTokenAuthorised(contextUserId, apiToken, new AccessCheck(null, ResourceTypes.Domain, StandardResourceActions.CertificateDownload, identifier: "random.microsoft.com")); Assert.IsTrue(isAuthorized.IsSuccess, "Token should have access"); From a3130c7cf1611bd82117e613869e1fcc7f15468a Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Wed, 22 Jan 2025 17:42:07 +0800 Subject: [PATCH 300/328] Package updates --- src/Certify.Client/Certify.Client.csproj | 2 +- src/Certify.Models/Certify.Models.csproj | 2 +- .../DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj | 2 +- .../Certify.Core.Tests.Integration.csproj | 4 ++-- .../Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj | 6 +++--- .../Certify.Service.Tests.Integration.csproj | 4 ++-- .../Certify.UI.Tests.Integration.csproj | 4 ++-- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Certify.Client/Certify.Client.csproj b/src/Certify.Client/Certify.Client.csproj index bb63790e9..3c594a1eb 100644 --- a/src/Certify.Client/Certify.Client.csproj +++ b/src/Certify.Client/Certify.Client.csproj @@ -18,7 +18,7 @@ - + diff --git a/src/Certify.Models/Certify.Models.csproj b/src/Certify.Models/Certify.Models.csproj index a0ea409b2..de7677b82 100644 --- a/src/Certify.Models/Certify.Models.csproj +++ b/src/Certify.Models/Certify.Models.csproj @@ -21,7 +21,7 @@ all runtime; build; native; contentfiles; analyzers - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj index caace9741..197a33aaf 100644 --- a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj +++ b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj b/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj index 802464715..af13506a8 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj @@ -76,8 +76,8 @@ - - + + diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj index 62524eccf..af516bcee 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj @@ -100,7 +100,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -109,8 +109,8 @@ - - + + diff --git a/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj b/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj index 9a1bbd784..d320c4a7b 100644 --- a/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj @@ -62,8 +62,8 @@ - - + + diff --git a/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj b/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj index b452c1e2e..eb28a1791 100644 --- a/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj @@ -58,8 +58,8 @@ - - + + From 538c81c6aeb6bf1368f946f11d1066df96b1904e Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Thu, 23 Jan 2025 12:53:22 +0800 Subject: [PATCH 301/328] Implement cache control headers for cert download API --- .../Controllers/v1/CertificateController.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs index 3a60dac5b..fbf9542d7 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs @@ -113,7 +113,7 @@ public async Task Download(string instanceId, string managedCertI return new NotFoundResult(); } - if (managedCert != null && managedCert.DateRenewed == null) + if (managedCert.DateRenewed == null) { // item exists but a cert is not yet available, set Retry-After header in RC1123 date format var nextAttempt = managedCert.DateNextScheduledRenewalAttempt ?? DateTimeOffset.UtcNow.AddHours(1); @@ -121,9 +121,16 @@ public async Task Download(string instanceId, string managedCertI } var headers = Request.GetTypedHeaders(); + + // allow client to skip the download by sending an If-Modified-Since http header. If not renewed since that date return 304 Not Modified. if (headers.IfModifiedSince.HasValue && headers.IfModifiedSince.Value > managedCert.DateRenewed) { - // if item has not been modified since the provided date return 304 not modified + return StatusCode((int)HttpStatusCode.NotModified); + } + + // allow client to skip the download by sending an If-None-Match header with a quote "" of the cert they currently have. wildcard/weak tags not supported. + if (headers.IfNoneMatch.Any(etag => string.Equals(etag.Tag.ToString().Replace("\"", ""), managedCert.CertificateThumbprintHash, StringComparison.InvariantCultureIgnoreCase))) + { return StatusCode((int)HttpStatusCode.NotModified); } From f875a66357f05142d8852b7bfcf38aaeadb3d454 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Thu, 23 Jan 2025 12:54:00 +0800 Subject: [PATCH 302/328] Update assigned access token API --- .../Management/Access/AccessControl.cs | 24 ++++----------- .../Management/Access/IAccessControl.cs | 4 +-- .../Certify.API.Public.cs | 30 +++++++++---------- .../Controllers/AccessController.cs | 12 ++++---- src/Certify.SourceGenerators/ApiMethods.cs | 20 ++++++------- 5 files changed, 39 insertions(+), 51 deletions(-) diff --git a/src/Certify.Core/Management/Access/AccessControl.cs b/src/Certify.Core/Management/Access/AccessControl.cs index aafb341bd..8f7325eaa 100644 --- a/src/Certify.Core/Management/Access/AccessControl.cs +++ b/src/Certify.Core/Management/Access/AccessControl.cs @@ -477,18 +477,6 @@ public async Task AddAssignedRole(string contextUserId, AssignedRole r) return true; } - public async Task AddAssignedAccessToken(string contextUserId, AssignedAccessToken t) - { - if (!await IsPrincipleInRole(contextUserId, contextUserId, StandardRoles.Administrator.Id)) - { - await AuditWarning("User {contextUserId} attempted to add an assigned access token without being in required role.", contextUserId); - return false; - } - - await _store.Add(nameof(AssignedAccessToken), t); - return true; - } - public async Task AddResourceAction(string contextUserId, ResourceAction action) { if (!await IsPrincipleInRole(contextUserId, contextUserId, StandardRoles.Administrator.Id)) @@ -597,26 +585,26 @@ await GetSecurityPrincipleByUsername(contextUserId, passwordCheck.Username) : } } - public async Task> GetAccessTokens(string contextUserId) + public async Task> GetAssignedAccessTokens(string contextUserId) { if (!await IsPrincipleInRole(contextUserId, contextUserId, StandardRoles.Administrator.Id)) { - await AuditWarning("User {contextUserId} attempted to list access tokens without being in required role.", contextUserId); + await AuditWarning("User {contextUserId} attempted to list assigned access tokens without being in required role.", contextUserId); return []; } - return await _store.GetItems(nameof(AccessToken)); + return await _store.GetItems(nameof(AssignedAccessToken)); } - public async Task AddAccessToken(string contextUserId, AccessToken a) + public async Task AddAssignedAccessToken(string contextUserId, AssignedAccessToken a) { if (!await IsPrincipleInRole(contextUserId, contextUserId, StandardRoles.Administrator.Id)) { - await AuditWarning("User {contextUserId} attempted to add an access token without being in required role.", contextUserId); + await AuditWarning("User {contextUserId} attempted to add an assigned access token without being in required role.", contextUserId); return false; } - await _store.Add(nameof(AccessToken), a); + await _store.Add(nameof(AssignedAccessToken), a); return true; } diff --git a/src/Certify.Core/Management/Access/IAccessControl.cs b/src/Certify.Core/Management/Access/IAccessControl.cs index fae63f52a..3468c7930 100644 --- a/src/Certify.Core/Management/Access/IAccessControl.cs +++ b/src/Certify.Core/Management/Access/IAccessControl.cs @@ -31,8 +31,8 @@ public interface IAccessControl Task AddAssignedRole(string contextUserId, AssignedRole assignedRole); Task AddResourceAction(string contextUserId, ResourceAction action); - Task> GetAccessTokens(string contextUserId); - Task AddAccessToken(string contextUserId, AccessToken token); + Task> GetAssignedAccessTokens(string contextUserId); + Task AddAssignedAccessToken(string contextUserId, AssignedAccessToken token); Task IsInitialized(); } } diff --git a/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs b/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs index 6d515a797..f935f2602 100644 --- a/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs +++ b/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs @@ -429,22 +429,22 @@ public virtual async System.Threading.Tasks.Task GetSecurityPrincipl } /// - /// Get list of API access tokens [Generated] + /// Get list of API assigned access tokens [Generated] /// /// OK /// A server side error occurred. - public virtual System.Threading.Tasks.Task> GetAccessTokensAsync() + public virtual System.Threading.Tasks.Task> GetAssignedAccessTokensAsync() { - return GetAccessTokensAsync(System.Threading.CancellationToken.None); + return GetAssignedAccessTokensAsync(System.Threading.CancellationToken.None); } /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Get list of API access tokens [Generated] + /// Get list of API assigned access tokens [Generated] /// /// OK /// A server side error occurred. - public virtual async System.Threading.Tasks.Task> GetAccessTokensAsync(System.Threading.CancellationToken cancellationToken) + public virtual async System.Threading.Tasks.Task> GetAssignedAccessTokensAsync(System.Threading.CancellationToken cancellationToken) { var client_ = _httpClient; var disposeClient_ = false; @@ -457,8 +457,8 @@ public virtual async System.Threading.Tasks.Task GetSecurityPrincipl var urlBuilder_ = new System.Text.StringBuilder(); if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); - // Operation Path: "internal/v1/access/token" - urlBuilder_.Append("internal/v1/access/token"); + // Operation Path: "internal/v1/access/assignedtoken" + urlBuilder_.Append("internal/v1/access/assignedtoken"); PrepareRequest(client_, request_, urlBuilder_); @@ -485,7 +485,7 @@ public virtual async System.Threading.Tasks.Task GetSecurityPrincipl var status_ = (int)response_.StatusCode; if (status_ == 200) { - var objectResponse_ = await ReadObjectResponseAsync>(response_, headers_, cancellationToken).ConfigureAwait(false); + var objectResponse_ = await ReadObjectResponseAsync>(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); @@ -513,22 +513,22 @@ public virtual async System.Threading.Tasks.Task GetSecurityPrincipl } /// - /// Add new access token [Generated] + /// Add new assigned access token [Generated] /// /// OK /// A server side error occurred. - public virtual System.Threading.Tasks.Task AddAccessTokenAsync(AccessToken body) + public virtual System.Threading.Tasks.Task AddAssignedAccessTokenAsync(AssignedAccessToken body) { - return AddAccessTokenAsync(body, System.Threading.CancellationToken.None); + return AddAssignedAccessTokenAsync(body, System.Threading.CancellationToken.None); } /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Add new access token [Generated] + /// Add new assigned access token [Generated] /// /// OK /// A server side error occurred. - public virtual async System.Threading.Tasks.Task AddAccessTokenAsync(AccessToken body, System.Threading.CancellationToken cancellationToken) + public virtual async System.Threading.Tasks.Task AddAssignedAccessTokenAsync(AssignedAccessToken body, System.Threading.CancellationToken cancellationToken) { var client_ = _httpClient; var disposeClient_ = false; @@ -545,8 +545,8 @@ public virtual async System.Threading.Tasks.Task AddAccessTokenAsy var urlBuilder_ = new System.Text.StringBuilder(); if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); - // Operation Path: "internal/v1/access/token" - urlBuilder_.Append("internal/v1/access/token"); + // Operation Path: "internal/v1/access/assignedtoken" + urlBuilder_.Append("internal/v1/access/assignedtoken"); PrepareRequest(client_, request_, urlBuilder_); diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccessController.cs b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccessController.cs index aae40af7b..0eccf5ea8 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccessController.cs +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccessController.cs @@ -117,19 +117,19 @@ public async Task CheckSecurityPrincipleHasAccess(AccessCheck check) return await accessControl.IsAccessTokenAuthorised(GetContextUserId(), tokenCheck.Token, tokenCheck.Check); } - [HttpGet, Route("apitoken/list/")] - public async Task> GetAccessTokens() + [HttpGet, Route("assignedtoken/list/")] + public async Task> GetAssignedAccessTokens() { var accessControl = await _certifyManager.GetCurrentAccessControl(); - return await accessControl.GetAccessTokens(GetContextUserId()); + return await accessControl.GetAssignedAccessTokens(GetContextUserId()); } - [HttpPost, Route("apitoken/")] - public async Task AddAccessToken([FromBody] AccessToken token) + [HttpPost, Route("assignedtoken/")] + public async Task AddAAssignedccessToken([FromBody] AssignedAccessToken token) { var accessControl = await _certifyManager.GetCurrentAccessControl(); - var addResultOk = await accessControl.AddAccessToken(GetContextUserId(), token); + var addResultOk = await accessControl.AddAssignedAccessToken(GetContextUserId(), token); return new Models.Config.ActionResult { diff --git a/src/Certify.SourceGenerators/ApiMethods.cs b/src/Certify.SourceGenerators/ApiMethods.cs index 4329fce8a..b504a6566 100644 --- a/src/Certify.SourceGenerators/ApiMethods.cs +++ b/src/Certify.SourceGenerators/ApiMethods.cs @@ -73,23 +73,23 @@ public static List GetApiDefinitions() ReturnType = "ICollection" }, new() { - OperationName = "GetAccessTokens", + OperationName = "GetAssignedAccessTokens", OperationMethod = HttpGet, - Comment = "Get list of API access tokens", + Comment = "Get list of API assigned access tokens", PublicAPIController = "Access", - PublicAPIRoute = "token", - ServiceAPIRoute = "access/apitoken/list", - ReturnType = "ICollection" + PublicAPIRoute = "assignedtoken", + ServiceAPIRoute = "access/assignedtoken/list", + ReturnType = "ICollection" }, new() { - OperationName = "AddAccessToken", + OperationName = "AddAssignedAccessToken", OperationMethod = HttpPost, - Comment = "Add new access token", + Comment = "Add new assigned access token", PublicAPIController = "Access", - PublicAPIRoute = "token", - ServiceAPIRoute = "access/apitoken", + PublicAPIRoute = "assignedtoken", + ServiceAPIRoute = "access/assignedtoken", ReturnType = "Models.Config.ActionResult", - Params = new Dictionary{{"token", "Certify.Models.Hub.AccessToken" } } + Params = new Dictionary{{"token", "Certify.Models.Hub.AssignedAccessToken" } } }, new() { From c6439c3012964a766b95ffcda2cd1ebda9a81f31 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Thu, 23 Jan 2025 17:51:43 +0800 Subject: [PATCH 303/328] Access control: add further role checks and update tests --- .../Management/Access/AccessControl.cs | 17 +++--- .../Management/Access/IAccessControl.cs | 6 +-- src/Certify.Models/Hub/AccessControl.cs | 7 ++- .../Tests/AccessControlTests.cs | 53 +++++++++++-------- 4 files changed, 48 insertions(+), 35 deletions(-) diff --git a/src/Certify.Core/Management/Access/AccessControl.cs b/src/Certify.Core/Management/Access/AccessControl.cs index 8f7325eaa..fb5fabcf0 100644 --- a/src/Certify.Core/Management/Access/AccessControl.cs +++ b/src/Certify.Core/Management/Access/AccessControl.cs @@ -453,9 +453,9 @@ public string HashPassword(string password, string saltString = null) return hashed; } - public async Task AddRole(string contextUserId, Role r) + public async Task AddRole(string contextUserId, Role r, bool bypassIntegrityCheck = false) { - if (!await IsPrincipleInRole(contextUserId, contextUserId, StandardRoles.Administrator.Id)) + if (!bypassIntegrityCheck && !await IsPrincipleInRole(contextUserId, contextUserId, StandardRoles.Administrator.Id)) { await AuditWarning("User {contextUserId} attempted to add an role action without being in required role.", contextUserId); return false; @@ -465,9 +465,9 @@ public async Task AddRole(string contextUserId, Role r) return true; } - public async Task AddAssignedRole(string contextUserId, AssignedRole r) + public async Task AddAssignedRole(string contextUserId, AssignedRole r, bool bypassIntegrityCheck = false) { - if (!await IsPrincipleInRole(contextUserId, contextUserId, StandardRoles.Administrator.Id)) + if (!bypassIntegrityCheck && !await IsPrincipleInRole(contextUserId, contextUserId, StandardRoles.Administrator.Id)) { await AuditWarning("User {contextUserId} attempted to add an assigned role without being in required role.", contextUserId); return false; @@ -477,9 +477,9 @@ public async Task AddAssignedRole(string contextUserId, AssignedRole r) return true; } - public async Task AddResourceAction(string contextUserId, ResourceAction action) + public async Task AddResourceAction(string contextUserId, ResourceAction action, bool bypassIntegrityCheck = false) { - if (!await IsPrincipleInRole(contextUserId, contextUserId, StandardRoles.Administrator.Id)) + if (!bypassIntegrityCheck && !await IsPrincipleInRole(contextUserId, contextUserId, StandardRoles.Administrator.Id)) { await AuditWarning("User {contextUserId} attempted to add a resource action without being in required role.", contextUserId); return false; @@ -494,7 +494,7 @@ public async Task> GetAssignedRoles(string contextUserId, str if (id != contextUserId && !await IsPrincipleInRole(contextUserId, contextUserId, StandardRoles.Administrator.Id)) { await AuditWarning("User {contextUserId} attempted to read assigned role for [{id}] without being in required role.", contextUserId, id); - return new List(); + return null; } var assignedRoles = await _store.GetItems(nameof(AssignedRole)); @@ -507,6 +507,7 @@ public async Task GetSecurityPrincipleRoleStatus(string contextUserI if (id != contextUserId && !await IsPrincipleInRole(contextUserId, contextUserId, StandardRoles.Administrator.Id)) { await AuditWarning("User {contextUserId} attempted to read role status role for [{id}] without being in required role.", contextUserId, id); + return null; } var allAssignedRoles = await _store.GetItems(nameof(AssignedRole)); @@ -590,7 +591,7 @@ public async Task> GetAssignedAccessTokens(string cont if (!await IsPrincipleInRole(contextUserId, contextUserId, StandardRoles.Administrator.Id)) { await AuditWarning("User {contextUserId} attempted to list assigned access tokens without being in required role.", contextUserId); - return []; + return null; } return await _store.GetItems(nameof(AssignedAccessToken)); diff --git a/src/Certify.Core/Management/Access/IAccessControl.cs b/src/Certify.Core/Management/Access/IAccessControl.cs index 3468c7930..ce6669e26 100644 --- a/src/Certify.Core/Management/Access/IAccessControl.cs +++ b/src/Certify.Core/Management/Access/IAccessControl.cs @@ -27,9 +27,9 @@ public interface IAccessControl Task UpdateSecurityPrinciplePassword(string contextUserId, SecurityPrinciplePasswordUpdate passwordUpdate); Task CheckSecurityPrinciplePassword(string contextUserId, SecurityPrinciplePasswordCheck passwordCheck); - Task AddRole(string contextUserId, Role role); - Task AddAssignedRole(string contextUserId, AssignedRole assignedRole); - Task AddResourceAction(string contextUserId, ResourceAction action); + Task AddRole(string contextUserId, Role role, bool bypassIntegrityCheck = false); + Task AddAssignedRole(string contextUserId, AssignedRole assignedRole, bool bypassIntegrityCheck = false); + Task AddResourceAction(string contextUserId, ResourceAction action, bool bypassIntegrityCheck = false); Task> GetAssignedAccessTokens(string contextUserId); Task AddAssignedAccessToken(string contextUserId, AssignedAccessToken token); diff --git a/src/Certify.Models/Hub/AccessControl.cs b/src/Certify.Models/Hub/AccessControl.cs index 06537cfc0..e7eefebae 100644 --- a/src/Certify.Models/Hub/AccessControl.cs +++ b/src/Certify.Models/Hub/AccessControl.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; namespace Certify.Models.Hub @@ -104,6 +104,11 @@ public class AccessTokenCheck public AccessToken Token { get; set; } public AccessCheck Check { get; set; } } + + public class AccessTokenTypes + { + public const string Simple = "simple"; + } public class AccessToken : ConfigurationStoreItem { public string TokenType { get; set; } = default!; diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/AccessControlTests.cs b/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/AccessControlTests.cs index 2844c6899..ee15f865f 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/AccessControlTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/Tests/AccessControlTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; @@ -229,15 +229,19 @@ public async Task TestAddGetAssignedRoles() // Setup policy with actions and add policy to store var policy = Policies.GetStandardPolicies().Find(p => p.Id == StandardPolicies.AccessAdmin); - _ = await access.AddResourcePolicy(contextUserId, policy, bypassIntegrityCheck: true); + var addPolicy = await access.AddResourcePolicy(contextUserId, policy, bypassIntegrityCheck: true); + + Assert.IsTrue(addPolicy, "Expected to add role"); // Setup and add roles and policy assignments to store var role = Policies.GetStandardRoles().Find(r => r.Id == StandardRoles.Administrator.Id); - await access.AddRole(contextUserId, role); + var addedRole = await access.AddRole(contextUserId, role, bypassIntegrityCheck: true); + + Assert.IsTrue(addedRole, "Expected to add role"); // Assign security principles to roles and add roles and policy assignments to store var assignedRoles = new List { TestAssignedRoles.Admin, TestAssignedRoles.TestAdmin }; - assignedRoles.ForEach(async r => await access.AddAssignedRole(contextUserId, r)); + assignedRoles.ForEach(async r => await access.AddAssignedRole(contextUserId, r, bypassIntegrityCheck: true)); // Validate AssignedRole list returned by AccessControl.GetAssignedRoles() foreach (var assignedRole in assignedRoles) @@ -256,6 +260,9 @@ public async Task TestGetAssignedRolesNoRoles() var adminSecurityPrinciples = new List { TestSecurityPrinciples.Admin, TestSecurityPrinciples.TestAdmin }; adminSecurityPrinciples.ForEach(async p => await access.AddSecurityPrinciple(contextUserId, p, bypassIntegrityCheck: true)); + // assigned admin role to TestAdmin (also the contextUserId) so they can check roles for the other admin user + await access.AddAssignedRole(TestSecurityPrinciples.TestAdmin.Id, TestAssignedRoles.TestAdmin, bypassIntegrityCheck: true); + // Validate AssignedRole list returned by AccessControl.GetAssignedRoles() var adminAssignedRoles = await access.GetAssignedRoles(contextUserId, adminSecurityPrinciples[0].Id); Assert.IsNotNull(adminAssignedRoles, "Expected list returned by AccessControl.GetAssignedRoles() to not be null"); @@ -303,7 +310,7 @@ public async Task TestUpdateSecurityPrinciple() // Assign security principles to roles and add roles and policy assignments to store var assignedRoles = new List { TestAssignedRoles.Admin, TestAssignedRoles.TestAdmin }; - assignedRoles.ForEach(async r => await access.AddAssignedRole(contextUserId, r)); + assignedRoles.ForEach(async r => await access.AddAssignedRole(contextUserId, r, bypassIntegrityCheck: true)); // Validate email of SecurityPrinciple object returned by AccessControl.GetSecurityPrinciple() before update var storedSecurityPrinciple = await access.GetSecurityPrinciple(contextUserId, adminSecurityPrinciples[0].Id); @@ -363,11 +370,11 @@ public async Task TestUpdateSecurityPrincipleBadUpdate() // Setup and add roles and policy assignments to store var role = Policies.GetStandardRoles().Find(r => r.Id == StandardRoles.Administrator.Id); - await access.AddRole(contextUserId, role); + await access.AddRole(contextUserId, role, bypassIntegrityCheck: true); // Assign security principles to roles and add roles and policy assignments to store var assignedRoles = new List { TestAssignedRoles.Admin, TestAssignedRoles.TestAdmin }; - assignedRoles.ForEach(async r => await access.AddAssignedRole(contextUserId, r)); + assignedRoles.ForEach(async r => await access.AddAssignedRole(contextUserId, r, bypassIntegrityCheck: true)); // Validate email of SecurityPrinciple object returned by AccessControl.GetSecurityPrinciple() before update var storedSecurityPrinciple = await access.GetSecurityPrinciple(contextUserId, adminSecurityPrinciples[0].Id); @@ -400,11 +407,11 @@ public async Task TestUpdateSecurityPrinciplePassword() // Setup and add roles and policy assignments to store var role = Policies.GetStandardRoles().Find(r => r.Id == StandardRoles.Administrator.Id); - await access.AddRole(contextUserId, role); + await access.AddRole(contextUserId, role, bypassIntegrityCheck: true); // Assign security principles to roles and add roles and policy assignments to store var assignedRoles = new List { TestAssignedRoles.Admin, TestAssignedRoles.TestAdmin }; - assignedRoles.ForEach(async r => await access.AddAssignedRole(contextUserId, r)); + assignedRoles.ForEach(async r => await access.AddAssignedRole(contextUserId, r, bypassIntegrityCheck: true)); // Validate password of SecurityPrinciple object returned by AccessControl.GetSecurityPrinciple() before update var storedSecurityPrinciple = await access.GetSecurityPrinciple(contextUserId, adminSecurityPrinciples[0].Id); @@ -496,11 +503,11 @@ public async Task TestDeleteSecurityPrinciple() // Setup and add roles and policy assignments to store var role = Policies.GetStandardRoles().Find(r => r.Id == StandardRoles.Administrator.Id); - await access.AddRole(contextUserId, role); + await access.AddRole(contextUserId, role, bypassIntegrityCheck: true); // Assign security principles to roles and add roles and policy assignments to store var assignedRoles = new List { TestAssignedRoles.Admin, TestAssignedRoles.TestAdmin }; - assignedRoles.ForEach(async r => await access.AddAssignedRole(contextUserId, r)); + assignedRoles.ForEach(async r => await access.AddAssignedRole(contextUserId, r, bypassIntegrityCheck: true)); // Validate SecurityPrinciple object returned by AccessControl.GetSecurityPrinciple() before delete is not null var storedSecurityPrinciple = await access.GetSecurityPrinciple(contextUserId, adminSecurityPrinciples[0].Id); @@ -620,7 +627,7 @@ public async Task TestIsPrincipleInRole() // Setup security principle actions var actions = Policies.GetStandardResourceActions().FindAll(a => a.ResourceType == ResourceTypes.System); - actions.ForEach(async a => await access.AddResourceAction(contextUserId, a)); + actions.ForEach(async a => await access.AddResourceAction(contextUserId, a, bypassIntegrityCheck: true)); // Setup policy with actions and add policy to store var policy = Policies.GetStandardPolicies().Find(p => p.Id == StandardPolicies.AccessAdmin); @@ -628,11 +635,11 @@ public async Task TestIsPrincipleInRole() // Setup and add roles and policy assignments to store var role = Policies.GetStandardRoles().Find(r => r.Id == StandardRoles.Administrator.Id); - await access.AddRole(contextUserId, role); + await access.AddRole(contextUserId, role, bypassIntegrityCheck: true); // Assign security principles to roles and add roles and policy assignments to store var assignedRoles = new List { TestAssignedRoles.Admin, TestAssignedRoles.TestAdmin }; - assignedRoles.ForEach(async r => await access.AddAssignedRole(contextUserId, r)); + assignedRoles.ForEach(async r => await access.AddAssignedRole(contextUserId, r, bypassIntegrityCheck: true)); // Validate specified admin user is a principle role bool hasAccess; @@ -662,10 +669,10 @@ public async Task TestDomainAuth() // Setup and add roles and policy assignments to store var role = Policies.GetStandardRoles().Find(r => r.Id == StandardRoles.CertificateConsumer.Id); - await access.AddRole(contextUserId, role); + await access.AddRole(contextUserId, role, bypassIntegrityCheck: true); // Assign security principles to roles and add roles and policy assignments to store - await access.AddAssignedRole(contextUserId, TestAssignedRoles.DevopsUserDomainConsumer); // devops user in consumer role for a specific domain + await access.AddAssignedRole(contextUserId, TestAssignedRoles.DevopsUserDomainConsumer, true); // devops user in consumer role for a specific domain // Validate user can consume a cert for a given domain var isAuthorised = await access.IsSecurityPrincipleAuthorised(contextUserId, new AccessCheck(TestSecurityPrinciples.DevopsAppDomainConsumer.Id, ResourceTypes.Domain, StandardResourceActions.CertificateDownload, identifier: "www.example.com")); @@ -691,10 +698,10 @@ public async Task TestWildcardDomainAuth() // Setup and add roles and policy assignments to store var role = Policies.GetStandardRoles().Find(r => r.Id == StandardRoles.CertificateConsumer.Id); - await access.AddRole(contextUserId, role); + await access.AddRole(contextUserId, role, bypassIntegrityCheck: true); // Assign security principles to roles and add roles and policy assignments to store - await access.AddAssignedRole(contextUserId, TestAssignedRoles.DevopsUserWildcardDomainConsumer); // devops user in consumer role for a wildcard domain + await access.AddAssignedRole(contextUserId, TestAssignedRoles.DevopsUserWildcardDomainConsumer, bypassIntegrityCheck: true); // devops user in consumer role for a wildcard domain // Validate user can consume any subdomain via a granted wildcard var isAuthorised = await access.IsSecurityPrincipleAuthorised(contextUserId, new AccessCheck(TestSecurityPrinciples.DevopsUser.Id, ResourceTypes.Domain, StandardResourceActions.CertificateDownload, identifier: "random.microsoft.com")); @@ -761,7 +768,7 @@ public async Task TestUserAPIToken() // allow test admin to perform access checks var assignedRoles = new List { TestAssignedRoles.TestAdmin }; - assignedRoles.ForEach(async r => await access.AddAssignedRole(contextUserId, r)); + assignedRoles.ForEach(async r => await access.AddAssignedRole(contextUserId, r, bypassIntegrityCheck: true)); // Add test devops user security principle _ = await access.AddSecurityPrinciple(contextUserId, TestSecurityPrinciples.DevopsUser, bypassIntegrityCheck: true); @@ -783,10 +790,10 @@ public async Task TestUserAPIToken() var assignedRolesForDevopsUser = await access.GetAssignedRoles(contextUserId, TestSecurityPrinciples.DevopsUser.Id); // create and assign a new API token - var apiToken = new AccessToken { ClientId = TestSecurityPrinciples.DevopsUser.Id, Secret = Guid.NewGuid().ToString(), TokenType = "Simple", Description = "An example API token" }; - var apiExpiredToken = new AccessToken { ClientId = TestSecurityPrinciples.DevopsUser.Id, Secret = Guid.NewGuid().ToString(), TokenType = "Simple", Description = "An example expired API token", DateExpiry = DateTimeOffset.UtcNow.AddDays(-1) }; - var apiRevokedToken = new AccessToken { ClientId = TestSecurityPrinciples.DevopsUser.Id, Secret = Guid.NewGuid().ToString(), TokenType = "Simple", Description = "An example revoked API token", DateRevoked = DateTimeOffset.UtcNow.AddDays(-1) }; - var apiTokenBad = new AccessToken { ClientId = TestSecurityPrinciples.DomainOwner.Id, Secret = Guid.NewGuid().ToString(), TokenType = "Simple", Description = "An example bad API token (invalid client id)" }; + var apiToken = new AccessToken { ClientId = TestSecurityPrinciples.DevopsUser.Id, Secret = Guid.NewGuid().ToString(), TokenType = AccessTokenTypes.Simple, Description = "An example API token" }; + var apiExpiredToken = new AccessToken { ClientId = TestSecurityPrinciples.DevopsUser.Id, Secret = Guid.NewGuid().ToString(), TokenType = AccessTokenTypes.Simple, Description = "An example expired API token", DateExpiry = DateTimeOffset.UtcNow.AddDays(-1) }; + var apiRevokedToken = new AccessToken { ClientId = TestSecurityPrinciples.DevopsUser.Id, Secret = Guid.NewGuid().ToString(), TokenType = AccessTokenTypes.Simple, Description = "An example revoked API token", DateRevoked = DateTimeOffset.UtcNow.AddDays(-1) }; + var apiTokenBad = new AccessToken { ClientId = TestSecurityPrinciples.DomainOwner.Id, Secret = Guid.NewGuid().ToString(), TokenType = AccessTokenTypes.Simple, Description = "An example bad API token (invalid client id)" }; var assignedToken = new AssignedAccessToken { AccessTokens = [apiToken, apiExpiredToken, apiRevokedToken], From 77893cae9b5d493548286c391dbb6c6abb2bde46 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Tue, 28 Jan 2025 13:07:25 +0800 Subject: [PATCH 304/328] Package updates --- .../Certify.Aspire.ServiceDefaults.csproj | 10 +++++----- .../DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj | 2 +- .../Certify.Shared.Extensions.csproj | 2 +- .../Certify.Core.Tests.Integration.csproj | 4 ++-- .../Certify.Core.Tests.Unit.csproj | 4 ++-- .../Certify.Service.Tests.Integration.csproj | 4 ++-- .../Certify.UI.Tests.Integration.csproj | 4 ++-- 7 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Certify.Aspire/Certify.Aspire.ServiceDefaults/Certify.Aspire.ServiceDefaults.csproj b/src/Certify.Aspire/Certify.Aspire.ServiceDefaults/Certify.Aspire.ServiceDefaults.csproj index e50a4253d..e4f8adcef 100644 --- a/src/Certify.Aspire/Certify.Aspire.ServiceDefaults/Certify.Aspire.ServiceDefaults.csproj +++ b/src/Certify.Aspire/Certify.Aspire.ServiceDefaults/Certify.Aspire.ServiceDefaults.csproj @@ -13,12 +13,12 @@ - - - + + + - - + + diff --git a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj index 197a33aaf..5be930970 100644 --- a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj +++ b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/Certify.Shared.Extensions/Certify.Shared.Extensions.csproj b/src/Certify.Shared.Extensions/Certify.Shared.Extensions.csproj index 3729bf4a9..d16bc24fe 100644 --- a/src/Certify.Shared.Extensions/Certify.Shared.Extensions.csproj +++ b/src/Certify.Shared.Extensions/Certify.Shared.Extensions.csproj @@ -23,7 +23,7 @@ - + diff --git a/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj b/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj index af13506a8..3d30b6d17 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj @@ -76,8 +76,8 @@ - - + + diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj index af516bcee..c8ad54a68 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj @@ -109,8 +109,8 @@ - - + + diff --git a/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj b/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj index d320c4a7b..671368bc9 100644 --- a/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj @@ -62,8 +62,8 @@ - - + + diff --git a/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj b/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj index eb28a1791..af79b86d4 100644 --- a/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.UI.Tests.Integration/Certify.UI.Tests.Integration.csproj @@ -58,8 +58,8 @@ - - + + From b946b16fe14d6a95b98d583ef516727d5bfbb0cc Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Tue, 28 Jan 2025 16:06:41 +0800 Subject: [PATCH 305/328] Implement per-instance cert multi-format export --- .../CertifyManager.ManagedCertificates.cs | 87 +++++++- .../CertifyManager.ManagementHub.cs | 7 + .../Hub/ManagementHubMessages.cs | 1 + .../Certify.API.Public.cs | 187 +++++++++--------- .../Controllers/v1/CertificateController.cs | 46 +++-- .../Services/ManagementAPI.cs | 23 ++- 6 files changed, 233 insertions(+), 118 deletions(-) diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagedCertificates.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagedCertificates.cs index 62197fb46..9b17487fe 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagedCertificates.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagedCertificates.cs @@ -2,14 +2,16 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; +using System.IO; using System.Linq; using System.Threading.Tasks; -using Certify.Models.Hub; using Certify.Models; +using Certify.Models.Config; +using Certify.Models.Hub; using Certify.Models.Providers; using Certify.Models.Reporting; using Certify.Models.Shared; -using Certify.Models.Config; +using Certify.Shared.Core.Utils.PKI; namespace Certify.Management { @@ -531,6 +533,87 @@ public async Task> GeneratePreview(ManagedCertificate item) return await new PreviewManager().GeneratePreview(item, serverProvider, this, _credentialsManager); } + /// + /// Prepare an export of a managed certificate in the given format (if certificate present) + /// + /// + /// + public async Task> ExportCertificate(string managedCertId, string format) + { + var item = await GetManagedCertificate(managedCertId); + + if (string.IsNullOrEmpty(item?.CertificatePath) || !File.Exists(item?.CertificatePath)) + { + return new ActionResult("Source certificate file is not present. Export cannot continue.", false); + } + + if (string.IsNullOrWhiteSpace(format)) + { + format = "pfx"; + } + + try + { + var pfxData = File.ReadAllBytes(item.CertificatePath); + + var certPwd = ""; + + // if credential used for private key, check if we can decrypt that (unless we exporting PFX which is just a file copy) + if (!string.IsNullOrWhiteSpace(item.CertificatePasswordCredentialId) && format != "pfx") + { + var cred = await GetCredentialsManager().GetUnlockedCredentialsDictionary(item.CertificatePasswordCredentialId); + if (cred != null) + { + certPwd = cred["password"]; + } + else + { + return new ActionResult($"Export - the credentials for this export could not be unlocked or were not accessible {item.CertificatePasswordCredentialId}.", false); + } + } + + byte[] result = []; + + if (format == "pfx") + { + result = pfxData; + } + else if (format == "pem_key") + { + result = CertUtils.GetCertComponentsAsPEMBytes(pfxData, certPwd, ExportFlags.PrivateKey); + } + else if (format == "pem_fullchain") + { + result = CertUtils.GetCertComponentsAsPEMBytes(pfxData, certPwd, ExportFlags.EndEntityCertificate | ExportFlags.IntermediateCertificates); + } + else if (format == "pem_fullchain_key") + { + result = CertUtils.GetCertComponentsAsPEMBytes(pfxData, certPwd, ExportFlags.PrivateKey | ExportFlags.EndEntityCertificate | ExportFlags.IntermediateCertificates); + } + else if (format == "pem_fullchain_root") + { + result = CertUtils.GetCertComponentsAsPEMBytes(pfxData, certPwd, ExportFlags.EndEntityCertificate | ExportFlags.IntermediateCertificates | ExportFlags.RootCertificate); + } + else if (format == "pem_fullchain_root_key") + { + result = CertUtils.GetCertComponentsAsPEMBytes(pfxData, certPwd, ExportFlags.PrivateKey | ExportFlags.EndEntityCertificate | ExportFlags.IntermediateCertificates | ExportFlags.RootCertificate); + } + + if (result.Length == 0) + { + return new ActionResult($"Export - no files where selected for export or export could not be applied for source certificate.", false); + } + else + { + return new ActionResult { Result = result, IsSuccess = true }; + } + } + catch (Exception exp) + { + return new ActionResult($"Export - {exp}", false); + } + } + public async Task> GetDnsProviderZones(string providerTypeId, string credentialId) { var dnsHelper = new Core.Management.Challenges.DnsChallengeHelper(_credentialsManager); diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs index d7fbaaa4d..a1a2f1be4 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs @@ -142,6 +142,13 @@ private async Task _managementServerClient_OnGetCommandRe val = await GeneratePreview(managedCert); } + else if (arg.CommandType == ManagementHubCommands.ExportCertificate) + { + var args = JsonSerializer.Deserialize[]>(arg.Value); + var managedCertIdArg = args.FirstOrDefault(a => a.Key == "managedCertId"); + var format = args.FirstOrDefault(a => a.Key == "format"); + val = await ExportCertificate(managedCertIdArg.Value, format.Value); + } else if (arg.CommandType == ManagementHubCommands.UpdateManagedItem) { // update a single managed item diff --git a/src/Certify.Models/Hub/ManagementHubMessages.cs b/src/Certify.Models/Hub/ManagementHubMessages.cs index 14d3e6149..1c230c363 100644 --- a/src/Certify.Models/Hub/ManagementHubMessages.cs +++ b/src/Certify.Models/Hub/ManagementHubMessages.cs @@ -19,6 +19,7 @@ public class ManagementHubCommands public const string GetManagedItem = "GetManagedItem"; public const string GetManagedItemLog = "GetManagedItemLog"; public const string GetManagedItemRenewalPreview = "GetManagedItemRenewalPreview"; + public const string ExportCertificate = "ExportCertificate"; public const string UpdateManagedItem = "UpdateManagedItem"; public const string RemoveManagedItem = "RemoveManagedItem"; diff --git a/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs b/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs index f935f2602..605f21f08 100644 --- a/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs +++ b/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs @@ -1474,22 +1474,28 @@ public virtual async System.Threading.Tasks.Task RefreshAsync(stri } /// - /// Download the latest certificate for the given managed certificate. For auth provide either a valid JWT via Authorization header or use an API token (using X-ClientID and X-Client-Secret HTTP headers) + /// Download the latest certificate for the given managed certificate. For auth provide either a valid JWT via Authorization header or use an API token (using X-ClientID and X-Client-Secret HTTP headers). /// + /// Instance to fetch managed certificate info from + /// Id of managed cert to fetch + /// pfx = PKCS#12 archive, pem_key = private key only, pem encoded, pem_fullchain = end-entity + intermediates chain, pem_fullchain_key = chain plus key, pem_fullchain_root = chain plus root, pem_fullchain_root_key = chain plus root and key /// OK /// A server side error occurred. - public virtual System.Threading.Tasks.Task DownloadAsync(string instanceId, string managedCertId, string format, string mode) + public virtual System.Threading.Tasks.Task DownloadAsync(string instanceId, string managedCertId, string format) { - return DownloadAsync(instanceId, managedCertId, format, mode, System.Threading.CancellationToken.None); + return DownloadAsync(instanceId, managedCertId, format, System.Threading.CancellationToken.None); } /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Download the latest certificate for the given managed certificate. For auth provide either a valid JWT via Authorization header or use an API token (using X-ClientID and X-Client-Secret HTTP headers) + /// Download the latest certificate for the given managed certificate. For auth provide either a valid JWT via Authorization header or use an API token (using X-ClientID and X-Client-Secret HTTP headers). /// + /// Instance to fetch managed certificate info from + /// Id of managed cert to fetch + /// pfx = PKCS#12 archive, pem_key = private key only, pem encoded, pem_fullchain = end-entity + intermediates chain, pem_fullchain_key = chain plus key, pem_fullchain_root = chain plus root, pem_fullchain_root_key = chain plus root and key /// OK /// A server side error occurred. - public virtual async System.Threading.Tasks.Task DownloadAsync(string instanceId, string managedCertId, string format, string mode, System.Threading.CancellationToken cancellationToken) + public virtual async System.Threading.Tasks.Task DownloadAsync(string instanceId, string managedCertId, string format, System.Threading.CancellationToken cancellationToken) { if (instanceId == null) throw new System.ArgumentNullException("instanceId"); @@ -1511,19 +1517,13 @@ public virtual async System.Threading.Tasks.Task DownloadAsync(str var urlBuilder_ = new System.Text.StringBuilder(); if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); - // Operation Path: "api/v1/certificate/{instanceId}/{managedCertId}/download/{format}" + // Operation Path: "api/v1/certificate/{instanceId}/download/{managedCertId}/{format}" urlBuilder_.Append("api/v1/certificate/"); urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(instanceId, System.Globalization.CultureInfo.InvariantCulture))); - urlBuilder_.Append('/'); - urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(managedCertId, System.Globalization.CultureInfo.InvariantCulture))); urlBuilder_.Append("/download/"); + urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(managedCertId, System.Globalization.CultureInfo.InvariantCulture))); + urlBuilder_.Append('/'); urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(format, System.Globalization.CultureInfo.InvariantCulture))); - urlBuilder_.Append('?'); - if (mode != null) - { - urlBuilder_.Append(System.Uri.EscapeDataString("mode")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(mode, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); - } - urlBuilder_.Length--; PrepareRequest(client_, request_, urlBuilder_); @@ -1896,10 +1896,10 @@ public virtual async System.Threading.Tasks.Task GetManagedC var urlBuilder_ = new System.Text.StringBuilder(); if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); - // Operation Path: "api/v1/certificate/settings/{instanceId}/{managedCertId}" - urlBuilder_.Append("api/v1/certificate/settings/"); + // Operation Path: "api/v1/certificate/{instanceId}/settings/{managedCertId}" + urlBuilder_.Append("api/v1/certificate/"); urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(instanceId, System.Globalization.CultureInfo.InvariantCulture))); - urlBuilder_.Append('/'); + urlBuilder_.Append("/settings/"); urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(managedCertId, System.Globalization.CultureInfo.InvariantCulture))); PrepareRequest(client_, request_, urlBuilder_); @@ -1955,45 +1955,45 @@ public virtual async System.Threading.Tasks.Task GetManagedC } /// - /// Add/update the full settings for a specific managed certificate + /// Remove Managed Certificate [Generated] /// /// OK /// A server side error occurred. - public virtual System.Threading.Tasks.Task UpdateManagedCertificateDetailsAsync(string instanceId, ManagedCertificate body) + public virtual System.Threading.Tasks.Task RemoveManagedCertificateAsync(string instanceId, string managedCertId) { - return UpdateManagedCertificateDetailsAsync(instanceId, body, System.Threading.CancellationToken.None); + return RemoveManagedCertificateAsync(instanceId, managedCertId, System.Threading.CancellationToken.None); } /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Add/update the full settings for a specific managed certificate + /// Remove Managed Certificate [Generated] /// /// OK /// A server side error occurred. - public virtual async System.Threading.Tasks.Task UpdateManagedCertificateDetailsAsync(string instanceId, ManagedCertificate body, System.Threading.CancellationToken cancellationToken) + public virtual async System.Threading.Tasks.Task RemoveManagedCertificateAsync(string instanceId, string managedCertId, System.Threading.CancellationToken cancellationToken) { if (instanceId == null) throw new System.ArgumentNullException("instanceId"); + if (managedCertId == null) + throw new System.ArgumentNullException("managedCertId"); + var client_ = _httpClient; var disposeClient_ = false; try { using (var request_ = new System.Net.Http.HttpRequestMessage()) { - var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(body, JsonSerializerSettings); - var content_ = new System.Net.Http.StringContent(json_); - content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); - request_.Content = content_; - request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Method = new System.Net.Http.HttpMethod("DELETE"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); var urlBuilder_ = new System.Text.StringBuilder(); if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); - // Operation Path: "api/v1/certificate/settings/{instanceId}/update" - urlBuilder_.Append("api/v1/certificate/settings/"); + // Operation Path: "api/v1/certificate/{instanceId}/settings/{managedCertId}" + urlBuilder_.Append("api/v1/certificate/"); urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(instanceId, System.Globalization.CultureInfo.InvariantCulture))); - urlBuilder_.Append("/update"); + urlBuilder_.Append("/settings/"); + urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(managedCertId, System.Globalization.CultureInfo.InvariantCulture))); PrepareRequest(client_, request_, urlBuilder_); @@ -2020,7 +2020,7 @@ public virtual async System.Threading.Tasks.Task UpdateManag var status_ = (int)response_.StatusCode; if (status_ == 200) { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); @@ -2048,46 +2048,44 @@ public virtual async System.Threading.Tasks.Task UpdateManag } /// - /// Begin the managed certificate request/renewal process for the given managed certificate id (on demand) + /// Add/update the full settings for a specific managed certificate /// /// OK /// A server side error occurred. - public virtual System.Threading.Tasks.Task BeginOrderAsync(string instanceId, string id) + public virtual System.Threading.Tasks.Task UpdateManagedCertificateDetailsAsync(string instanceId, ManagedCertificate body) { - return BeginOrderAsync(instanceId, id, System.Threading.CancellationToken.None); + return UpdateManagedCertificateDetailsAsync(instanceId, body, System.Threading.CancellationToken.None); } /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Begin the managed certificate request/renewal process for the given managed certificate id (on demand) + /// Add/update the full settings for a specific managed certificate /// /// OK /// A server side error occurred. - public virtual async System.Threading.Tasks.Task BeginOrderAsync(string instanceId, string id, System.Threading.CancellationToken cancellationToken) + public virtual async System.Threading.Tasks.Task UpdateManagedCertificateDetailsAsync(string instanceId, ManagedCertificate body, System.Threading.CancellationToken cancellationToken) { + if (instanceId == null) + throw new System.ArgumentNullException("instanceId"); + var client_ = _httpClient; var disposeClient_ = false; try { using (var request_ = new System.Net.Http.HttpRequestMessage()) { - request_.Content = new System.Net.Http.StringContent(string.Empty, System.Text.Encoding.UTF8, "application/json"); + var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(body, JsonSerializerSettings); + var content_ = new System.Net.Http.StringContent(json_); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); var urlBuilder_ = new System.Text.StringBuilder(); if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); - // Operation Path: "api/v1/certificate/order" - urlBuilder_.Append("api/v1/certificate/order"); - urlBuilder_.Append('?'); - if (instanceId != null) - { - urlBuilder_.Append(System.Uri.EscapeDataString("instanceId")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(instanceId, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); - } - if (id != null) - { - urlBuilder_.Append(System.Uri.EscapeDataString("id")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(id, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); - } - urlBuilder_.Length--; + // Operation Path: "{instanceId}/settings/update" + urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(instanceId, System.Globalization.CultureInfo.InvariantCulture))); + urlBuilder_.Append("/settings/update"); PrepareRequest(client_, request_, urlBuilder_); @@ -2114,7 +2112,12 @@ public virtual async System.Threading.Tasks.Task BeginOrderAsync(string instance var status_ = (int)response_.StatusCode; if (status_ == 200) { - return; + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + if (objectResponse_.Object == null) + { + throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); + } + return objectResponse_.Object; } else { @@ -2137,22 +2140,22 @@ public virtual async System.Threading.Tasks.Task BeginOrderAsync(string instance } /// - /// Begin the managed certificate request/renewal process a set of managed certificates + /// Begin the managed certificate request/renewal process for the given managed certificate id (on demand) /// /// OK /// A server side error occurred. - public virtual System.Threading.Tasks.Task PerformRenewalAsync(string instanceId, RenewalSettings body) + public virtual System.Threading.Tasks.Task BeginOrderAsync(string instanceId, string id) { - return PerformRenewalAsync(instanceId, body, System.Threading.CancellationToken.None); + return BeginOrderAsync(instanceId, id, System.Threading.CancellationToken.None); } /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Begin the managed certificate request/renewal process a set of managed certificates + /// Begin the managed certificate request/renewal process for the given managed certificate id (on demand) /// /// OK /// A server side error occurred. - public virtual async System.Threading.Tasks.Task PerformRenewalAsync(string instanceId, RenewalSettings body, System.Threading.CancellationToken cancellationToken) + public virtual async System.Threading.Tasks.Task BeginOrderAsync(string instanceId, string id, System.Threading.CancellationToken cancellationToken) { var client_ = _httpClient; var disposeClient_ = false; @@ -2160,22 +2163,22 @@ public virtual async System.Threading.Tasks.Task PerformRene { using (var request_ = new System.Net.Http.HttpRequestMessage()) { - var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(body, JsonSerializerSettings); - var content_ = new System.Net.Http.StringContent(json_); - content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); - request_.Content = content_; + request_.Content = new System.Net.Http.StringContent(string.Empty, System.Text.Encoding.UTF8, "application/json"); request_.Method = new System.Net.Http.HttpMethod("POST"); - request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); var urlBuilder_ = new System.Text.StringBuilder(); if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); - // Operation Path: "api/v1/certificate/renew" - urlBuilder_.Append("api/v1/certificate/renew"); + // Operation Path: "api/v1/certificate/order" + urlBuilder_.Append("api/v1/certificate/order"); urlBuilder_.Append('?'); if (instanceId != null) { urlBuilder_.Append(System.Uri.EscapeDataString("instanceId")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(instanceId, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); } + if (id != null) + { + urlBuilder_.Append(System.Uri.EscapeDataString("id")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(id, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); + } urlBuilder_.Length--; PrepareRequest(client_, request_, urlBuilder_); @@ -2203,12 +2206,7 @@ public virtual async System.Threading.Tasks.Task PerformRene var status_ = (int)response_.StatusCode; if (status_ == 200) { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); - if (objectResponse_.Object == null) - { - throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); - } - return objectResponse_.Object; + return; } else { @@ -2231,22 +2229,22 @@ public virtual async System.Threading.Tasks.Task PerformRene } /// - /// Perform default tests for the given configuration + /// Begin the managed certificate request/renewal process a set of managed certificates /// /// OK /// A server side error occurred. - public virtual System.Threading.Tasks.Task> PerformConfigurationTestAsync(string instanceId, ManagedCertificate body) + public virtual System.Threading.Tasks.Task PerformRenewalAsync(string instanceId, RenewalSettings body) { - return PerformConfigurationTestAsync(instanceId, body, System.Threading.CancellationToken.None); + return PerformRenewalAsync(instanceId, body, System.Threading.CancellationToken.None); } /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Perform default tests for the given configuration + /// Begin the managed certificate request/renewal process a set of managed certificates /// /// OK /// A server side error occurred. - public virtual async System.Threading.Tasks.Task> PerformConfigurationTestAsync(string instanceId, ManagedCertificate body, System.Threading.CancellationToken cancellationToken) + public virtual async System.Threading.Tasks.Task PerformRenewalAsync(string instanceId, RenewalSettings body, System.Threading.CancellationToken cancellationToken) { var client_ = _httpClient; var disposeClient_ = false; @@ -2263,8 +2261,8 @@ public virtual async System.Threading.Tasks.Task PerformRene var urlBuilder_ = new System.Text.StringBuilder(); if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); - // Operation Path: "api/v1/certificate/test" - urlBuilder_.Append("api/v1/certificate/test"); + // Operation Path: "api/v1/certificate/renew" + urlBuilder_.Append("api/v1/certificate/renew"); urlBuilder_.Append('?'); if (instanceId != null) { @@ -2297,7 +2295,7 @@ public virtual async System.Threading.Tasks.Task PerformRene var status_ = (int)response_.StatusCode; if (status_ == 200) { - var objectResponse_ = await ReadObjectResponseAsync>(response_, headers_, cancellationToken).ConfigureAwait(false); + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); @@ -2325,45 +2323,46 @@ public virtual async System.Threading.Tasks.Task PerformRene } /// - /// Remove Managed Certificate [Generated] + /// Perform default tests for the given configuration /// /// OK /// A server side error occurred. - public virtual System.Threading.Tasks.Task RemoveManagedCertificateAsync(string instanceId, string managedCertId) + public virtual System.Threading.Tasks.Task> PerformConfigurationTestAsync(string instanceId, ManagedCertificate body) { - return RemoveManagedCertificateAsync(instanceId, managedCertId, System.Threading.CancellationToken.None); + return PerformConfigurationTestAsync(instanceId, body, System.Threading.CancellationToken.None); } /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Remove Managed Certificate [Generated] + /// Perform default tests for the given configuration /// /// OK /// A server side error occurred. - public virtual async System.Threading.Tasks.Task RemoveManagedCertificateAsync(string instanceId, string managedCertId, System.Threading.CancellationToken cancellationToken) + public virtual async System.Threading.Tasks.Task> PerformConfigurationTestAsync(string instanceId, ManagedCertificate body, System.Threading.CancellationToken cancellationToken) { - if (instanceId == null) - throw new System.ArgumentNullException("instanceId"); - - if (managedCertId == null) - throw new System.ArgumentNullException("managedCertId"); - var client_ = _httpClient; var disposeClient_ = false; try { using (var request_ = new System.Net.Http.HttpRequestMessage()) { - request_.Method = new System.Net.Http.HttpMethod("DELETE"); + var json_ = Newtonsoft.Json.JsonConvert.SerializeObject(body, JsonSerializerSettings); + var content_ = new System.Net.Http.StringContent(json_); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("text/plain")); var urlBuilder_ = new System.Text.StringBuilder(); if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); - // Operation Path: "api/v1/certificate/{instanceId}/settings/{managedCertId}" - urlBuilder_.Append("api/v1/certificate/"); - urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(instanceId, System.Globalization.CultureInfo.InvariantCulture))); - urlBuilder_.Append("/settings/"); - urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(managedCertId, System.Globalization.CultureInfo.InvariantCulture))); + // Operation Path: "api/v1/certificate/test" + urlBuilder_.Append("api/v1/certificate/test"); + urlBuilder_.Append('?'); + if (instanceId != null) + { + urlBuilder_.Append(System.Uri.EscapeDataString("instanceId")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(instanceId, System.Globalization.CultureInfo.InvariantCulture))).Append('&'); + } + urlBuilder_.Length--; PrepareRequest(client_, request_, urlBuilder_); @@ -2390,7 +2389,7 @@ public virtual async System.Threading.Tasks.Task RemoveManagedCert var status_ = (int)response_.StatusCode; if (status_ == 200) { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + var objectResponse_ = await ReadObjectResponseAsync>(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs index fbf9542d7..55d6af440 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs @@ -37,18 +37,18 @@ public CertificateController(ILogger logger, ICertifyInte } /// - /// Download the latest certificate for the given managed certificate. For auth provide either a valid JWT via Authorization header or use an API token (using X-ClientID and X-Client-Secret HTTP headers) + /// Download the latest certificate for the given managed certificate. For auth provide either a valid JWT via Authorization header or use an API token (using X-ClientID and X-Client-Secret HTTP headers). + /// /// - /// - /// - /// - /// + /// Instance to fetch managed certificate info from + /// Id of managed cert to fetch + /// pfx = PKCS#12 archive, pem_key = private key only, pem encoded, pem_fullchain = end-entity + intermediates chain, pem_fullchain_key = chain plus key, pem_fullchain_root = chain plus root, pem_fullchain_root_key = chain plus root and key /// The certificate file in the chosen format [HttpGet] - [Route("{instanceId}/{managedCertId}/download/{format?}")] + [Route("{instanceId}/download/{managedCertId}/{format?}")] [AllowAnonymous] [ProducesResponseType(typeof(FileContentResult), 200)] - public async Task Download(string instanceId, string managedCertId, string format, string? mode = null) + public async Task Download(string instanceId, string managedCertId, string format) { var accessPermitted = false; @@ -77,7 +77,7 @@ public async Task Download(string instanceId, string managedCertI return Problem(detail: "X-Client-ID or X-Client-Secret HTTP header missing in request", statusCode: (int)HttpStatusCode.Unauthorized); } - var accessPermittedResult = await IsAccessTokenAuthorized(_client, new AccessToken { ClientId = clientId, Secret = secret, TokenType = "Simple" }, new AccessCheck(default!, ResourceTypes.Certificate, StandardResourceActions.CertificateDownload)); + var accessPermittedResult = await IsAccessTokenAuthorized(_client, new AccessToken { ClientId = clientId, Secret = secret }, new AccessCheck(default!, ResourceTypes.Certificate, StandardResourceActions.CertificateDownload)); if (accessPermittedResult.IsSuccess) { @@ -100,12 +100,7 @@ public async Task Download(string instanceId, string managedCertI format = "pfx"; } - if (mode == null) - { - mode = "fullchain"; - } - - // TODO: certify manager to do all the cert conversion work, server may be on another machine + // fetch managed cert info an check if we have a cert available and if any of our caching headers are applicable var managedCert = await _mgmtAPI.GetManagedCertificate(instanceId, managedCertId, CurrentAuthContext); if (managedCert == null) @@ -134,14 +129,23 @@ public async Task Download(string instanceId, string managedCertI return StatusCode((int)HttpStatusCode.NotModified); } - var content = await System.IO.File.ReadAllBytesAsync(managedCert.CertificatePath); + // perform the export from the instance holding the cert + var exportResult = await _mgmtAPI.ExportCertificate(instanceId, managedCertId, format, CurrentAuthContext); - if (!string.IsNullOrEmpty(managedCert.CertificateThumbprintHash)) + //return the cert or cert component as a file + if (exportResult.IsSuccess && exportResult.Result != null) { - Response.Headers.Append("ETag", managedCert.CertificateThumbprintHash.ToLowerInvariant()); - } + if (!string.IsNullOrEmpty(managedCert.CertificateThumbprintHash)) + { + Response.Headers.Append("ETag", managedCert.CertificateThumbprintHash.ToLowerInvariant()); + } - return new FileContentResult(content, "application/x-pkcs12") { FileDownloadName = "certificate.pfx" }; + return new FileContentResult(exportResult.Result, "application/x-pkcs12") { FileDownloadName = "certificate.pfx" }; + } + else + { + return Problem(detail: exportResult.Message, statusCode: (int)HttpStatusCode.BadRequest); + } } /// @@ -235,7 +239,7 @@ public async Task GetManagedCertificateSummary() /// managed item /// [HttpGet] - [Route("settings/{instanceId}/{managedCertId}")] + [Route("{instanceId}/settings/{managedCertId}")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Models.ManagedCertificate))] public async Task GetManagedCertificateDetails(string instanceId, string managedCertId) @@ -251,7 +255,7 @@ public async Task GetManagedCertificateDetails(string instanceId, /// /// [HttpPost] - [Route("settings/{instanceId}/update")] + [Route("/{instanceId}/settings/update")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Models.ManagedCertificate))] public async Task UpdateManagedCertificateDetails(string instanceId, Models.ManagedCertificate managedCertificate) diff --git a/src/Certify.Server/Certify.Server.Api.Public/Services/ManagementAPI.cs b/src/Certify.Server/Certify.Server.Api.Public/Services/ManagementAPI.cs index 3654ca6b8..8ae438a8f 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Services/ManagementAPI.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Services/ManagementAPI.cs @@ -1,8 +1,8 @@ using System.Text.Json; using Certify.Client; -using Certify.Models.Hub; using Certify.Models; using Certify.Models.Config; +using Certify.Models.Hub; using Certify.Models.Providers; using Certify.Models.Reporting; using Certify.Server.Api.Public.SignalR.ManagementHub; @@ -97,6 +97,27 @@ private async Task SendCommandWithNoResult(string instanceId, InstanceCommandReq return await PerformInstanceCommandTaskWithResult(instanceId, args, ManagementHubCommands.GetManagedItem); } + /// + /// Fetch managed cert details from the target instance + /// + /// + /// + /// + /// + /// + public async Task> ExportCertificate(string instanceId, string managedCertId, string format, AuthContext authContext) + { + // get managed cert via local api or via management hub + + var args = new KeyValuePair[] { + new("instanceId", instanceId) , + new("managedCertId", managedCertId), + new("format", format) + }; + + return await PerformInstanceCommandTaskWithResult>(instanceId, args, ManagementHubCommands.ExportCertificate); + } + /// /// Add or Update Managed Certificate for target instance /// From c64f164fa523fba168fa8131a90917f15b4d8022 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Tue, 28 Jan 2025 16:35:19 +0800 Subject: [PATCH 306/328] Cleanup Cleanup Cleanup --- .../CertifyManager/CertifyManager.ManagedChallenges.cs | 1 - .../CertifyManager/CertifyManager.ServerType.cs | 6 +++--- .../Management/CertifyManager/CertifyManager.cs | 2 -- src/Certify.Models/Hub/AccessControl.cs | 3 +-- src/Certify.Models/Hub/ManagedCertificateSummary.cs | 1 - src/Certify.Models/Hub/ManagedChallenge.cs | 4 +--- src/Certify.Models/Hub/ManagedInstanceInfo.cs | 1 - src/Certify.Providers/ACME/Anvil/AnvilACMEProvider.cs | 4 ++-- .../DNS/Cloudflare/DnsProviderCloudflare.cs | 8 ++++---- .../Certify.API.Public.cs | 3 ++- .../Controllers/internal/TargetController.cs | 2 -- .../Controllers/v1/CertificateController.cs | 2 +- .../ManagementHub/InstanceManagementStateProvider.cs | 6 +++--- .../Controllers/ManagedCertificateController.cs | 4 ++-- .../Controllers/ManagedChallengeController.cs | 1 - .../Controllers/ManagedCertificateController.cs | 2 +- src/Certify.Shared/Management/CredentialsUtil.cs | 1 - src/Certify.UI.Desktop/AssemblyInfo.cs | 2 +- src/Certify.UI.Shared/AssemblyInfo.cs | 2 +- .../ManagedCertificate/ManagedCertificateSettings.xaml.cs | 4 ++-- ...dCerticates.cs => AppViewModel.ManagedCertificates.cs} | 2 +- .../ViewModel/AppViewModel/AppViewModel.cs | 4 ++-- 22 files changed, 27 insertions(+), 38 deletions(-) rename src/Certify.UI.Shared/ViewModel/AppViewModel/{AppViewModel.ManagedCerticates.cs => AppViewModel.ManagedCertificates.cs} (100%) diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagedChallenges.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagedChallenges.cs index b377a2af6..e8be41a77 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagedChallenges.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagedChallenges.cs @@ -7,7 +7,6 @@ using Certify.Models; using Certify.Models.Config; using Certify.Models.Hub; -using Serilog; namespace Certify.Management { diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.ServerType.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.ServerType.cs index 1719b80b8..05dade748 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.ServerType.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.ServerType.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -17,12 +17,12 @@ private async Task> GetTargetServiceTypes() if (await IsServerTypeAvailable(StandardServerTypes.IIS)) { list.Add(StandardServerTypes.IIS.ToString()); - }; + } if (await IsServerTypeAvailable(StandardServerTypes.Nginx)) { list.Add(StandardServerTypes.Nginx.ToString()); - }; + } return list; } diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.cs index 105f5b3a0..62da3f38d 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.cs @@ -9,8 +9,6 @@ using Certify.Core.Management.Challenges; using Certify.Datastore.SQLite; using Certify.Models; -using Certify.Models.Config; -using Certify.Models.Hub; using Certify.Models.Providers; using Certify.Providers; using Microsoft.Extensions.Logging; diff --git a/src/Certify.Models/Hub/AccessControl.cs b/src/Certify.Models/Hub/AccessControl.cs index e7eefebae..3223795a0 100644 --- a/src/Certify.Models/Hub/AccessControl.cs +++ b/src/Certify.Models/Hub/AccessControl.cs @@ -21,7 +21,6 @@ public enum SecurityPermissionType /// public class SecurityPrinciple : ConfigurationStoreItem { - public string? Username { get; set; } public string? Password { get; set; } public string? Email { get; set; } @@ -111,7 +110,7 @@ public class AccessTokenTypes } public class AccessToken : ConfigurationStoreItem { - public string TokenType { get; set; } = default!; + public string? TokenType { get; set; } = default!; public string Secret { get; set; } = default!; public string ClientId { get; set; } = default!; diff --git a/src/Certify.Models/Hub/ManagedCertificateSummary.cs b/src/Certify.Models/Hub/ManagedCertificateSummary.cs index 9c5b5b3ea..cf01dd4a2 100644 --- a/src/Certify.Models/Hub/ManagedCertificateSummary.cs +++ b/src/Certify.Models/Hub/ManagedCertificateSummary.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using Certify.Models; namespace Certify.Models.Hub { diff --git a/src/Certify.Models/Hub/ManagedChallenge.cs b/src/Certify.Models/Hub/ManagedChallenge.cs index 98b80dabb..9cf85ae65 100644 --- a/src/Certify.Models/Hub/ManagedChallenge.cs +++ b/src/Certify.Models/Hub/ManagedChallenge.cs @@ -1,6 +1,4 @@ -using System; - -namespace Certify.Models.Hub +namespace Certify.Models.Hub { /// /// Configuration for a managed challenge, such as a DNS challenge for a specific domain/zone diff --git a/src/Certify.Models/Hub/ManagedInstanceInfo.cs b/src/Certify.Models/Hub/ManagedInstanceInfo.cs index 96d068880..407c6b451 100644 --- a/src/Certify.Models/Hub/ManagedInstanceInfo.cs +++ b/src/Certify.Models/Hub/ManagedInstanceInfo.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using Certify.Models; namespace Certify.Models.Hub { diff --git a/src/Certify.Providers/ACME/Anvil/AnvilACMEProvider.cs b/src/Certify.Providers/ACME/Anvil/AnvilACMEProvider.cs index b1f23f330..2ff897d1b 100644 --- a/src/Certify.Providers/ACME/Anvil/AnvilACMEProvider.cs +++ b/src/Certify.Providers/ACME/Anvil/AnvilACMEProvider.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; @@ -1434,7 +1434,7 @@ public async Task CompleteCertificateRequest(ILog log, Manage log.Warning($"Order context was not cached: {orderId}"); // didn't have cached info orderContext = _acme.Order(new Uri(orderId)); - }; + } // check order status, if it's not 'ready' then try a few more times before giving up var order = await orderContext.Resource(); diff --git a/src/Certify.Providers/DNS/Cloudflare/DnsProviderCloudflare.cs b/src/Certify.Providers/DNS/Cloudflare/DnsProviderCloudflare.cs index e3299248e..a1d68ebf7 100644 --- a/src/Certify.Providers/DNS/Cloudflare/DnsProviderCloudflare.cs +++ b/src/Certify.Providers/DNS/Cloudflare/DnsProviderCloudflare.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; @@ -6,7 +6,6 @@ using Certify.Models.Config; using Certify.Models.Plugins; using Certify.Models.Providers; -using Certify.Models.Shared.Validation; using Certify.Plugins; using Newtonsoft.Json; @@ -214,7 +213,7 @@ private string NormalizeTXTValue(string val) private async Task AddDnsRecord(string zoneId, string name, string value) { value = NormalizeTXTValue(value); - + var request = CreateRequest(HttpMethod.Post, string.Format(_createRecordUri, zoneId)); request.Content = new StringContent( @@ -428,7 +427,8 @@ public async Task InitProvider(Dictionary credentials, Dic if (credentials == null || credentials?.Any() == false) { throw new ArgumentException(credentialError); - }; + } + ; _authKey = credentials.ContainsKey("authkey") ? credentials["authkey"] : null; _apiToken = credentials.ContainsKey("apitoken") ? credentials["apitoken"] : null; diff --git a/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs b/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs index 605f21f08..79b2624e8 100644 --- a/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs +++ b/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs @@ -2083,7 +2083,8 @@ public virtual async System.Threading.Tasks.Task UpdateManag var urlBuilder_ = new System.Text.StringBuilder(); if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); - // Operation Path: "{instanceId}/settings/update" + // Operation Path: "api/v1/certificate/{instanceId}/settings/update" + urlBuilder_.Append("api/v1/certificate/"); urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(instanceId, System.Globalization.CultureInfo.InvariantCulture))); urlBuilder_.Append("/settings/update"); diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/TargetController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/TargetController.cs index edfc943b8..c5d85196e 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/TargetController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/TargetController.cs @@ -1,8 +1,6 @@ using Certify.Client; using Certify.Models; using Certify.Server.Api.Public.Services; -using Microsoft.AspNetCore.Authentication.JwtBearer; -using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace Certify.Server.Api.Public.Controllers diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs index 55d6af440..776fcb16a 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs @@ -255,7 +255,7 @@ public async Task GetManagedCertificateDetails(string instanceId, /// /// [HttpPost] - [Route("/{instanceId}/settings/update")] + [Route("{instanceId}/settings/update")] [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Models.ManagedCertificate))] public async Task UpdateManagedCertificateDetails(string instanceId, Models.ManagedCertificate managedCertificate) diff --git a/src/Certify.Server/Certify.Server.Api.Public/SignalR/ManagementHub/InstanceManagementStateProvider.cs b/src/Certify.Server/Certify.Server.Api.Public/SignalR/ManagementHub/InstanceManagementStateProvider.cs index bb3c829fe..684f86d7c 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/SignalR/ManagementHub/InstanceManagementStateProvider.cs +++ b/src/Certify.Server/Certify.Server.Api.Public/SignalR/ManagementHub/InstanceManagementStateProvider.cs @@ -1,6 +1,6 @@ -using System.Collections.Concurrent; -using Certify.Models.Hub; +using System.Collections.Concurrent; using Certify.Models; +using Certify.Models.Hub; using Certify.Models.Reporting; namespace Certify.Server.Api.Public.SignalR.ManagementHub @@ -145,7 +145,7 @@ public void AddAwaitedCommandResult(InstanceCommandResult result) attempts--; await Task.Delay(100); _logger.LogInformation("Still waiting for command result {commandId}..", commandId); - }; + } _awaitedCommandResults.Remove(commandId, out var cmd); diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/ManagedCertificateController.cs b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/ManagedCertificateController.cs index 05dd3a40a..8bb8e5e60 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/ManagedCertificateController.cs +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/ManagedCertificateController.cs @@ -1,8 +1,8 @@ -using Certify.Config; -using Certify.Models.Hub; +using Certify.Config; using Certify.Management; using Certify.Models; using Certify.Models.Config; +using Certify.Models.Hub; using Certify.Models.Reporting; using Certify.Models.Utils; using Microsoft.AspNetCore.Mvc; diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/ManagedChallengeController.cs b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/ManagedChallengeController.cs index f37226f19..f733bb770 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/ManagedChallengeController.cs +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/ManagedChallengeController.cs @@ -1,5 +1,4 @@ using Certify.Management; -using Certify.Models.Config; using Certify.Models.Hub; using Microsoft.AspNetCore.Mvc; diff --git a/src/Certify.Service/Controllers/ManagedCertificateController.cs b/src/Certify.Service/Controllers/ManagedCertificateController.cs index 65785e2e7..669553175 100644 --- a/src/Certify.Service/Controllers/ManagedCertificateController.cs +++ b/src/Certify.Service/Controllers/ManagedCertificateController.cs @@ -5,10 +5,10 @@ using System.Web.Http; using System.Web.Http.Cors; using Certify.Config; -using Certify.Models.Hub; using Certify.Management; using Certify.Models; using Certify.Models.Config; +using Certify.Models.Hub; using Certify.Models.Reporting; using Certify.Models.Utils; using Microsoft.Extensions.Logging; diff --git a/src/Certify.Shared/Management/CredentialsUtil.cs b/src/Certify.Shared/Management/CredentialsUtil.cs index 032853126..395cbe27e 100644 --- a/src/Certify.Shared/Management/CredentialsUtil.cs +++ b/src/Certify.Shared/Management/CredentialsUtil.cs @@ -1,5 +1,4 @@ using System; -using System.Diagnostics; using System.IO; using System.Linq; using System.Runtime.InteropServices; diff --git a/src/Certify.UI.Desktop/AssemblyInfo.cs b/src/Certify.UI.Desktop/AssemblyInfo.cs index 8b5504ecf..f60efc019 100644 --- a/src/Certify.UI.Desktop/AssemblyInfo.cs +++ b/src/Certify.UI.Desktop/AssemblyInfo.cs @@ -1,4 +1,4 @@ -using System.Windows; +using System.Windows; [assembly: ThemeInfo( ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located diff --git a/src/Certify.UI.Shared/AssemblyInfo.cs b/src/Certify.UI.Shared/AssemblyInfo.cs index 8b5504ecf..f60efc019 100644 --- a/src/Certify.UI.Shared/AssemblyInfo.cs +++ b/src/Certify.UI.Shared/AssemblyInfo.cs @@ -1,4 +1,4 @@ -using System.Windows; +using System.Windows; [assembly: ThemeInfo( ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located diff --git a/src/Certify.UI.Shared/Controls/ManagedCertificate/ManagedCertificateSettings.xaml.cs b/src/Certify.UI.Shared/Controls/ManagedCertificate/ManagedCertificateSettings.xaml.cs index 999c7e793..022dc93d0 100644 --- a/src/Certify.UI.Shared/Controls/ManagedCertificate/ManagedCertificateSettings.xaml.cs +++ b/src/Certify.UI.Shared/Controls/ManagedCertificate/ManagedCertificateSettings.xaml.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using System.Threading.Tasks; using System.Windows; @@ -120,7 +120,7 @@ private async Task ValidateAndSave() { // opted not to save return false; - }; + } } //create/update managed item diff --git a/src/Certify.UI.Shared/ViewModel/AppViewModel/AppViewModel.ManagedCerticates.cs b/src/Certify.UI.Shared/ViewModel/AppViewModel/AppViewModel.ManagedCertificates.cs similarity index 100% rename from src/Certify.UI.Shared/ViewModel/AppViewModel/AppViewModel.ManagedCerticates.cs rename to src/Certify.UI.Shared/ViewModel/AppViewModel/AppViewModel.ManagedCertificates.cs index b0201450e..f8f256d6b 100644 --- a/src/Certify.UI.Shared/ViewModel/AppViewModel/AppViewModel.ManagedCerticates.cs +++ b/src/Certify.UI.Shared/ViewModel/AppViewModel/AppViewModel.ManagedCertificates.cs @@ -6,9 +6,9 @@ using System.Threading.Tasks; using System.Windows; using System.Windows.Input; -using Certify.Models.Hub; using Certify.Locales; using Certify.Models; +using Certify.Models.Hub; using Certify.Models.Reporting; using Certify.UI.Shared; using PropertyChanged; diff --git a/src/Certify.UI.Shared/ViewModel/AppViewModel/AppViewModel.cs b/src/Certify.UI.Shared/ViewModel/AppViewModel/AppViewModel.cs index 15b2dc1f3..5b5140889 100644 --- a/src/Certify.UI.Shared/ViewModel/AppViewModel/AppViewModel.cs +++ b/src/Certify.UI.Shared/ViewModel/AppViewModel/AppViewModel.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; @@ -110,7 +110,7 @@ public void StartProgressCleanupTask() foreach (var item in items.ToList()) { ProgressResults.Remove(item); - }; + } }); } } From d2cbd0bf31a84d06ef12300c2ce5274481ab38b4 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Fri, 31 Jan 2025 13:48:42 +0800 Subject: [PATCH 307/328] Rename Certify.Server.Api.Public to Certify.Server.Hub.Api --- .../Certify.Aspire.AppHost.csproj | 2 +- .../Certify.Aspire.AppHost/Program.cs | 2 +- src/Certify.Core.Service.sln | 2 +- .../Certify.Server.Core/Certify.Server.Core.csproj | 4 ++-- .../Certify.Server.Hub.Api.Client.cs} | 12 ++++++------ .../Certify.Server.Hub.Api.Client.csproj} | 0 .../nswag.json | 6 +++--- .../readme.md | 2 +- .../APITestBase.cs | 0 .../AccessTests.cs | 0 .../AuthTests.cs | 0 .../CertificateTests.cs | 0 .../Certify.Server.Api.Public.Tests.csproj | 4 ++-- .../Properties/launchSettings.json | 2 +- .../SystemTests.cs | 0 .../appsettings.api.public.test.json | 0 .../appsettings.worker.test.json | 0 .../.config/dotnet-tools.json | 0 .../Certify.Server.Hub.Api.csproj} | 0 .../Controllers/internal/AccessController.cs | 2 +- .../Controllers/internal/ApiControllerBase.cs | 4 ++-- .../internal/CertificateAuthorityController.cs | 4 ++-- .../internal/ChallengeProviderController.cs | 4 ++-- .../internal/DeploymentTaskController.cs | 4 ++-- .../Controllers/internal/HubController.cs | 4 ++-- .../Controllers/internal/PreviewController.cs | 4 ++-- .../internal/StoredCredentialController.cs | 4 ++-- .../Controllers/internal/TargetController.cs | 4 ++-- .../Controllers/v1/AuthController.cs | 6 +++--- .../Controllers/v1/CertificateController.cs | 4 ++-- .../Controllers/v1/ManagedChallengeController.cs | 2 +- .../Controllers/v1/SystemController.cs | 10 +++++----- .../Controllers/v1/ValidationController.cs | 2 +- .../Dockerfile | 14 +++++++------- .../Middleware/AuthenticationExtension.cs | 2 +- .../Program.cs | 2 +- .../Properties/launchSettings.json | 2 +- .../Services/JwtService.cs | 2 +- .../Services/ManagementAPI.cs | 4 ++-- .../Services/ManagementWorker.cs | 4 ++-- .../SignalR/ManagementHub/InstanceManagementHub.cs | 2 +- .../InstanceManagementStateProvider.cs | 4 ++-- .../SignalR/UserInterfaceStatusHub.cs | 2 +- .../Startup.cs | 10 +++++----- .../appsettings.Development.json | 0 .../appsettings.json | 2 +- .../docs/readme.md | 0 .../PublicAPISourceGenerator.cs | 8 ++++---- 48 files changed, 76 insertions(+), 76 deletions(-) rename src/Certify.Server/{Certify.Server.Api.Public.Client/Certify.API.Public.cs => Certify.Server.Hub.Api.Client/Certify.Server.Hub.Api.Client.cs} (99%) rename src/Certify.Server/{Certify.Server.Api.Public.Client/Certify.Server.Api.Public.Client.csproj => Certify.Server.Hub.Api.Client/Certify.Server.Hub.Api.Client.csproj} (100%) rename src/Certify.Server/{Certify.Server.Api.Public.Client => Certify.Server.Hub.Api.Client}/nswag.json (97%) rename src/Certify.Server/{Certify.Server.Api.Public.Client => Certify.Server.Hub.Api.Client}/readme.md (88%) rename src/Certify.Server/{Certify.Server.Api.Public.Tests => Certify.Server.Hub.Api.Tests}/APITestBase.cs (100%) rename src/Certify.Server/{Certify.Server.Api.Public.Tests => Certify.Server.Hub.Api.Tests}/AccessTests.cs (100%) rename src/Certify.Server/{Certify.Server.Api.Public.Tests => Certify.Server.Hub.Api.Tests}/AuthTests.cs (100%) rename src/Certify.Server/{Certify.Server.Api.Public.Tests => Certify.Server.Hub.Api.Tests}/CertificateTests.cs (100%) rename src/Certify.Server/{Certify.Server.Api.Public.Tests => Certify.Server.Hub.Api.Tests}/Certify.Server.Api.Public.Tests.csproj (82%) rename src/Certify.Server/{Certify.Server.Api.Public.Tests => Certify.Server.Hub.Api.Tests}/Properties/launchSettings.json (85%) rename src/Certify.Server/{Certify.Server.Api.Public.Tests => Certify.Server.Hub.Api.Tests}/SystemTests.cs (100%) rename src/Certify.Server/{Certify.Server.Api.Public.Tests => Certify.Server.Hub.Api.Tests}/appsettings.api.public.test.json (100%) rename src/Certify.Server/{Certify.Server.Api.Public.Tests => Certify.Server.Hub.Api.Tests}/appsettings.worker.test.json (100%) rename src/Certify.Server/{Certify.Server.Api.Public => Certify.Server.Hub.Api}/.config/dotnet-tools.json (100%) rename src/Certify.Server/{Certify.Server.Api.Public/Certify.Server.Api.Public.csproj => Certify.Server.Hub.Api/Certify.Server.Hub.Api.csproj} (100%) rename src/Certify.Server/{Certify.Server.Api.Public => Certify.Server.Hub.Api}/Controllers/internal/AccessController.cs (94%) rename src/Certify.Server/{Certify.Server.Api.Public => Certify.Server.Hub.Api}/Controllers/internal/ApiControllerBase.cs (96%) rename src/Certify.Server/{Certify.Server.Api.Public => Certify.Server.Hub.Api}/Controllers/internal/CertificateAuthorityController.cs (91%) rename src/Certify.Server/{Certify.Server.Api.Public => Certify.Server.Hub.Api}/Controllers/internal/ChallengeProviderController.cs (91%) rename src/Certify.Server/{Certify.Server.Api.Public => Certify.Server.Hub.Api}/Controllers/internal/DeploymentTaskController.cs (91%) rename src/Certify.Server/{Certify.Server.Api.Public => Certify.Server.Hub.Api}/Controllers/internal/HubController.cs (98%) rename src/Certify.Server/{Certify.Server.Api.Public => Certify.Server.Hub.Api}/Controllers/internal/PreviewController.cs (94%) rename src/Certify.Server/{Certify.Server.Api.Public => Certify.Server.Hub.Api}/Controllers/internal/StoredCredentialController.cs (91%) rename src/Certify.Server/{Certify.Server.Api.Public => Certify.Server.Hub.Api}/Controllers/internal/TargetController.cs (93%) rename src/Certify.Server/{Certify.Server.Api.Public => Certify.Server.Hub.Api}/Controllers/v1/AuthController.cs (96%) rename src/Certify.Server/{Certify.Server.Api.Public => Certify.Server.Hub.Api}/Controllers/v1/CertificateController.cs (99%) rename src/Certify.Server/{Certify.Server.Api.Public => Certify.Server.Hub.Api}/Controllers/v1/ManagedChallengeController.cs (97%) rename src/Certify.Server/{Certify.Server.Api.Public => Certify.Server.Hub.Api}/Controllers/v1/SystemController.cs (81%) rename src/Certify.Server/{Certify.Server.Api.Public => Certify.Server.Hub.Api}/Controllers/v1/ValidationController.cs (97%) rename src/Certify.Server/{Certify.Server.Api.Public => Certify.Server.Hub.Api}/Dockerfile (58%) rename src/Certify.Server/{Certify.Server.Api.Public => Certify.Server.Hub.Api}/Middleware/AuthenticationExtension.cs (97%) rename src/Certify.Server/{Certify.Server.Api.Public => Certify.Server.Hub.Api}/Program.cs (95%) rename src/Certify.Server/{Certify.Server.Api.Public => Certify.Server.Hub.Api}/Properties/launchSettings.json (97%) rename src/Certify.Server/{Certify.Server.Api.Public => Certify.Server.Hub.Api}/Services/JwtService.cs (98%) rename src/Certify.Server/{Certify.Server.Api.Public => Certify.Server.Hub.Api}/Services/ManagementAPI.cs (99%) rename src/Certify.Server/{Certify.Server.Api.Public => Certify.Server.Hub.Api}/Services/ManagementWorker.cs (96%) rename src/Certify.Server/{Certify.Server.Api.Public => Certify.Server.Hub.Api}/SignalR/ManagementHub/InstanceManagementHub.cs (99%) rename src/Certify.Server/{Certify.Server.Api.Public => Certify.Server.Hub.Api}/SignalR/ManagementHub/InstanceManagementStateProvider.cs (98%) rename src/Certify.Server/{Certify.Server.Api.Public => Certify.Server.Hub.Api}/SignalR/UserInterfaceStatusHub.cs (98%) rename src/Certify.Server/{Certify.Server.Api.Public => Certify.Server.Hub.Api}/Startup.cs (98%) rename src/Certify.Server/{Certify.Server.Api.Public => Certify.Server.Hub.Api}/appsettings.Development.json (100%) rename src/Certify.Server/{Certify.Server.Api.Public => Certify.Server.Hub.Api}/appsettings.json (89%) rename src/Certify.Server/{Certify.Server.Api.Public => Certify.Server.Hub.Api}/docs/readme.md (100%) diff --git a/src/Certify.Aspire/Certify.Aspire.AppHost/Certify.Aspire.AppHost.csproj b/src/Certify.Aspire/Certify.Aspire.AppHost/Certify.Aspire.AppHost.csproj index b79fdc93c..2dde524f8 100644 --- a/src/Certify.Aspire/Certify.Aspire.AppHost/Certify.Aspire.AppHost.csproj +++ b/src/Certify.Aspire/Certify.Aspire.AppHost/Certify.Aspire.AppHost.csproj @@ -14,7 +14,7 @@ - + diff --git a/src/Certify.Aspire/Certify.Aspire.AppHost/Program.cs b/src/Certify.Aspire/Certify.Aspire.AppHost/Program.cs index d12b890cc..7519fdf6a 100644 --- a/src/Certify.Aspire/Certify.Aspire.AppHost/Program.cs +++ b/src/Certify.Aspire/Certify.Aspire.AppHost/Program.cs @@ -1,6 +1,6 @@ var builder = DistributedApplication.CreateBuilder(args); -builder.AddProject("certifyserverapi"); +builder.AddProject("certifyserverhubapi"); builder.AddProject("certifyservercore"); diff --git a/src/Certify.Core.Service.sln b/src/Certify.Core.Service.sln index 410c05d24..271562c99 100644 --- a/src/Certify.Core.Service.sln +++ b/src/Certify.Core.Service.sln @@ -69,7 +69,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Certify.Providers.ACME.Anvi EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Certify.ACME.Anvil", "..\..\libs\anvil\src\Certify.ACME.Anvil\Certify.ACME.Anvil.csproj", "{443202E1-B6E5-4625-BC3E-B3CB54CF4055}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Certify.Server.Api.Public", "Certify.Server\Certify.Server.Api.Public\Certify.Server.Api.Public.csproj", "{2DB50C13-7535-4D01-8EA5-1839F1472D7B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Certify.Server.Hub.Api", "Certify.Server\Certify.Server.Hub.Api\Certify.Server.Hub.Api.csproj", "{2DB50C13-7535-4D01-8EA5-1839F1472D7B}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj index a9ef0addd..de14495b5 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj @@ -31,6 +31,6 @@ - + - \ No newline at end of file + diff --git a/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs b/src/Certify.Server/Certify.Server.Hub.Api.Client/Certify.Server.Hub.Api.Client.cs similarity index 99% rename from src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs rename to src/Certify.Server/Certify.Server.Hub.Api.Client/Certify.Server.Hub.Api.Client.cs index 79b2624e8..6e96f5338 100644 --- a/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.API.Public.cs +++ b/src/Certify.Server/Certify.Server.Hub.Api.Client/Certify.Server.Hub.Api.Client.cs @@ -26,7 +26,7 @@ #pragma warning disable 8625 // Disable "CS8625 Cannot convert null literal to non-nullable reference type" #pragma warning disable 8765 // Disable "CS8765 Nullability of type of parameter doesn't match overridden member (possibly because of nullability attributes)." -namespace Certify.API.Public +namespace Certify.Server.Hub.Api { using System = global::System; @@ -4558,22 +4558,22 @@ public virtual async System.Threading.Tasks.Task GetSystemVersionAs } /// - /// Check API is responding and can connect to background service + /// Check API is configured, responding and can connect to background service /// /// OK /// A server side error occurred. - public virtual System.Threading.Tasks.Task GetHealthAsync() + public virtual System.Threading.Tasks.Task GetHealthAsync() { return GetHealthAsync(System.Threading.CancellationToken.None); } /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// - /// Check API is responding and can connect to background service + /// Check API is configured, responding and can connect to background service /// /// OK /// A server side error occurred. - public virtual async System.Threading.Tasks.Task GetHealthAsync(System.Threading.CancellationToken cancellationToken) + public virtual async System.Threading.Tasks.Task GetHealthAsync(System.Threading.CancellationToken cancellationToken) { var client_ = _httpClient; var disposeClient_ = false; @@ -4614,7 +4614,7 @@ public virtual async System.Threading.Tasks.Task GetHealthAsync(System.T var status_ = (int)response_.StatusCode; if (status_ == 200) { - var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); + var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); diff --git a/src/Certify.Server/Certify.Server.Api.Public.Client/Certify.Server.Api.Public.Client.csproj b/src/Certify.Server/Certify.Server.Hub.Api.Client/Certify.Server.Hub.Api.Client.csproj similarity index 100% rename from src/Certify.Server/Certify.Server.Api.Public.Client/Certify.Server.Api.Public.Client.csproj rename to src/Certify.Server/Certify.Server.Hub.Api.Client/Certify.Server.Hub.Api.Client.csproj diff --git a/src/Certify.Server/Certify.Server.Api.Public.Client/nswag.json b/src/Certify.Server/Certify.Server.Hub.Api.Client/nswag.json similarity index 97% rename from src/Certify.Server/Certify.Server.Api.Public.Client/nswag.json rename to src/Certify.Server/Certify.Server.Hub.Api.Client/nswag.json index 9c857d408..0529f90ad 100644 --- a/src/Certify.Server/Certify.Server.Api.Public.Client/nswag.json +++ b/src/Certify.Server/Certify.Server.Hub.Api.Client/nswag.json @@ -67,7 +67,7 @@ "wrapResponseMethods": [], "generateResponseClasses": true, "responseClass": "SwaggerResponse", - "namespace": "Certify.API.Public", + "namespace": "Certify.Server.Hub.Api", "requiredPropertiesMustBeDefined": true, "dateType": "System.DateTimeOffset", "jsonConverters": null, @@ -110,8 +110,8 @@ "templateDirectory": null, "serviceHost": null, "serviceSchemes": null, - "output": "Certify.API.Public.cs", + "output": "Certify.Server.Hub.Api.Client.cs", "newLineBehavior": "Auto" } } -} \ No newline at end of file +} diff --git a/src/Certify.Server/Certify.Server.Api.Public.Client/readme.md b/src/Certify.Server/Certify.Server.Hub.Api.Client/readme.md similarity index 88% rename from src/Certify.Server/Certify.Server.Api.Public.Client/readme.md rename to src/Certify.Server/Certify.Server.Hub.Api.Client/readme.md index 9385e0f1f..44b408334 100644 --- a/src/Certify.Server/Certify.Server.Api.Public.Client/readme.md +++ b/src/Certify.Server/Certify.Server.Hub.Api.Client/readme.md @@ -1,4 +1,4 @@ -# Certify.Server.Api.Public.Client +# Certify.Server.Hub.Api.Client This is a an example C# client for the public API of Certify Server. The Client is currently generated using NSwagStudio (start public API in debug then run the tool to generate the updated client). diff --git a/src/Certify.Server/Certify.Server.Api.Public.Tests/APITestBase.cs b/src/Certify.Server/Certify.Server.Hub.Api.Tests/APITestBase.cs similarity index 100% rename from src/Certify.Server/Certify.Server.Api.Public.Tests/APITestBase.cs rename to src/Certify.Server/Certify.Server.Hub.Api.Tests/APITestBase.cs diff --git a/src/Certify.Server/Certify.Server.Api.Public.Tests/AccessTests.cs b/src/Certify.Server/Certify.Server.Hub.Api.Tests/AccessTests.cs similarity index 100% rename from src/Certify.Server/Certify.Server.Api.Public.Tests/AccessTests.cs rename to src/Certify.Server/Certify.Server.Hub.Api.Tests/AccessTests.cs diff --git a/src/Certify.Server/Certify.Server.Api.Public.Tests/AuthTests.cs b/src/Certify.Server/Certify.Server.Hub.Api.Tests/AuthTests.cs similarity index 100% rename from src/Certify.Server/Certify.Server.Api.Public.Tests/AuthTests.cs rename to src/Certify.Server/Certify.Server.Hub.Api.Tests/AuthTests.cs diff --git a/src/Certify.Server/Certify.Server.Api.Public.Tests/CertificateTests.cs b/src/Certify.Server/Certify.Server.Hub.Api.Tests/CertificateTests.cs similarity index 100% rename from src/Certify.Server/Certify.Server.Api.Public.Tests/CertificateTests.cs rename to src/Certify.Server/Certify.Server.Hub.Api.Tests/CertificateTests.cs diff --git a/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj b/src/Certify.Server/Certify.Server.Hub.Api.Tests/Certify.Server.Api.Public.Tests.csproj similarity index 82% rename from src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj rename to src/Certify.Server/Certify.Server.Hub.Api.Tests/Certify.Server.Api.Public.Tests.csproj index ed075a0ce..8960e27de 100644 --- a/src/Certify.Server/Certify.Server.Api.Public.Tests/Certify.Server.Api.Public.Tests.csproj +++ b/src/Certify.Server/Certify.Server.Hub.Api.Tests/Certify.Server.Api.Public.Tests.csproj @@ -22,8 +22,8 @@ - - + + diff --git a/src/Certify.Server/Certify.Server.Api.Public.Tests/Properties/launchSettings.json b/src/Certify.Server/Certify.Server.Hub.Api.Tests/Properties/launchSettings.json similarity index 85% rename from src/Certify.Server/Certify.Server.Api.Public.Tests/Properties/launchSettings.json rename to src/Certify.Server/Certify.Server.Hub.Api.Tests/Properties/launchSettings.json index 83c4de950..61a4a858f 100644 --- a/src/Certify.Server/Certify.Server.Api.Public.Tests/Properties/launchSettings.json +++ b/src/Certify.Server/Certify.Server.Hub.Api.Tests/Properties/launchSettings.json @@ -1,6 +1,6 @@ { "profiles": { - "Certify.Server.Api.Public.Tests": { + "Certify.Server.Hub.Api.Tests": { "commandName": "Project", "launchBrowser": true, "environmentVariables": { diff --git a/src/Certify.Server/Certify.Server.Api.Public.Tests/SystemTests.cs b/src/Certify.Server/Certify.Server.Hub.Api.Tests/SystemTests.cs similarity index 100% rename from src/Certify.Server/Certify.Server.Api.Public.Tests/SystemTests.cs rename to src/Certify.Server/Certify.Server.Hub.Api.Tests/SystemTests.cs diff --git a/src/Certify.Server/Certify.Server.Api.Public.Tests/appsettings.api.public.test.json b/src/Certify.Server/Certify.Server.Hub.Api.Tests/appsettings.api.public.test.json similarity index 100% rename from src/Certify.Server/Certify.Server.Api.Public.Tests/appsettings.api.public.test.json rename to src/Certify.Server/Certify.Server.Hub.Api.Tests/appsettings.api.public.test.json diff --git a/src/Certify.Server/Certify.Server.Api.Public.Tests/appsettings.worker.test.json b/src/Certify.Server/Certify.Server.Hub.Api.Tests/appsettings.worker.test.json similarity index 100% rename from src/Certify.Server/Certify.Server.Api.Public.Tests/appsettings.worker.test.json rename to src/Certify.Server/Certify.Server.Hub.Api.Tests/appsettings.worker.test.json diff --git a/src/Certify.Server/Certify.Server.Api.Public/.config/dotnet-tools.json b/src/Certify.Server/Certify.Server.Hub.Api/.config/dotnet-tools.json similarity index 100% rename from src/Certify.Server/Certify.Server.Api.Public/.config/dotnet-tools.json rename to src/Certify.Server/Certify.Server.Hub.Api/.config/dotnet-tools.json diff --git a/src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj b/src/Certify.Server/Certify.Server.Hub.Api/Certify.Server.Hub.Api.csproj similarity index 100% rename from src/Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj rename to src/Certify.Server/Certify.Server.Hub.Api/Certify.Server.Hub.Api.csproj diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/AccessController.cs b/src/Certify.Server/Certify.Server.Hub.Api/Controllers/internal/AccessController.cs similarity index 94% rename from src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/AccessController.cs rename to src/Certify.Server/Certify.Server.Hub.Api/Controllers/internal/AccessController.cs index 91feadc4d..40b50db9e 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/AccessController.cs +++ b/src/Certify.Server/Certify.Server.Hub.Api/Controllers/internal/AccessController.cs @@ -1,7 +1,7 @@ using Certify.Client; using Microsoft.AspNetCore.Mvc; -namespace Certify.Server.Api.Public.Controllers +namespace Certify.Server.Hub.Api.Controllers { /// /// Internal API controller for access related admin. Some controller endpoints may be Source Generated by Certify.SourceGenerators. diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/ApiControllerBase.cs b/src/Certify.Server/Certify.Server.Hub.Api/Controllers/internal/ApiControllerBase.cs similarity index 96% rename from src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/ApiControllerBase.cs rename to src/Certify.Server/Certify.Server.Hub.Api/Controllers/internal/ApiControllerBase.cs index e34d7d4a2..38836932c 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/ApiControllerBase.cs +++ b/src/Certify.Server/Certify.Server.Hub.Api/Controllers/internal/ApiControllerBase.cs @@ -5,7 +5,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Caching.Memory; -namespace Certify.Server.Api.Public.Controllers +namespace Certify.Server.Hub.Api.Controllers { /// /// Base class for public api controllers @@ -90,7 +90,7 @@ internal AuthContext? CurrentAuthContext try { var _config = HttpContext.RequestServices.GetRequiredService(); - var jwt = new Api.Public.Services.JwtService(_config); + var jwt = new Hub.Api.Services.JwtService(_config); var claimsIdentity = jwt.ClaimsIdentityFromTokenAsync(authToken, false).Result; var userId = claimsIdentity.FindFirst(ClaimTypes.Sid)?.Value; diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/CertificateAuthorityController.cs b/src/Certify.Server/Certify.Server.Hub.Api/Controllers/internal/CertificateAuthorityController.cs similarity index 91% rename from src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/CertificateAuthorityController.cs rename to src/Certify.Server/Certify.Server.Hub.Api/Controllers/internal/CertificateAuthorityController.cs index e3c8b55bb..1b186cb49 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/CertificateAuthorityController.cs +++ b/src/Certify.Server/Certify.Server.Hub.Api/Controllers/internal/CertificateAuthorityController.cs @@ -1,8 +1,8 @@ using Certify.Client; -using Certify.Server.Api.Public.Services; +using Certify.Server.Hub.Api.Services; using Microsoft.AspNetCore.Mvc; -namespace Certify.Server.Api.Public.Controllers +namespace Certify.Server.Hub.Api.Controllers { /// /// Internal API for extended certificate management. Not intended for general use. diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/ChallengeProviderController.cs b/src/Certify.Server/Certify.Server.Hub.Api/Controllers/internal/ChallengeProviderController.cs similarity index 91% rename from src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/ChallengeProviderController.cs rename to src/Certify.Server/Certify.Server.Hub.Api/Controllers/internal/ChallengeProviderController.cs index 0d6c4f449..2822ecf82 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/ChallengeProviderController.cs +++ b/src/Certify.Server/Certify.Server.Hub.Api/Controllers/internal/ChallengeProviderController.cs @@ -1,8 +1,8 @@ using Certify.Client; -using Certify.Server.Api.Public.Services; +using Certify.Server.Hub.Api.Services; using Microsoft.AspNetCore.Mvc; -namespace Certify.Server.Api.Public.Controllers +namespace Certify.Server.Hub.Api.Controllers { /// /// Internal API for extended certificate management. Not intended for general use. diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/DeploymentTaskController.cs b/src/Certify.Server/Certify.Server.Hub.Api/Controllers/internal/DeploymentTaskController.cs similarity index 91% rename from src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/DeploymentTaskController.cs rename to src/Certify.Server/Certify.Server.Hub.Api/Controllers/internal/DeploymentTaskController.cs index 2a8dc75bf..9726743f6 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/DeploymentTaskController.cs +++ b/src/Certify.Server/Certify.Server.Hub.Api/Controllers/internal/DeploymentTaskController.cs @@ -1,8 +1,8 @@ using Certify.Client; -using Certify.Server.Api.Public.Services; +using Certify.Server.Hub.Api.Services; using Microsoft.AspNetCore.Mvc; -namespace Certify.Server.Api.Public.Controllers +namespace Certify.Server.Hub.Api.Controllers { /// /// Internal API for extended certificate management. Not intended for general use. diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/HubController.cs b/src/Certify.Server/Certify.Server.Hub.Api/Controllers/internal/HubController.cs similarity index 98% rename from src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/HubController.cs rename to src/Certify.Server/Certify.Server.Hub.Api/Controllers/internal/HubController.cs index 3ec28cef0..7ba38cdc5 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/HubController.cs +++ b/src/Certify.Server/Certify.Server.Hub.Api/Controllers/internal/HubController.cs @@ -1,12 +1,12 @@ using Certify.Client; using Certify.Models.Hub; -using Certify.Server.Api.Public.SignalR.ManagementHub; +using Certify.Server.Hub.Api.SignalR.ManagementHub; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.SignalR; -namespace Certify.Server.Api.Public.Controllers +namespace Certify.Server.Hub.Api.Controllers { /// /// Provides managed certificate related operations diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/PreviewController.cs b/src/Certify.Server/Certify.Server.Hub.Api/Controllers/internal/PreviewController.cs similarity index 94% rename from src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/PreviewController.cs rename to src/Certify.Server/Certify.Server.Hub.Api/Controllers/internal/PreviewController.cs index c6db096cf..04d38f868 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/PreviewController.cs +++ b/src/Certify.Server/Certify.Server.Hub.Api/Controllers/internal/PreviewController.cs @@ -1,10 +1,10 @@ using Certify.Models; -using Certify.Server.Api.Public.Services; +using Certify.Server.Hub.Api.Services; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace Certify.Server.Api.Public.Controllers +namespace Certify.Server.Hub.Api.Controllers { /// /// Internal API for extended certificate management. Not intended for general use. diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/StoredCredentialController.cs b/src/Certify.Server/Certify.Server.Hub.Api/Controllers/internal/StoredCredentialController.cs similarity index 91% rename from src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/StoredCredentialController.cs rename to src/Certify.Server/Certify.Server.Hub.Api/Controllers/internal/StoredCredentialController.cs index 243eb339b..a2ff59eeb 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/StoredCredentialController.cs +++ b/src/Certify.Server/Certify.Server.Hub.Api/Controllers/internal/StoredCredentialController.cs @@ -1,8 +1,8 @@ using Certify.Client; -using Certify.Server.Api.Public.Services; +using Certify.Server.Hub.Api.Services; using Microsoft.AspNetCore.Mvc; -namespace Certify.Server.Api.Public.Controllers +namespace Certify.Server.Hub.Api.Controllers { /// /// Internal API for extended certificate management. Not intended for general use. diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/TargetController.cs b/src/Certify.Server/Certify.Server.Hub.Api/Controllers/internal/TargetController.cs similarity index 93% rename from src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/TargetController.cs rename to src/Certify.Server/Certify.Server.Hub.Api/Controllers/internal/TargetController.cs index c5d85196e..ae6074304 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/internal/TargetController.cs +++ b/src/Certify.Server/Certify.Server.Hub.Api/Controllers/internal/TargetController.cs @@ -1,9 +1,9 @@ using Certify.Client; using Certify.Models; -using Certify.Server.Api.Public.Services; +using Certify.Server.Hub.Api.Services; using Microsoft.AspNetCore.Mvc; -namespace Certify.Server.Api.Public.Controllers +namespace Certify.Server.Hub.Api.Controllers { /// /// Internal API for extended certificate management. Not intended for general use. diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/AuthController.cs b/src/Certify.Server/Certify.Server.Hub.Api/Controllers/v1/AuthController.cs similarity index 96% rename from src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/AuthController.cs rename to src/Certify.Server/Certify.Server.Hub.Api/Controllers/v1/AuthController.cs index 18e9dac70..0490b52a8 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/AuthController.cs +++ b/src/Certify.Server/Certify.Server.Hub.Api/Controllers/v1/AuthController.cs @@ -6,7 +6,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace Certify.Server.Api.Public.Controllers +namespace Certify.Server.Hub.Api.Controllers { /// /// Provides auth related operations @@ -62,7 +62,7 @@ public async Task Login(AuthRequest login) { // TODO: get user details from API and return as part of response instead of returning as json - var jwt = new Api.Public.Services.JwtService(_config); + var jwt = new Hub.Api.Services.JwtService(_config); var authResponse = new AuthResponse { @@ -104,7 +104,7 @@ public async Task Refresh(string refreshToken) try { // validate token and issue new one - var jwt = new Api.Public.Services.JwtService(_config); + var jwt = new Hub.Api.Services.JwtService(_config); var claimsIdentity = await jwt.ClaimsIdentityFromTokenAsync(authToken, false); var userId = claimsIdentity.FindFirst(ClaimTypes.Sid)?.Value; diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs b/src/Certify.Server/Certify.Server.Hub.Api/Controllers/v1/CertificateController.cs similarity index 99% rename from src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs rename to src/Certify.Server/Certify.Server.Hub.Api/Controllers/v1/CertificateController.cs index 776fcb16a..1d103aeaa 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/CertificateController.cs +++ b/src/Certify.Server/Certify.Server.Hub.Api/Controllers/v1/CertificateController.cs @@ -2,12 +2,12 @@ using Certify.Client; using Certify.Models.Hub; using Certify.Models.Reporting; -using Certify.Server.Api.Public.Services; +using Certify.Server.Hub.Api.Services; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace Certify.Server.Api.Public.Controllers +namespace Certify.Server.Hub.Api.Controllers { /// /// Provides managed certificate related operations diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/ManagedChallengeController.cs b/src/Certify.Server/Certify.Server.Hub.Api/Controllers/v1/ManagedChallengeController.cs similarity index 97% rename from src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/ManagedChallengeController.cs rename to src/Certify.Server/Certify.Server.Hub.Api/Controllers/v1/ManagedChallengeController.cs index 8c2db0d7a..c91d43cf1 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/ManagedChallengeController.cs +++ b/src/Certify.Server/Certify.Server.Hub.Api/Controllers/v1/ManagedChallengeController.cs @@ -3,7 +3,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace Certify.Server.Api.Public.Controllers +namespace Certify.Server.Hub.Api.Controllers { /// /// Provides managed challenges such as DNS challenges on behalf of other ACME clients diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/SystemController.cs b/src/Certify.Server/Certify.Server.Hub.Api/Controllers/v1/SystemController.cs similarity index 81% rename from src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/SystemController.cs rename to src/Certify.Server/Certify.Server.Hub.Api/Controllers/v1/SystemController.cs index cee67f72e..b2f44d180 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/SystemController.cs +++ b/src/Certify.Server/Certify.Server.Hub.Api/Controllers/v1/SystemController.cs @@ -2,7 +2,7 @@ using Certify.Models.Hub; using Microsoft.AspNetCore.Mvc; -namespace Certify.Server.Api.Public.Controllers +namespace Certify.Server.Hub.Api.Controllers { /// /// Provides general system level information (version etc) @@ -42,12 +42,12 @@ public async Task GetSystemVersion() } /// - /// Check API is responding and can connect to background service + /// Check API is configured, responding and can connect to background service /// /// [HttpGet] [Route("health")] - [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(object))] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(HubHealth))] public async Task GetHealth() { var serviceAvailable = false; @@ -60,9 +60,9 @@ public async Task GetHealth() catch { } #if DEBUG - var health = new { API = "OK", Service = versionInfo, ServiceAvailable = serviceAvailable, env = Environment.GetEnvironmentVariables() }; + var health = new HubHealth { Status = "OK", Version = versionInfo, ServiceAvailable = serviceAvailable, env = Environment.GetEnvironmentVariables() }; #else - var health = new { API = "OK", Service = versionInfo, ServiceAvailable = serviceAvailable}; + var health = new HubHealth { Status = "OK", Version = versionInfo, ServiceAvailable = serviceAvailable }; #endif return new OkObjectResult(health); diff --git a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/ValidationController.cs b/src/Certify.Server/Certify.Server.Hub.Api/Controllers/v1/ValidationController.cs similarity index 97% rename from src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/ValidationController.cs rename to src/Certify.Server/Certify.Server.Hub.Api/Controllers/v1/ValidationController.cs index fa1aabf02..90ec17c85 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Controllers/v1/ValidationController.cs +++ b/src/Certify.Server/Certify.Server.Hub.Api/Controllers/v1/ValidationController.cs @@ -3,7 +3,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace Certify.Server.Api.Public.Controllers +namespace Certify.Server.Hub.Api.Controllers { /// /// Provides operations related to identifier validation challenges (proof of domain control etc) diff --git a/src/Certify.Server/Certify.Server.Api.Public/Dockerfile b/src/Certify.Server/Certify.Server.Hub.Api/Dockerfile similarity index 58% rename from src/Certify.Server/Certify.Server.Api.Public/Dockerfile rename to src/Certify.Server/Certify.Server.Hub.Api/Dockerfile index a9b4d78e0..56e7d3480 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Dockerfile +++ b/src/Certify.Server/Certify.Server.Hub.Api/Dockerfile @@ -1,6 +1,6 @@ #See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging. -FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base +FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base # grant write to store settings path before switching to app user RUN mkdir /usr/share/certify && chown -R app:app /usr/share/certify @@ -13,21 +13,21 @@ EXPOSE 44360 FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build ARG BUILD_CONFIGURATION=Release WORKDIR /src -COPY ["Certify.Server/Certify.Server.Api.Public/Certify.Server.Api.Public.csproj", "Certify.Server/Certify.Server.Api.Public/"] +COPY ["Certify.Server/Certify.Server.Hub.Api/Certify.Server.Hub.Api.csproj", "Certify.Server/Certify.Server.Hub.Api/"] COPY ["Certify.Client/Certify.Client.csproj", "Certify.Client/"] COPY ["Certify.Locales/Certify.Locales.csproj", "Certify.Locales/"] COPY ["Certify.Models/Certify.Models.csproj", "Certify.Models/"] COPY ["Certify.Shared/Certify.Shared.Core.csproj", "Certify.Shared/"] -RUN dotnet restore "./Certify.Server/Certify.Server.Api.Public/./Certify.Server.Api.Public.csproj" +RUN dotnet restore "./Certify.Server/Certify.Server.Hub.Api/./Certify.Server.Hub.Api.csproj" COPY . . -WORKDIR "/src/Certify.Server/Certify.Server.Api.Public" -RUN dotnet build "./Certify.Server.Api.Public.csproj" -c $BUILD_CONFIGURATION -o /app/build +WORKDIR "/src/Certify.Server/Certify.Server.Hub.Api" +RUN dotnet build "./Certify.Server.Hub.Api.csproj" -c $BUILD_CONFIGURATION -o /app/build FROM build AS publish ARG BUILD_CONFIGURATION=Release -RUN dotnet publish "./Certify.Server.Api.Public.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false +RUN dotnet publish "./Certify.Server.Hub.Api.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false FROM base AS final WORKDIR /app COPY --from=publish /app/publish . -ENTRYPOINT ["dotnet", "Certify.Server.Api.Public.dll"] +ENTRYPOINT ["dotnet", "Certify.Server.Hub.Api.dll"] diff --git a/src/Certify.Server/Certify.Server.Api.Public/Middleware/AuthenticationExtension.cs b/src/Certify.Server/Certify.Server.Hub.Api/Middleware/AuthenticationExtension.cs similarity index 97% rename from src/Certify.Server/Certify.Server.Api.Public/Middleware/AuthenticationExtension.cs rename to src/Certify.Server/Certify.Server.Hub.Api/Middleware/AuthenticationExtension.cs index 990ee518f..0d73f2b62 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Middleware/AuthenticationExtension.cs +++ b/src/Certify.Server/Certify.Server.Hub.Api/Middleware/AuthenticationExtension.cs @@ -2,7 +2,7 @@ using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.IdentityModel.Tokens; -namespace Certify.Server.Api.Public.Middleware +namespace Certify.Server.Hub.Api.Middleware { /// /// Provides authentication related extensions diff --git a/src/Certify.Server/Certify.Server.Api.Public/Program.cs b/src/Certify.Server/Certify.Server.Hub.Api/Program.cs similarity index 95% rename from src/Certify.Server/Certify.Server.Api.Public/Program.cs rename to src/Certify.Server/Certify.Server.Hub.Api/Program.cs index 783a1dcf1..c998a4162 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Program.cs +++ b/src/Certify.Server/Certify.Server.Hub.Api/Program.cs @@ -1,4 +1,4 @@ -using Certify.Server.API; +using Certify.Server.Hub.Api; var builder = WebApplication.CreateBuilder(args); diff --git a/src/Certify.Server/Certify.Server.Api.Public/Properties/launchSettings.json b/src/Certify.Server/Certify.Server.Hub.Api/Properties/launchSettings.json similarity index 97% rename from src/Certify.Server/Certify.Server.Api.Public/Properties/launchSettings.json rename to src/Certify.Server/Certify.Server.Hub.Api/Properties/launchSettings.json index e6a49d2e6..8d2d878ca 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Properties/launchSettings.json +++ b/src/Certify.Server/Certify.Server.Hub.Api/Properties/launchSettings.json @@ -9,7 +9,7 @@ "CERTIFY_SERVICE_PORT": "9695" } }, - "Certify.Server.Api.Public": { + "Certify.Server.Hub.Api": { "commandName": "Project", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development", diff --git a/src/Certify.Server/Certify.Server.Api.Public/Services/JwtService.cs b/src/Certify.Server/Certify.Server.Hub.Api/Services/JwtService.cs similarity index 98% rename from src/Certify.Server/Certify.Server.Api.Public/Services/JwtService.cs rename to src/Certify.Server/Certify.Server.Hub.Api/Services/JwtService.cs index 26a76c046..2758ecba9 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Services/JwtService.cs +++ b/src/Certify.Server/Certify.Server.Hub.Api/Services/JwtService.cs @@ -4,7 +4,7 @@ using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.Tokens; -namespace Certify.Server.Api.Public.Services +namespace Certify.Server.Hub.Api.Services { // https://www.c-sharpcorner.com/article/implement-jwt-in-asp-net-core-3-1/ // https://www.blinkingcaret.com/2018/05/30/refresh-tokens-in-asp-net-core-web-api/ diff --git a/src/Certify.Server/Certify.Server.Api.Public/Services/ManagementAPI.cs b/src/Certify.Server/Certify.Server.Hub.Api/Services/ManagementAPI.cs similarity index 99% rename from src/Certify.Server/Certify.Server.Api.Public/Services/ManagementAPI.cs rename to src/Certify.Server/Certify.Server.Hub.Api/Services/ManagementAPI.cs index 8ae438a8f..5cd42324c 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Services/ManagementAPI.cs +++ b/src/Certify.Server/Certify.Server.Hub.Api/Services/ManagementAPI.cs @@ -5,10 +5,10 @@ using Certify.Models.Hub; using Certify.Models.Providers; using Certify.Models.Reporting; -using Certify.Server.Api.Public.SignalR.ManagementHub; +using Certify.Server.Hub.Api.SignalR.ManagementHub; using Microsoft.AspNetCore.SignalR; -namespace Certify.Server.Api.Public.Services +namespace Certify.Server.Hub.Api.Services { /// /// Management Hub API diff --git a/src/Certify.Server/Certify.Server.Api.Public/Services/ManagementWorker.cs b/src/Certify.Server/Certify.Server.Hub.Api/Services/ManagementWorker.cs similarity index 96% rename from src/Certify.Server/Certify.Server.Api.Public/Services/ManagementWorker.cs rename to src/Certify.Server/Certify.Server.Hub.Api/Services/ManagementWorker.cs index 8f62d7a66..3ef75d039 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Services/ManagementWorker.cs +++ b/src/Certify.Server/Certify.Server.Hub.Api/Services/ManagementWorker.cs @@ -1,8 +1,8 @@ using Certify.Models.Hub; -using Certify.Server.Api.Public.SignalR.ManagementHub; +using Certify.Server.Hub.Api.SignalR.ManagementHub; using Microsoft.AspNetCore.SignalR; -namespace Certify.Server.Api.Public.Services +namespace Certify.Server.Hub.Api.Services { public class ManagementWorker : IHostedService, IDisposable { diff --git a/src/Certify.Server/Certify.Server.Api.Public/SignalR/ManagementHub/InstanceManagementHub.cs b/src/Certify.Server/Certify.Server.Hub.Api/SignalR/ManagementHub/InstanceManagementHub.cs similarity index 99% rename from src/Certify.Server/Certify.Server.Api.Public/SignalR/ManagementHub/InstanceManagementHub.cs rename to src/Certify.Server/Certify.Server.Hub.Api/SignalR/ManagementHub/InstanceManagementHub.cs index 5b1016b58..66fea38e9 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/SignalR/ManagementHub/InstanceManagementHub.cs +++ b/src/Certify.Server/Certify.Server.Hub.Api/SignalR/ManagementHub/InstanceManagementHub.cs @@ -2,7 +2,7 @@ using Certify.Models.Reporting; using Microsoft.AspNetCore.SignalR; -namespace Certify.Server.Api.Public.SignalR.ManagementHub +namespace Certify.Server.Hub.Api.SignalR.ManagementHub { /// /// Interface for instance management hub events diff --git a/src/Certify.Server/Certify.Server.Api.Public/SignalR/ManagementHub/InstanceManagementStateProvider.cs b/src/Certify.Server/Certify.Server.Hub.Api/SignalR/ManagementHub/InstanceManagementStateProvider.cs similarity index 98% rename from src/Certify.Server/Certify.Server.Api.Public/SignalR/ManagementHub/InstanceManagementStateProvider.cs rename to src/Certify.Server/Certify.Server.Hub.Api/SignalR/ManagementHub/InstanceManagementStateProvider.cs index 684f86d7c..6845a5185 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/SignalR/ManagementHub/InstanceManagementStateProvider.cs +++ b/src/Certify.Server/Certify.Server.Hub.Api/SignalR/ManagementHub/InstanceManagementStateProvider.cs @@ -1,9 +1,9 @@ -using System.Collections.Concurrent; +using System.Collections.Concurrent; using Certify.Models; using Certify.Models.Hub; using Certify.Models.Reporting; -namespace Certify.Server.Api.Public.SignalR.ManagementHub +namespace Certify.Server.Hub.Api.SignalR.ManagementHub { public interface IInstanceManagementStateProvider { diff --git a/src/Certify.Server/Certify.Server.Api.Public/SignalR/UserInterfaceStatusHub.cs b/src/Certify.Server/Certify.Server.Hub.Api/SignalR/UserInterfaceStatusHub.cs similarity index 98% rename from src/Certify.Server/Certify.Server.Api.Public/SignalR/UserInterfaceStatusHub.cs rename to src/Certify.Server/Certify.Server.Hub.Api/SignalR/UserInterfaceStatusHub.cs index fc2200381..0772bee8c 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/SignalR/UserInterfaceStatusHub.cs +++ b/src/Certify.Server/Certify.Server.Hub.Api/SignalR/UserInterfaceStatusHub.cs @@ -3,7 +3,7 @@ using Certify.Providers; using Microsoft.AspNetCore.SignalR; -namespace Certify.Server.Api.Public.SignalR +namespace Certify.Server.Hub.Api.SignalR { /// /// Forwards status messages via SignalR back to UI client(s) diff --git a/src/Certify.Server/Certify.Server.Api.Public/Startup.cs b/src/Certify.Server/Certify.Server.Hub.Api/Startup.cs similarity index 98% rename from src/Certify.Server/Certify.Server.Api.Public/Startup.cs rename to src/Certify.Server/Certify.Server.Hub.Api/Startup.cs index 9e48e70bd..c0ff13721 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/Startup.cs +++ b/src/Certify.Server/Certify.Server.Hub.Api/Startup.cs @@ -1,16 +1,16 @@ using System.Reflection; using Certify.Client; -using Certify.Server.Api.Public.Middleware; -using Certify.Server.Api.Public.Services; -using Certify.Server.Api.Public.SignalR; -using Certify.Server.Api.Public.SignalR.ManagementHub; +using Certify.Server.Hub.Api.Middleware; +using Certify.Server.Hub.Api.Services; +using Certify.Server.Hub.Api.SignalR; +using Certify.Server.Hub.Api.SignalR.ManagementHub; using Certify.SharedUtils; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.ResponseCompression; using Microsoft.AspNetCore.SignalR; using Microsoft.OpenApi.Models; -namespace Certify.Server.API +namespace Certify.Server.Hub.Api { /// diff --git a/src/Certify.Server/Certify.Server.Api.Public/appsettings.Development.json b/src/Certify.Server/Certify.Server.Hub.Api/appsettings.Development.json similarity index 100% rename from src/Certify.Server/Certify.Server.Api.Public/appsettings.Development.json rename to src/Certify.Server/Certify.Server.Hub.Api/appsettings.Development.json diff --git a/src/Certify.Server/Certify.Server.Api.Public/appsettings.json b/src/Certify.Server/Certify.Server.Hub.Api/appsettings.json similarity index 89% rename from src/Certify.Server/Certify.Server.Api.Public/appsettings.json rename to src/Certify.Server/Certify.Server.Hub.Api/appsettings.json index c1f97dfd3..7675acb21 100644 --- a/src/Certify.Server/Certify.Server.Api.Public/appsettings.json +++ b/src/Certify.Server/Certify.Server.Hub.Api/appsettings.json @@ -11,6 +11,6 @@ "secret": "8FdYdFZKb2gQz7c4hpX7BMKpEnrpGhI7APd7GHMdvGg", "refreshTokenExpirationInDays": 1, "authTokenExpirationInMinutes": 60, - "issuer": "Certify.Api.Public" + "issuer": "Certify.Server.Hub.Api" } } diff --git a/src/Certify.Server/Certify.Server.Api.Public/docs/readme.md b/src/Certify.Server/Certify.Server.Hub.Api/docs/readme.md similarity index 100% rename from src/Certify.Server/Certify.Server.Api.Public/docs/readme.md rename to src/Certify.Server/Certify.Server.Hub.Api/docs/readme.md diff --git a/src/Certify.SourceGenerators/PublicAPISourceGenerator.cs b/src/Certify.SourceGenerators/PublicAPISourceGenerator.cs index fac0a6b4f..388ea23b2 100644 --- a/src/Certify.SourceGenerators/PublicAPISourceGenerator.cs +++ b/src/Certify.SourceGenerators/PublicAPISourceGenerator.cs @@ -55,7 +55,7 @@ public void Execute(GeneratorExecutionContext context) var apiParamCall = paramSet.Any() ? string.Join(", ", paramSet.Select(p => $"{p.Key}")) : ""; var apiParamCallWithoutAuthContext = config.Params.Any() ? string.Join(", ", config.Params.Select(p => $"{p.Key}")) : ""; - if (context.Compilation.AssemblyName.EndsWith("Api.Public") && config.PublicAPIController != null) + if (context.Compilation.AssemblyName.EndsWith("Hub.Api") && config.PublicAPIController != null) { ImplementPublicAPI(context, config, apiParamDeclWithoutAuthContext, apiParamDecl, apiParamCall); } @@ -100,7 +100,7 @@ private static void ImplementPublicAPI(GeneratorExecutionContext context, Genera var publicApiSrc = $@" using Certify.Client; - using Certify.Server.Api.Public.Controllers; + using Certify.Server.Hub.Api.Controllers; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; using System.Collections.Generic; @@ -112,7 +112,7 @@ private static void ImplementPublicAPI(GeneratorExecutionContext context, Genera using Certify.Models.Hub; - namespace Certify.Server.Api.Public.Controllers + namespace Certify.Server.Hub.Api.Controllers {{ public partial class {config.PublicAPIController}Controller {{ @@ -174,7 +174,7 @@ public partial class {config.PublicAPIController}Controller using Certify.Models.Reporting; using Microsoft.AspNetCore.SignalR; - namespace Certify.Server.Api.Public.Services + namespace Certify.Server.Hub.Api.Services {{ public partial class ManagementAPI {{ From 83c8910dee3686e885f3fd52648c13b2ccab51a2 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Fri, 31 Jan 2025 13:51:37 +0800 Subject: [PATCH 308/328] Rename IsManagementHub setting to IsManagementHubService --- .../Management/CertifyManager/CertifyManager.Maintenance.cs | 4 ++-- src/Certify.Core/Management/SettingsManager.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.Maintenance.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.Maintenance.cs index 12d626f0e..f86babc42 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.Maintenance.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.Maintenance.cs @@ -39,14 +39,14 @@ private async Task UpgradeSettings() if (Environment.GetEnvironmentVariable("CERTIFY_ENABLE_MANAGEMENT_HUB")?.Equals("true", StringComparison.InvariantCultureIgnoreCase) == true) { - CoreAppSettings.Current.IsManagementHub = true; + CoreAppSettings.Current.IsManagementHubService = true; } SettingsManager.SaveAppSettings(); var accessControl = await GetCurrentAccessControl(); - if (CoreAppSettings.Current.IsManagementHub) + if (CoreAppSettings.Current.IsManagementHubService) { if (await accessControl.IsInitialized() == false) { diff --git a/src/Certify.Core/Management/SettingsManager.cs b/src/Certify.Core/Management/SettingsManager.cs index 77a81df9c..7f31f977d 100644 --- a/src/Certify.Core/Management/SettingsManager.cs +++ b/src/Certify.Core/Management/SettingsManager.cs @@ -184,7 +184,7 @@ public static CoreAppSettings Current /// /// if true, additional management hub features and data stores may be enabled /// - public bool IsManagementHub { get; set; } + public bool IsManagementHubService { get; set; } } public class SettingsManager From 5c1153455eb7ec52db28c8b42f828860327cb2ca Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Tue, 4 Feb 2025 16:26:23 +0800 Subject: [PATCH 309/328] Mgmt hub: connect to hub after init as completed, refactoring --- src/Certify.Client/IManagementServerClient.cs | 21 ++++++++++ src/Certify.Client/ManagementServerClient.cs | 2 +- .../CertifyManager.ManagementHub.cs | 32 ++++++++------- .../CertifyManager/CertifyManager.cs | 16 +++++++- .../CertifyManager/ICertifyManager.cs | 40 +++---------------- .../Certify.Server.Core/Program.cs | 4 +- ...json => appsettings-core.Development.json} | 0 ...appsettings.json => appsettings-core.json} | 0 .../Certify.Server.Hub.Api/Program.cs | 2 + .../InstanceManagementStateProvider.cs | 14 +++++++ .../Certify.Server.Hub.Api/Startup.cs | 9 +---- ....json => appsettings-api.Development.json} | 0 ...{appsettings.json => appsettings-api.json} | 0 13 files changed, 81 insertions(+), 59 deletions(-) create mode 100644 src/Certify.Client/IManagementServerClient.cs rename src/Certify.Server/Certify.Server.Core/Certify.Server.Core/{appsettings.Development.json => appsettings-core.Development.json} (100%) rename src/Certify.Server/Certify.Server.Core/Certify.Server.Core/{appsettings.json => appsettings-core.json} (100%) rename src/Certify.Server/Certify.Server.Hub.Api/{appsettings.Development.json => appsettings-api.Development.json} (100%) rename src/Certify.Server/Certify.Server.Hub.Api/{appsettings.json => appsettings-api.json} (100%) diff --git a/src/Certify.Client/IManagementServerClient.cs b/src/Certify.Client/IManagementServerClient.cs new file mode 100644 index 000000000..1f40a555f --- /dev/null +++ b/src/Certify.Client/IManagementServerClient.cs @@ -0,0 +1,21 @@ +using System; +using System.Threading.Tasks; +using Certify.Models.Hub; + +namespace Certify.Client +{ + public interface IManagementServerClient + { + event Action OnConnectionClosed; + event Action OnConnectionReconnected; + event Action OnConnectionReconnecting; + event Func> OnGetCommandResult; + event Func OnGetInstanceItems; + + Task ConnectAsync(); + Task Disconnect(); + bool IsConnected(); + void SendInstanceInfo(Guid commandId, bool isCommandResponse = true); + void SendNotificationToManagementHub(string msgCommandType, object updateMsg); + } +} \ No newline at end of file diff --git a/src/Certify.Client/ManagementServerClient.cs b/src/Certify.Client/ManagementServerClient.cs index af2db99e8..d122015f9 100644 --- a/src/Certify.Client/ManagementServerClient.cs +++ b/src/Certify.Client/ManagementServerClient.cs @@ -9,7 +9,7 @@ namespace Certify.Client /// /// Implements hub communication with a central management server /// - public class ManagementServerClient + public class ManagementServerClient : IManagementServerClient { public event Action OnConnectionReconnecting; diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs index a1a2f1be4..05819c5d2 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text.Json; @@ -14,7 +14,7 @@ namespace Certify.Management { public partial class CertifyManager { - private ManagementServerClient _managementServerClient; + private IManagementServerClient _managementServerClient; private string _managementServerConnectionId = string.Empty; public async Task UpdateManagementHub(string url, string joiningKey) @@ -63,26 +63,30 @@ private void SendHeartbeatToManagementHub() _managementServerClient.SendInstanceInfo(Guid.NewGuid(), false); } - private async Task StartManagementHubConnection(string hubUri) + public ManagedInstanceInfo GetManagedInstanceInfo() { - - _serviceLog.Debug("Attempting connection to management hub {hubUri}", hubUri); - - var appVersion = Util.GetAppVersion().ToString(); - - var instanceInfo = new ManagedInstanceInfo + return new ManagedInstanceInfo { - InstanceId = $"{this.InstanceId}", + InstanceId = InstanceId, Title = $"{Environment.MachineName}", OS = EnvironmentUtil.GetFriendlyOSName(detailed: false), OSVersion = EnvironmentUtil.GetFriendlyOSName(), - ClientVersion = appVersion, + ClientVersion = Util.GetAppVersion().ToString(), ClientName = ConfigResources.AppName }; + } + private async Task StartManagementHubConnection(string hubUri) + { + + _serviceLog.Debug("Attempting connection to management hub {hubUri}", hubUri); + + var appVersion = Util.GetAppVersion().ToString(); + + var instanceInfo = GetManagedInstanceInfo(); if (_managementServerClient != null) { - _managementServerClient.OnGetCommandResult -= _managementServerClient_OnGetCommandResult; + _managementServerClient.OnGetCommandResult -= PerformDirectHubCommandWithResult; _managementServerClient.OnConnectionReconnecting -= _managementServerClient_OnConnectionReconnecting; } @@ -92,7 +96,7 @@ private async Task StartManagementHubConnection(string hubUri) { await _managementServerClient.ConnectAsync(); - _managementServerClient.OnGetCommandResult += _managementServerClient_OnGetCommandResult; + _managementServerClient.OnGetCommandResult += PerformDirectHubCommandWithResult; _managementServerClient.OnConnectionReconnecting += _managementServerClient_OnConnectionReconnecting; } catch (Exception ex) @@ -103,7 +107,7 @@ private async Task StartManagementHubConnection(string hubUri) } } - private async Task _managementServerClient_OnGetCommandResult(InstanceCommandRequest arg) + public async Task PerformDirectHubCommandWithResult(InstanceCommandRequest arg) { object val = null; diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.cs index 62da3f38d..8174f2847 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.cs @@ -94,6 +94,7 @@ public partial class CertifyManager : ICertifyManager, IDisposable /// private Shared.ServiceConfig _serverConfig; + private System.Timers.Timer _initTimer; private System.Timers.Timer _heartbeatTimer; private System.Timers.Timer _frequentTimer; private System.Timers.Timer _hourlyTimer; @@ -197,8 +198,6 @@ public async Task Init() GenerateDemoItems(); } #endif - - await EnsureMgmtHubConnection(); } /// @@ -206,6 +205,19 @@ public async Task Init() /// private void SetupJobs() { + // 1 shot init of async startup dependencyies (e.g. initial connection to mgmt hub instance) + _initTimer = new System.Timers.Timer(2 * 1000); // 2 seconds + _initTimer.Elapsed += async (s, e) => + { + _initTimer.Stop(); + await EnsureMgmtHubConnection(); + }; + _initTimer.Start(); + + _heartbeatTimer = new System.Timers.Timer(30 * 1000); // every n seconds + _heartbeatTimer.Elapsed += _heartbeatTimer_Elapsed; + _heartbeatTimer.Start(); + // n second job timer (reporting etc) _heartbeatTimer = new System.Timers.Timer(30 * 1000); // every n seconds _heartbeatTimer.Elapsed += _heartbeatTimer_Elapsed; diff --git a/src/Certify.Core/Management/CertifyManager/ICertifyManager.cs b/src/Certify.Core/Management/CertifyManager/ICertifyManager.cs index 6d30723cf..cca7c08c1 100644 --- a/src/Certify.Core/Management/CertifyManager/ICertifyManager.cs +++ b/src/Certify.Core/Management/CertifyManager/ICertifyManager.cs @@ -1,7 +1,8 @@ -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading.Tasks; +using Certify.Client; using Certify.Config; using Certify.Models; using Certify.Models.Config; @@ -16,36 +17,23 @@ namespace Certify.Management public interface ICertifyManager { Task Init(); - void SetStatusReporting(IStatusReporting statusReporting); - Task IsServerTypeAvailable(StandardServerTypes serverType); - Task GetServerTypeVersion(StandardServerTypes serverType); - Task> RunServerDiagnostics(StandardServerTypes serverType, string siteId); - Task GetManagedCertificate(string id); - Task> GetManagedCertificates(ManagedCertificateFilter filter = null); Task GetManagedCertificateResults(ManagedCertificateFilter filter = null); Task GetManagedCertificateSummary(ManagedCertificateFilter filter = null); - Task UpdateManagedCertificate(ManagedCertificate site); - Task DeleteManagedCertificate(string id); - Task PerformExport(ExportRequest exportRequest); Task> PerformImport(ImportRequest importRequest); - Task> GetCurrentChallengeResponses(string challengeType, string key = null); Task> GetAccountRegistrations(); - Task AddAccount(ContactRegistration reg); - Task UpdateAccountContact(string storageKey, ContactRegistration contact); - Task RemoveAccount(string storageKey, bool includeAccountDeactivation = false); Task> ChangeAccountKey(string storageKey, string newKeyPEM = null); @@ -55,50 +43,30 @@ public interface ICertifyManager Task> GetDnsProviderZones(string providerTypeId, string credentialId); Task UpdateCertificateAuthority(CertificateAuthority certificateAuthority); Task> GetCertificateAuthorities(); - Task RevokeCertificate(ILog log, ManagedCertificate managedCertificate); - Task PerformDummyCertificateRequest(ManagedCertificate managedCertificate, IProgress progress = null); Task RemoveCertificateAuthority(string id); Task> GetPrimaryWebSites(StandardServerTypes serverType, bool ignoreStoppedSites, string itemId = null); - Task> RedeployManagedCertificates(ManagedCertificateFilter filter, IProgress progress = null, bool isPreviewOnly = false, bool includeDeploymentTasks = false); - Task DeployCertificate(ManagedCertificate managedCertificate, IProgress progress = null, bool isPreviewOnly = false, bool includeDeploymentTasks = false); - Task PerformCertificateRequest(ILog log, ManagedCertificate managedCertificate, IProgress progress = null, bool resumePaused = false, bool skipRequest = false, bool failOnSkip = false, bool skipTasks = false, bool isInteractive = false, string reason = null); - Task> GetDomainOptionsFromSite(StandardServerTypes serverType, string siteId); - Task> PerformRenewAll(RenewalSettings settings, ConcurrentDictionary> progressTrackers = null); - Task PerformRenewalTasks(); - Task PerformDailyMaintenanceTasks(); - Task PerformCertificateCleanup(); - Task> PerformCertificateMaintenanceTasks(string managedItemId = null); - Task> GeneratePreview(ManagedCertificate item); - void ReportProgress(IProgress progress, RequestProgressState state, bool logThisEvent = true); - Task> PerformDeploymentTask(ILog log, string managedCertificateId, string taskId, bool isPreviewOnly, bool skipDeferredTasks, bool forceTaskExecution); - Task> GetDeploymentProviders(); - Task> ValidateDeploymentTask(ManagedCertificate managedCertificate, DeploymentTaskConfig taskConfig); - Task GetDeploymentProviderDefinition(string id, DeploymentTaskConfig config); - Task GetItemLog(string id, int limit = 1000); Task GetServiceLog(string logType, int limit = 10000); - ICredentialsManager GetCredentialsManager(); IManagedItemStore GetManagedItemStore(); - Task ApplyPreferences(); Task> GetDataStoreProviders(); @@ -117,6 +85,10 @@ public interface ICertifyManager Task DeleteManagedChallenge(string id); Task PerformManagedChallengeRequest(ManagedChallengeRequest request); Task CleanupManagedChallengeRequest(ManagedChallengeRequest request); + Task UpdateManagementHub(string url, string joiningKey); + Task PerformDirectHubCommandWithResult(InstanceCommandRequest arg); + + ManagedInstanceInfo GetManagedInstanceInfo(); } } diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Program.cs b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Program.cs index b92f598d3..1104391a1 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Program.cs +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Program.cs @@ -1,6 +1,8 @@ - + var builder = WebApplication.CreateBuilder(args); +builder.Configuration.AddJsonFile("appsettings-core.json", optional: false, reloadOnChange: true); + builder.AddServiceDefaults(); var startup = new Certify.Server.Core.Startup(builder.Configuration); diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/appsettings.Development.json b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/appsettings-core.Development.json similarity index 100% rename from src/Certify.Server/Certify.Server.Core/Certify.Server.Core/appsettings.Development.json rename to src/Certify.Server/Certify.Server.Core/Certify.Server.Core/appsettings-core.Development.json diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/appsettings.json b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/appsettings-core.json similarity index 100% rename from src/Certify.Server/Certify.Server.Core/Certify.Server.Core/appsettings.json rename to src/Certify.Server/Certify.Server.Core/Certify.Server.Core/appsettings-core.json diff --git a/src/Certify.Server/Certify.Server.Hub.Api/Program.cs b/src/Certify.Server/Certify.Server.Hub.Api/Program.cs index c998a4162..545546d09 100644 --- a/src/Certify.Server/Certify.Server.Hub.Api/Program.cs +++ b/src/Certify.Server/Certify.Server.Hub.Api/Program.cs @@ -2,6 +2,8 @@ var builder = WebApplication.CreateBuilder(args); +builder.Configuration.AddJsonFile("appsettings-api.json", optional: false, reloadOnChange: true); + #if ASPIRE builder.AddServiceDefaults(); #endif diff --git a/src/Certify.Server/Certify.Server.Hub.Api/SignalR/ManagementHub/InstanceManagementStateProvider.cs b/src/Certify.Server/Certify.Server.Hub.Api/SignalR/ManagementHub/InstanceManagementStateProvider.cs index 6845a5185..7935e8094 100644 --- a/src/Certify.Server/Certify.Server.Hub.Api/SignalR/ManagementHub/InstanceManagementStateProvider.cs +++ b/src/Certify.Server/Certify.Server.Hub.Api/SignalR/ManagementHub/InstanceManagementStateProvider.cs @@ -8,6 +8,8 @@ namespace Certify.Server.Hub.Api.SignalR.ManagementHub public interface IInstanceManagementStateProvider { public void Clear(); + public void SetManagementHubInstanceId(string instanceId); + public string GetManagementHubInstanceId(); public void UpdateInstanceConnectionInfo(string connectionId, ManagedInstanceInfo instanceInfo); public void UpdateInstanceStatusSummary(string instanceId, StatusSummary summary); public string GetConnectionIdForInstance(string instanceId); @@ -40,6 +42,8 @@ public class InstanceManagementStateProvider : IInstanceManagementStateProvider private ConcurrentDictionary _managedInstanceItems = []; private ConcurrentDictionary _managedInstanceStatusSummary = []; private ILogger _logger; + private string _mgmtHubInstanceId = string.Empty; + public InstanceManagementStateProvider(ILogger logger) { _logger = logger; @@ -56,6 +60,16 @@ public void Clear() } + public void SetManagementHubInstanceId(string instanceId) + { + _mgmtHubInstanceId = instanceId; + } + + public string GetManagementHubInstanceId() + { + return _mgmtHubInstanceId; + } + public List GetConnectedInstances() { return _instanceConnections.Values.ToList(); diff --git a/src/Certify.Server/Certify.Server.Hub.Api/Startup.cs b/src/Certify.Server/Certify.Server.Hub.Api/Startup.cs index c0ff13721..9edc16a7f 100644 --- a/src/Certify.Server/Certify.Server.Hub.Api/Startup.cs +++ b/src/Certify.Server/Certify.Server.Hub.Api/Startup.cs @@ -173,13 +173,8 @@ public void ConfigureServices(IServiceCollection services) services.AddSingleton(); - services.AddTransient(s => - { - var p = s.GetService(typeof(IInstanceManagementStateProvider)) as IInstanceManagementStateProvider; - var h = s.GetService(typeof(IHubContext)) as IHubContext; - var c = s.GetService(typeof(ICertifyInternalApiClient)) as ICertifyInternalApiClient; - return new ManagementAPI(p, h, c); - }); + // we create a new instance of the management API for each request + services.AddTransient(); services.AddHostedService(); return results; diff --git a/src/Certify.Server/Certify.Server.Hub.Api/appsettings.Development.json b/src/Certify.Server/Certify.Server.Hub.Api/appsettings-api.Development.json similarity index 100% rename from src/Certify.Server/Certify.Server.Hub.Api/appsettings.Development.json rename to src/Certify.Server/Certify.Server.Hub.Api/appsettings-api.Development.json diff --git a/src/Certify.Server/Certify.Server.Hub.Api/appsettings.json b/src/Certify.Server/Certify.Server.Hub.Api/appsettings-api.json similarity index 100% rename from src/Certify.Server/Certify.Server.Hub.Api/appsettings.json rename to src/Certify.Server/Certify.Server.Hub.Api/appsettings-api.json From 8fe601c50001fff013610ad1813a27e5403abf5a Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Tue, 4 Feb 2025 16:27:15 +0800 Subject: [PATCH 310/328] Package updates --- .../DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj | 2 +- .../Certify.Server.Hub.Api/Certify.Server.Hub.Api.csproj | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj index 5be930970..9b0ab2a0e 100644 --- a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj +++ b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/Certify.Server/Certify.Server.Hub.Api/Certify.Server.Hub.Api.csproj b/src/Certify.Server/Certify.Server.Hub.Api/Certify.Server.Hub.Api.csproj index 18f761684..d4c8d38a2 100644 --- a/src/Certify.Server/Certify.Server.Hub.Api/Certify.Server.Hub.Api.csproj +++ b/src/Certify.Server/Certify.Server.Hub.Api/Certify.Server.Hub.Api.csproj @@ -28,6 +28,7 @@ + \ No newline at end of file From 9311c6e79b38f81657da03af449320048b6d0cc7 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Tue, 4 Feb 2025 16:29:50 +0800 Subject: [PATCH 311/328] Implement combined HubService for Hub API + Service Core Optionally hosting Web UI --- .../Certify.Server.HubService.csproj | 36 +++++ .../Certify.Server.HubService.http | 6 + .../Certify.Server.HubService/Program.cs | 125 +++++++++++++++++ .../Properties/launchSettings.json | 35 +++++ .../Services/CertifyHubService.cs | 130 ++++++++++++++++++ .../appsettings.Development.json | 8 ++ .../appsettings.json | 16 +++ 7 files changed, 356 insertions(+) create mode 100644 src/Certify.Server/Certify.Server.HubService/Certify.Server.HubService.csproj create mode 100644 src/Certify.Server/Certify.Server.HubService/Certify.Server.HubService.http create mode 100644 src/Certify.Server/Certify.Server.HubService/Program.cs create mode 100644 src/Certify.Server/Certify.Server.HubService/Properties/launchSettings.json create mode 100644 src/Certify.Server/Certify.Server.HubService/Services/CertifyHubService.cs create mode 100644 src/Certify.Server/Certify.Server.HubService/appsettings.Development.json create mode 100644 src/Certify.Server/Certify.Server.HubService/appsettings.json diff --git a/src/Certify.Server/Certify.Server.HubService/Certify.Server.HubService.csproj b/src/Certify.Server/Certify.Server.HubService/Certify.Server.HubService.csproj new file mode 100644 index 000000000..834a5a0ac --- /dev/null +++ b/src/Certify.Server/Certify.Server.HubService/Certify.Server.HubService.csproj @@ -0,0 +1,36 @@ + + + + net9.0 + enable + enable + linux-x64 + linux-x64 + True + mcr.microsoft.com/dotnet/aspnet:9.0 + c6402661-abaa-463e-ab79-e40d979f8b77 + ..\..\..\..\certify-manager + + + + + + + + + + + + + + + + + all + + + all + + + + diff --git a/src/Certify.Server/Certify.Server.HubService/Certify.Server.HubService.http b/src/Certify.Server/Certify.Server.HubService/Certify.Server.HubService.http new file mode 100644 index 000000000..47e347dc4 --- /dev/null +++ b/src/Certify.Server/Certify.Server.HubService/Certify.Server.HubService.http @@ -0,0 +1,6 @@ +@Certify.Server.HubService_HostAddress = https://localhost:7187 + +GET {{Certify.Server.HubService_HostAddress}}/api/v1/health/ +Accept: application/json + +### diff --git a/src/Certify.Server/Certify.Server.HubService/Program.cs b/src/Certify.Server/Certify.Server.HubService/Program.cs new file mode 100644 index 000000000..44c68c9dd --- /dev/null +++ b/src/Certify.Server/Certify.Server.HubService/Program.cs @@ -0,0 +1,125 @@ +using Certify.Client; +using Certify.Server.Hub.Api.Middleware; +using Certify.Server.Hub.Api.Services; +using Certify.Server.Hub.Api.SignalR; +using Certify.Server.Hub.Api.SignalR.ManagementHub; +using Certify.Server.HubService.Services; +using Microsoft.AspNetCore.Mvc.ApplicationParts; +using Microsoft.AspNetCore.SignalR; +using Microsoft.AspNetCore.StaticFiles; + +var builder = WebApplication.CreateBuilder(args); + +builder.AddServiceDefaults(); + +// Add services to the container + +var assembly = typeof(Certify.Server.Hub.Api.Startup).Assembly; +var part = new AssemblyPart(assembly); + +builder.Services + .AddMemoryCache() + .AddTokenAuthentication(builder.Configuration) + .AddAuthorization() + .AddControllers() + .ConfigureApplicationPartManager(apm => apm.ApplicationParts.Add(part)); + +builder.Services + .AddRouting(r => r.LowercaseUrls = true) + .AddSignalR(opt => opt.MaximumReceiveMessageSize = null) + .AddMessagePackProtocol(); + +builder.Services.AddDataProtection(a => +{ + a.ApplicationDiscriminator = "certify"; +}); + +builder.Services.AddResponseCompression(); + +// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi +builder.Services.AddOpenApi(); + +var certifyManager = new Certify.Management.CertifyManager(); +await certifyManager.Init(); + +// setup public/hub api +builder.Services.AddSingleton(certifyManager); + +builder.Services.AddTransient(typeof(ICertifyInternalApiClient), typeof(CertifyHubService)); + +// setup server core +builder.Services.AddSingleton(); + +builder.Services.AddTransient(); + +// used to directly talk back to the management server process instead of connecting back via SignalR +builder.Services.AddTransient(); +builder.Services.AddTransient(); + +builder.Services.AddHostedService(); + +// build app + +var app = builder.Build(); + +app.MapDefaultEndpoints(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.MapOpenApi(); +} + +app.UseHttpsRedirection(); + +// serve static files from wwwroot +app.UseDefaultFiles(); +// Set up custom content types - associating file extension to MIME type +var provider = new FileExtensionContentTypeProvider(); +// Add new mappings +provider.Mappings[".dat"] = "application/octet-stream"; +provider.Mappings[".dll"] = "application/octet-stream"; +provider.Mappings[".image"] = "image/png"; + +app.UseStaticFiles(new StaticFileOptions +{ + ContentTypeProvider = provider +}); + +// configure CROS +app.UseCors((p) => +{ + p.AllowAnyOrigin() + .AllowAnyMethod() + .AllowAnyHeader(); +}); + +app.UseAuthentication(); +app.UseAuthorization(); + +app.MapControllers(); + +app.MapHub("/api/internal/status"); +app.MapHub("/api/internal/managementhub"); + +app.MapDefaultControllerRoute().WithStaticAssets(); +app.UseResponseCompression(); + +var statusHubContext = app.Services.GetRequiredService>(); + +if (statusHubContext == null) +{ + throw new Exception("Status Hub not registered"); +} + +// setup signalr message forwarding, message received from internal service will be resent to our connected clients via our own SignalR hub +var statusReporting = new UserInterfaceStatusHubReporting(statusHubContext); +//UserInterfaceStatusHubReporting _statusReporting = new UserInterfaceStatusHubReporting(); + +var directServerClient = app.Services.GetRequiredService(); +certifyManager.SetDirectManagementClient(directServerClient); + +app.Start(); + +System.Diagnostics.Debug.WriteLine($"Server started {string.Join(";", app.Urls)}"); +app.WaitForShutdown(); diff --git a/src/Certify.Server/Certify.Server.HubService/Properties/launchSettings.json b/src/Certify.Server/Certify.Server.HubService/Properties/launchSettings.json new file mode 100644 index 000000000..67a19738d --- /dev/null +++ b/src/Certify.Server/Certify.Server.HubService/Properties/launchSettings.json @@ -0,0 +1,35 @@ +{ + "profiles": { + "http": { + "commandName": "Project", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "dotnetRunMessages": true, + "applicationUrl": "http://localhost:5028", + "CERTIFY_MANAGEMENT_HUB": "http://localhost:5028/api/internal/managementhub", + "CERTIFY_ENABLE_MANAGEMENT_HUB": "true" + }, + "https": { + "commandName": "Project", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "dotnetRunMessages": true, + "applicationUrl": "https://localhost:7187;http://localhost:5028", + "CERTIFY_MANAGEMENT_HUB": "https://localhost:7187/api/internal/managementhub", + "CERTIFY_ENABLE_MANAGEMENT_HUB": "true" + }, + "Container (.NET SDK)": { + "commandName": "SdkContainer", + "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}", + "environmentVariables": { + "ASPNETCORE_HTTPS_PORTS": "8081", + "ASPNETCORE_HTTP_PORTS": "8080" + }, + "publishAllPorts": true, + "useSSL": true + } + }, + "$schema": "https://json.schemastore.org/launchsettings.json" +} diff --git a/src/Certify.Server/Certify.Server.HubService/Services/CertifyHubService.cs b/src/Certify.Server/Certify.Server.HubService/Services/CertifyHubService.cs new file mode 100644 index 000000000..969e61188 --- /dev/null +++ b/src/Certify.Server/Certify.Server.HubService/Services/CertifyHubService.cs @@ -0,0 +1,130 @@ +using Certify.Client; +using Certify.Config; +using Certify.Management; +using Certify.Models; +using Certify.Models.Config; +using Certify.Models.Config.Migration; +using Certify.Models.Hub; +using Certify.Models.Providers; +using Certify.Models.Reporting; +using Certify.Models.Utils; +using Certify.Shared; +using Microsoft.AspNetCore.DataProtection; +using ServiceControllers = Certify.Service.Controllers; + +namespace Certify.Server.HubService.Services +{ + /// + /// The HubService is a surrogate for the Certify Server Core Service, Service API and Client. The Hub hosts a Certify Server Core instead of talking to a Service instance over http, skipping a layer of abstraction and a communication layer. + /// A further layer of abstraction can be skipped by implementing all controller logic in Certify.Core and using that directly + /// + public class CertifyHubService : ICertifyInternalApiClient + { + private ICertifyManager _certifyManager; + private IDataProtectionProvider _dataProtectionProvider; + public CertifyHubService(ICertifyManager certifyManager, IDataProtectionProvider dataProtectionProvider) + { + _certifyManager = certifyManager; + _dataProtectionProvider = dataProtectionProvider; + } + + private ServiceControllers.AccessController _accessController(AuthContext authContext) + { + var controller = new ServiceControllers.AccessController(_certifyManager, _dataProtectionProvider); + controller.SetCurrentAuthContext(authContext); + return controller; + } + + private ServiceControllers.ManagedChallengeController _managedChallengeController(AuthContext authContext) + { + var controller = new ServiceControllers.ManagedChallengeController(_certifyManager); + controller.SetCurrentAuthContext(authContext); + return controller; + } + + public Task GetPreferences(AuthContext authContext = null) => Task.FromResult(new ServiceControllers.PreferencesController(_certifyManager).GetPreferences()); + public Task AddSecurityPrinciple(SecurityPrinciple principle, AuthContext authContext) => _accessController(authContext).AddSecurityPrinciple(principle); + public Task CheckSecurityPrincipleHasAccess(AccessCheck check, AuthContext authContext) => _accessController(authContext).CheckSecurityPrincipleHasAccess(check); + public Task> GetSecurityPrincipleAssignedRoles(string id, AuthContext authContext) => _accessController(authContext).GetSecurityPrincipleAssignedRoles(id); + public Task GetSecurityPrincipleRoleStatus(string id, AuthContext authContext) => _accessController(authContext).GetSecurityPrincipleRoleStatus(id); + public Task> GetSecurityPrinciples(AuthContext authContext) => _accessController(authContext).GetSecurityPrinciples(); + public Task AddAssignedAccessToken(AssignedAccessToken token, AuthContext authContext) => _accessController(authContext).AddAssignedccessToken(token); + public Task CheckApiTokenHasAccess(AccessToken token, AccessCheck check, AuthContext authContext = null) => _accessController(authContext).CheckApiTokenHasAccess(new AccessTokenCheck { Check = check, Token = token }); + public Task> GetAssignedAccessTokens(AuthContext authContext) => _accessController(authContext).GetAssignedAccessTokens(); + public Task RemoveSecurityPrinciple(string id, AuthContext authContext) => _accessController(authContext).DeleteSecurityPrinciple(id); + public Task UpdateSecurityPrinciple(SecurityPrinciple principle, AuthContext authContext) => _accessController(authContext).UpdateSecurityPrinciple(principle); + public Task UpdateSecurityPrincipleAssignedRoles(SecurityPrincipleAssignedRoleUpdate update, AuthContext authContext) => _accessController(authContext).UpdateSecurityPrincipleAssignedRoles(update); + public Task UpdateSecurityPrinciplePassword(SecurityPrinciplePasswordUpdate passwordUpdate, AuthContext authContext) => _accessController(authContext).UpdatePassword(passwordUpdate); + public Task ValidateSecurityPrinciplePassword(SecurityPrinciplePasswordCheck passwordCheck, AuthContext authContext) => _accessController(authContext).Validate(passwordCheck); + public Task> GetAccessRoles(AuthContext authContext) => _accessController(authContext).GetRoles(); + public Task> GetManagedChallenges(AuthContext authContext) => _managedChallengeController(authContext).Get(); + public Task UpdateManagedChallenge(ManagedChallenge update, AuthContext authContext) => _managedChallengeController(authContext).Update(update); + public Task CleanupManagedChallenge(ManagedChallengeRequest request, AuthContext authContext) => _managedChallengeController(authContext).CleanupChallengeResponse(request); + + public Task AddAccount(ContactRegistration contact, AuthContext authContext = null) => throw new NotImplementedException(); + public Task> BeginAutoRenewal(RenewalSettings settings, AuthContext authContext = null) => throw new NotImplementedException(); + public Task BeginCertificateRequest(string managedItemId, bool resumePaused, bool isInteractive, AuthContext authContext = null) => throw new NotImplementedException(); + public Task ChangeAccountKey(string storageKey, string newKeyPEM = null, AuthContext authContext = null) => throw new NotImplementedException(); + + public Task CheckForUpdates(AuthContext authContext = null) => throw new NotImplementedException(); + + public Task> CopyDataStore(string sourceId, string targetId, AuthContext authContext = null) => throw new NotImplementedException(); + public Task DeleteCertificateAuthority(string id, AuthContext authContext = null) => throw new NotImplementedException(); + public Task DeleteCredential(string credentialKey, AuthContext authContext = null) => throw new NotImplementedException(); + public Task DeleteManagedCertificate(string managedItemId, AuthContext authContext = null) => throw new NotImplementedException(); + + public Task> GetAccounts(AuthContext authContext = null) => throw new NotImplementedException(); + public Task GetAppVersion(AuthContext authContext = null) => Task.FromResult(new ServiceControllers.SystemController(_certifyManager).GetAppVersion()); + + public Task> GetCertificateAuthorities(AuthContext authContext = null) => throw new NotImplementedException(); + public Task> GetChallengeAPIList(AuthContext authContext = null) => throw new NotImplementedException(); + public Task> GetCredentials(AuthContext authContext = null) => throw new NotImplementedException(); + public Task> GetCurrentChallenges(string type, string key, AuthContext authContext = null) => throw new NotImplementedException(); + public Task> GetDataStoreConnections(AuthContext authContext = null) => throw new NotImplementedException(); + public Task> GetDataStoreProviders(AuthContext authContext = null) => throw new NotImplementedException(); + public Task GetDeploymentProviderDefinition(string id, DeploymentTaskConfig config, AuthContext authContext = null) => throw new NotImplementedException(); + public Task> GetDeploymentProviderList(AuthContext authContext = null) => throw new NotImplementedException(); + public Task> GetDnsProviderZones(string providerTypeId, string credentialId, AuthContext authContext = null) => throw new NotImplementedException(); + public Task GetItemLog(string id, int limit, AuthContext authContext = null) => throw new NotImplementedException(); + public Task GetManagedCertificate(string managedItemId, AuthContext authContext = null) => throw new NotImplementedException(); + public Task> GetManagedCertificates(ManagedCertificateFilter filter, AuthContext authContext = null) => throw new NotImplementedException(); + public Task GetManagedCertificateSearchResult(ManagedCertificateFilter filter, AuthContext authContext = null) => throw new NotImplementedException(); + public Task GetManagedCertificateSummary(ManagedCertificateFilter filter, AuthContext authContext = null) => throw new NotImplementedException(); + + + public Task> GetServerSiteDomains(StandardServerTypes serverType, string serverSiteId, AuthContext authContext = null) => throw new NotImplementedException(); + public Task> GetServerSiteList(StandardServerTypes serverType, string itemId = null, AuthContext authContext = null) => throw new NotImplementedException(); + public Task GetServerVersion(StandardServerTypes serverType, AuthContext authContext = null) => throw new NotImplementedException(); + public Task IsServerAvailable(StandardServerTypes serverType, AuthContext authContext = null) => throw new NotImplementedException(); + public Task> PerformChallengeCleanup(ManagedCertificate site, AuthContext authContext = null) => throw new NotImplementedException(); + public Task> PerformDeployment(string managedCertificateId, string taskId, bool isPreviewOnly, bool forceTaskExecute, AuthContext authContext = null) => throw new NotImplementedException(); + public Task PerformExport(ExportRequest exportRequest, AuthContext authContext) => throw new NotImplementedException(); + public Task> PerformImport(ImportRequest importRequest, AuthContext authContext) => throw new NotImplementedException(); + public Task> PerformManagedCertMaintenance(string id = null, AuthContext authContext = null) => throw new NotImplementedException(); + public Task PerformManagedChallenge(ManagedChallengeRequest request, AuthContext authContext) => throw new NotImplementedException(); + public Task> PerformServiceDiagnostics(AuthContext authContext = null) => throw new NotImplementedException(); + public Task> PreviewActions(ManagedCertificate site, AuthContext authContext = null) => throw new NotImplementedException(); + public Task ReapplyCertificateBindings(string managedItemId, bool isPreviewOnly, bool includeDeploymentTasks, AuthContext authContext = null) => throw new NotImplementedException(); + public Task> RedeployManagedCertificates(bool isPreviewOnly, bool includeDeploymentTasks, AuthContext authContext = null) => throw new NotImplementedException(); + public Task RefetchCertificate(string managedItemId, AuthContext authContext = null) => throw new NotImplementedException(); + public Task RemoveAccount(string storageKey, bool deactivate, AuthContext authContext = null) => throw new NotImplementedException(); + public Task RemoveManagedChallenge(string id, AuthContext authContext) => throw new NotImplementedException(); + + public Task RevokeManageSiteCertificate(string managedItemId, AuthContext authContext = null) => throw new NotImplementedException(); + public Task> RunConfigurationDiagnostics(StandardServerTypes serverType, string serverSiteId, AuthContext authContext = null) => throw new NotImplementedException(); + public Task> SetDefaultDataStore(string dataStoreId, AuthContext authContext = null) => throw new NotImplementedException(); + public Task SetPreferences(Preferences preferences, AuthContext authContext = null) => throw new NotImplementedException(); + public Task> TestChallengeConfiguration(ManagedCertificate site, AuthContext authContext = null) => throw new NotImplementedException(); + public Task TestCredentials(string credentialKey, AuthContext authContext = null) => throw new NotImplementedException(); + public Task> TestDataStoreConnection(DataStoreConnection dataStoreConnection, AuthContext authContext = null) => throw new NotImplementedException(); + public Task UpdateAccountContact(ContactRegistration contact, AuthContext authContext = null) => throw new NotImplementedException(); + public Task UpdateCertificateAuthority(CertificateAuthority ca, AuthContext authContext = null) => throw new NotImplementedException(); + public Task UpdateCredentials(StoredCredential credential, AuthContext authContext = null) => throw new NotImplementedException(); + public Task> UpdateDataStoreConnection(DataStoreConnection dataStoreConnection, AuthContext authContext = null) => throw new NotImplementedException(); + public Task UpdateManagedCertificate(ManagedCertificate site, AuthContext authContext = null) => throw new NotImplementedException(); + + public Task UpdateManagementHub(string url, string joiningKey, AuthContext authContext = null) => throw new NotImplementedException(); + public Task> ValidateDeploymentTask(DeploymentTaskValidationInfo info, AuthContext authContext = null) => throw new NotImplementedException(); + + } +} diff --git a/src/Certify.Server/Certify.Server.HubService/appsettings.Development.json b/src/Certify.Server/Certify.Server.HubService/appsettings.Development.json new file mode 100644 index 000000000..0c208ae91 --- /dev/null +++ b/src/Certify.Server/Certify.Server.HubService/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/src/Certify.Server/Certify.Server.HubService/appsettings.json b/src/Certify.Server/Certify.Server.HubService/appsettings.json new file mode 100644 index 000000000..7675acb21 --- /dev/null +++ b/src/Certify.Server/Certify.Server.HubService/appsettings.json @@ -0,0 +1,16 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "AllowedHosts": "*", + "JwtSettings": { + "secret": "8FdYdFZKb2gQz7c4hpX7BMKpEnrpGhI7APd7GHMdvGg", + "refreshTokenExpirationInDays": 1, + "authTokenExpirationInMinutes": 60, + "issuer": "Certify.Server.Hub.Api" + } +} From 053551d204f5b7a2faff25af939657d3d94ccc82 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Tue, 4 Feb 2025 16:31:10 +0800 Subject: [PATCH 312/328] Debug: export providers that use credentials --- .../Management/Challenges/ChallengeProviders.cs | 8 ++++++++ .../DeploymentTasks/DeploymentTaskProviderFactory.cs | 7 +++++++ 2 files changed, 15 insertions(+) diff --git a/src/Certify.Core/Management/Challenges/ChallengeProviders.cs b/src/Certify.Core/Management/Challenges/ChallengeProviders.cs index e363b3a9e..8b99c6262 100644 --- a/src/Certify.Core/Management/Challenges/ChallengeProviders.cs +++ b/src/Certify.Core/Management/Challenges/ChallengeProviders.cs @@ -223,6 +223,14 @@ public static async Task GetDnsProvider(string providerType, Dicti public static async Task> GetChallengeAPIProviders() { var result = PluginManager.CurrentInstance.DnsProviderProviders.SelectMany(pp => pp.GetProviders(pp.GetType())).ToList(); + +#if DEBUG + // output list of providers which require credentials plus list of potential stored credential parameters + foreach (var resultItem in result.Where(p => p.ProviderParameters.Any(p => p.IsCredential)).OrderBy(r => r.Title)) + { + System.Diagnostics.Debug.WriteLine($"[{resultItem.Title}] ID: {resultItem.Id} {{{string.Join(",", resultItem.ProviderParameters.Where(p => p.IsCredential).Select(p => $"'{p.Key}','<{p.Name}>'"))}}}"); + } +#endif return await Task.FromResult(result); } } diff --git a/src/Certify.Core/Management/DeploymentTasks/DeploymentTaskProviderFactory.cs b/src/Certify.Core/Management/DeploymentTasks/DeploymentTaskProviderFactory.cs index f715a941e..a5a3deccf 100644 --- a/src/Certify.Core/Management/DeploymentTasks/DeploymentTaskProviderFactory.cs +++ b/src/Certify.Core/Management/DeploymentTasks/DeploymentTaskProviderFactory.cs @@ -52,6 +52,13 @@ public async static Task> GetDeploymentTaskPr } } +#if DEBUG + // output list of providers which require credentials plus list of potential stored credential parameters + foreach (var resultItem in list.Where(p => p.ProviderParameters.Any(p => p.IsCredential)).OrderBy(r => r.Title)) + { + System.Diagnostics.Debug.WriteLine($"[{resultItem.Title}] ID: {resultItem.Id} {{{string.Join(",", resultItem.ProviderParameters.Where(p => p.IsCredential).Select(p => $"'{p.Key}','<{p.Name}>'"))}}}"); + } +#endif return await Task.FromResult(list); } From 3e7788b5cbc917fa83752ee5f9c7ea4259ca6b3d Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Tue, 4 Feb 2025 16:33:22 +0800 Subject: [PATCH 313/328] Refactor and cleanup --- .../CertificateAuthority.cs | 10 +++---- src/Certify.Models/Hub/HubInfo.cs | 9 +++++++ .../Controllers/AccessController.cs | 18 ++++--------- .../Controllers/ControllerBase.cs | 27 +++++++++++++++++++ .../Controllers/internal/HubController.cs | 2 ++ 5 files changed, 48 insertions(+), 18 deletions(-) diff --git a/src/Certify.Models/CertificateAuthorities/CertificateAuthority.cs b/src/Certify.Models/CertificateAuthorities/CertificateAuthority.cs index 4a3dbd7c9..6b8b790bc 100644 --- a/src/Certify.Models/CertificateAuthorities/CertificateAuthority.cs +++ b/src/Certify.Models/CertificateAuthorities/CertificateAuthority.cs @@ -110,7 +110,7 @@ public class CertificateAuthority public string? Id { get; set; } public string APIType { get; set; } = CertAuthorityAPIType.ACME_V2.ToString(); - public List SupportedFeatures { get; set; } = new(); + public List SupportedFeatures { get; set; } = []; public string Title { get; set; } = string.Empty; public string Description { get; set; } = string.Empty; public string WebsiteUrl { get; set; } = string.Empty; @@ -132,21 +132,21 @@ public class CertificateAuthority public bool AllowInternalHostnames { get; set; } public bool SupportsCachedValidations { get; set; } = true; public string EabInstructions { get; set; } = string.Empty; - public List SupportedKeyTypes { get; set; } = new(); + public List SupportedKeyTypes { get; set; } = []; /// /// If set, lists intermediate cert for this CA which should be disabled or removed /// - public List DisabledIntermediates { get; set; } = new(); + public List DisabledIntermediates { get; set; } = []; /// /// Optional list of Trusted Root certificates to install for chain building and verification /// - public Dictionary TrustedRoots { get; set; } = new(); + public Dictionary TrustedRoots { get; set; } = []; /// /// Optional list of Intermediate certificates to install for chain building and verification /// - public Dictionary Intermediates { get; set; } = new(); + public Dictionary Intermediates { get; set; } = []; } } diff --git a/src/Certify.Models/Hub/HubInfo.cs b/src/Certify.Models/Hub/HubInfo.cs index 99ce73f28..071682cc5 100644 --- a/src/Certify.Models/Hub/HubInfo.cs +++ b/src/Certify.Models/Hub/HubInfo.cs @@ -6,4 +6,13 @@ public class HubInfo public VersionInfo Version { get; set; } } + + public class HubHealth + { + public string Status { get; set; } + public string Detail { get; set; } + public string Version { get; set; } + public bool ServiceAvailable { get; set; } + public object env { get; set; } + } } diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccessController.cs b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccessController.cs index 0eccf5ea8..f11638c7f 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccessController.cs +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/AccessController.cs @@ -18,14 +18,6 @@ public AccessController(ICertifyManager certifyManager, IDataProtectionProvider _dataProtectionProvider = dataProtectionProvider; } - private string GetContextUserId() - { - // TODO: sign passed value provided by public API using public APIs access token - var contextUserId = Request.Headers["X-Context-User-Id"]; - - return contextUserId; - } - [HttpPost, Route("securityprinciple")] public async Task AddSecurityPrinciple([FromBody] SecurityPrinciple principle) { @@ -79,7 +71,7 @@ private string GetContextUserId() } [HttpGet, Route("securityprinciples")] - public async Task> GetSecurityPrinciples() + public async Task> GetSecurityPrinciples() { var accessControl = await _certifyManager.GetCurrentAccessControl(); @@ -95,7 +87,7 @@ public async Task> GetSecurityPrinciples() } [HttpGet, Route("roles")] - public async Task> GetRoles() + public async Task> GetRoles() { var accessControl = await _certifyManager.GetCurrentAccessControl(); return await accessControl.GetRoles(GetContextUserId()); @@ -118,7 +110,7 @@ public async Task CheckSecurityPrincipleHasAccess(AccessCheck check) } [HttpGet, Route("assignedtoken/list/")] - public async Task> GetAssignedAccessTokens() + public async Task> GetAssignedAccessTokens() { var accessControl = await _certifyManager.GetCurrentAccessControl(); @@ -126,7 +118,7 @@ public async Task> GetAssignedAccessTokens() } [HttpPost, Route("assignedtoken/")] - public async Task AddAAssignedccessToken([FromBody] AssignedAccessToken token) + public async Task AddAssignedccessToken([FromBody] AssignedAccessToken token) { var accessControl = await _certifyManager.GetCurrentAccessControl(); var addResultOk = await accessControl.AddAssignedAccessToken(GetContextUserId(), token); @@ -139,7 +131,7 @@ public async Task> GetAssignedAccessTokens() } [HttpGet, Route("securityprinciple/{id}/assignedroles")] - public async Task> GetSecurityPrincipleAssignedRoles(string id) + public async Task> GetSecurityPrincipleAssignedRoles(string id) { var accessControl = await _certifyManager.GetCurrentAccessControl(); diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/ControllerBase.cs b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/ControllerBase.cs index 333eff4a6..4a73b8625 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/ControllerBase.cs +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Controllers/ControllerBase.cs @@ -47,5 +47,32 @@ internal void DebugLog(string msg = null, Console.ForegroundColor = ConsoleColor.White; #endif } + + Client.AuthContext _currentAuthContext = null; + + /// + /// Set the current auth context for the current request, only used internally when invoking controller outside of an http request + /// + /// + /// + [NonAction] + public void SetCurrentAuthContext(Client.AuthContext authContext) + { + _currentAuthContext = authContext; + } + + [NonAction] + public string GetContextUserId() + { + if (_currentAuthContext != null) + { + return _currentAuthContext.UserId; + } + + // TODO: sign passed value provided by public API using public APIs access token + var contextUserId = Request?.Headers["X-Context-User-Id"]; + + return contextUserId; + } } } diff --git a/src/Certify.Server/Certify.Server.Hub.Api/Controllers/internal/HubController.cs b/src/Certify.Server/Certify.Server.Hub.Api/Controllers/internal/HubController.cs index 7ba38cdc5..8b0507ed9 100644 --- a/src/Certify.Server/Certify.Server.Hub.Api/Controllers/internal/HubController.cs +++ b/src/Certify.Server/Certify.Server.Hub.Api/Controllers/internal/HubController.cs @@ -132,6 +132,8 @@ public async Task GetHubInfo() hubInfo.InstanceId = hubprefs.InstanceId; + _mgmtStateProvider.SetManagementHubInstanceId(hubInfo.InstanceId); + var versionInfo = await _client.GetAppVersion(); hubInfo.Version = new Models.Hub.VersionInfo { Version = versionInfo, Product = "Certify Management Hub" }; From cc6df2c1c21169965bf1ef6a7c1853696015f715 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Tue, 4 Feb 2025 16:35:34 +0800 Subject: [PATCH 314/328] Mgmt Hub: perform commands directly if in-process instance present --- .../Services/ManagementAPI.cs | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/Certify.Server/Certify.Server.Hub.Api/Services/ManagementAPI.cs b/src/Certify.Server/Certify.Server.Hub.Api/Services/ManagementAPI.cs index 5cd42324c..6029ff610 100644 --- a/src/Certify.Server/Certify.Server.Hub.Api/Services/ManagementAPI.cs +++ b/src/Certify.Server/Certify.Server.Hub.Api/Services/ManagementAPI.cs @@ -17,7 +17,7 @@ public partial class ManagementAPI { IInstanceManagementStateProvider _mgmtStateProvider; IHubContext _mgmtHubContext; - ICertifyInternalApiClient _backendAPIClient; + Certify.Management.ICertifyManager _certifyManager; /// /// Constructor for Management Hub API @@ -25,11 +25,17 @@ public partial class ManagementAPI /// /// /// - public ManagementAPI(IInstanceManagementStateProvider mgmtStateProvider, IHubContext mgmtHubContext, ICertifyInternalApiClient backendAPIClient) + public ManagementAPI(IInstanceManagementStateProvider mgmtStateProvider, IHubContext mgmtHubContext) { _mgmtStateProvider = mgmtStateProvider; _mgmtHubContext = mgmtHubContext; - _backendAPIClient = backendAPIClient; + } + + public ManagementAPI(IInstanceManagementStateProvider mgmtStateProvider, IHubContext mgmtHubContext, Certify.Management.ICertifyManager certifyManager) + { + _mgmtStateProvider = mgmtStateProvider; + _mgmtHubContext = mgmtHubContext; + _certifyManager = certifyManager; } private async Task GetCommandResult(string instanceId, InstanceCommandRequest cmd) @@ -64,9 +70,19 @@ private async Task SendCommandWithNoResult(string instanceId, InstanceCommandReq private async Task PerformInstanceCommandTaskWithResult(string instanceId, KeyValuePair[] args, string commandType) { + InstanceCommandResult result; var cmd = new InstanceCommandRequest(commandType, args); - var result = await GetCommandResult(instanceId, cmd); + if (_certifyManager != null && instanceId == _mgmtStateProvider.GetManagementHubInstanceId()) + { + // get command result directly from in-process instance + result = await _certifyManager.PerformDirectHubCommandWithResult(cmd); + } + else + { + // get command result via SignalR + result = await GetCommandResult(instanceId, cmd); + } if (result?.Value != null) { From 753a131896b19e7e3f8c3972ac2510f787dda35f Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Tue, 4 Feb 2025 17:45:15 +0800 Subject: [PATCH 315/328] WIP: systemd configs --- .../Certify.Server.Core/certify-core.service | 11 +++++++++++ .../Certify.Server.Hub.Api/certify-api.service | 11 +++++++++++ 2 files changed, 22 insertions(+) create mode 100644 src/Certify.Server/Certify.Server.Core/Certify.Server.Core/certify-core.service create mode 100644 src/Certify.Server/Certify.Server.Hub.Api/certify-api.service diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/certify-core.service b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/certify-core.service new file mode 100644 index 000000000..b60d2c24f --- /dev/null +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/certify-core.service @@ -0,0 +1,11 @@ +[Unit] +Description=Certify Core Service + +[Service] +Type=notify +ExecStart=/usr/sbin/certifytheweb +WorkingDirectory=/opt/certifytheweb +Restart=always + +[Install] +WantedBy=multi-user.target diff --git a/src/Certify.Server/Certify.Server.Hub.Api/certify-api.service b/src/Certify.Server/Certify.Server.Hub.Api/certify-api.service new file mode 100644 index 000000000..3c04daf8f --- /dev/null +++ b/src/Certify.Server/Certify.Server.Hub.Api/certify-api.service @@ -0,0 +1,11 @@ +[Unit] +Description=Certify Management Hub API Service + +[Service] +Type=notify +ExecStart=/usr/sbin/certifytheweb +WorkingDirectory=/opt/certifytheweb +Restart=always + +[Install] +WantedBy=multi-user.target From ac2617b1e285559e9b6e49e34cc93101a433a57b Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Tue, 4 Feb 2025 17:46:47 +0800 Subject: [PATCH 316/328] WIP: direct messaging back to hub instead of over SignalR --- .../CertifyManager.ManagementHub.cs | 5 + .../CertifyManager/ICertifyManager.cs | 3 +- .../Services/DirectManagementServerClient.cs | 217 ++++++++++++++++++ 3 files changed, 224 insertions(+), 1 deletion(-) create mode 100644 src/Certify.Server/Certify.Server.HubService/Services/DirectManagementServerClient.cs diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs index 05819c5d2..c845b214d 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs @@ -38,6 +38,11 @@ public async Task UpdateManagementHub(string url, string joiningKey) return new ActionStep("Updated Management Hub", "OK", false); } + public void SetDirectManagementClient(IManagementServerClient client) + { + _managementServerClient = client; + } + private async Task EnsureMgmtHubConnection() { // connect/reconnect to management hub if enabled diff --git a/src/Certify.Core/Management/CertifyManager/ICertifyManager.cs b/src/Certify.Core/Management/CertifyManager/ICertifyManager.cs index cca7c08c1..a1c3bd3d4 100644 --- a/src/Certify.Core/Management/CertifyManager/ICertifyManager.cs +++ b/src/Certify.Core/Management/CertifyManager/ICertifyManager.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading.Tasks; @@ -89,6 +89,7 @@ public interface ICertifyManager Task UpdateManagementHub(string url, string joiningKey); Task PerformDirectHubCommandWithResult(InstanceCommandRequest arg); + void SetDirectManagementClient(IManagementServerClient client); ManagedInstanceInfo GetManagedInstanceInfo(); } } diff --git a/src/Certify.Server/Certify.Server.HubService/Services/DirectManagementServerClient.cs b/src/Certify.Server/Certify.Server.HubService/Services/DirectManagementServerClient.cs new file mode 100644 index 000000000..5f316f3fc --- /dev/null +++ b/src/Certify.Server/Certify.Server.HubService/Services/DirectManagementServerClient.cs @@ -0,0 +1,217 @@ +using Certify.Client; +using Certify.Management; +using Certify.Models.Hub; +using Certify.Models.Reporting; +using Certify.Server.Hub.Api.SignalR.ManagementHub; + +namespace Certify.Server.HubService.Services +{ + public class DirectInstanceManagementHub : IInstanceManagementHub + { + private IInstanceManagementStateProvider _stateProvider; + private ILogger _logger; + public DirectInstanceManagementHub(ILogger logger, IInstanceManagementStateProvider stateProvider) + { + _stateProvider = stateProvider; + _logger = logger; + } + + /// + /// Receive results from a previously issued command + /// + /// + /// + public Task ReceiveCommandResult(InstanceCommandResult result) + { + + result.Received = DateTimeOffset.Now; + + // check we are awaiting this result + var cmd = _stateProvider.GetAwaitedCommandRequest(result.CommandId); + + if (cmd == null && !result.IsCommandResponse) + { + // message was not requested and has been sent by the instance (e.g. heartbeat) + cmd = new InstanceCommandRequest { CommandId = result.CommandId, CommandType = result.CommandType }; + } + + if (cmd != null) + { + _stateProvider.RemoveAwaitedCommandRequest(cmd.CommandId); + + if (cmd.CommandType == ManagementHubCommands.GetInstanceInfo) + { + var instanceInfo = System.Text.Json.JsonSerializer.Deserialize(result.Value); + + if (instanceInfo != null) + { + + instanceInfo.LastReported = DateTimeOffset.Now; + _stateProvider.UpdateInstanceConnectionInfo("internal", instanceInfo); + + _logger?.LogInformation("Received instance {instanceId} {instanceTitle} for mgmt hub connection.", instanceInfo.InstanceId, instanceInfo.Title); + + // if we don't yet have any managed items for this instance, ask for them + if (!_stateProvider.HasItemsForManagedInstance(instanceInfo.InstanceId)) + { + var request = new InstanceCommandRequest + { + CommandId = Guid.NewGuid(), + CommandType = ManagementHubCommands.GetManagedItems + }; + + IssueCommand(request); + } + + // if we dont have a status summary, ask for that + if (!_stateProvider.HasStatusSummaryForManagedInstance(instanceInfo.InstanceId)) + { + var request = new InstanceCommandRequest + { + CommandId = Guid.NewGuid(), + CommandType = ManagementHubCommands.GetStatusSummary + }; + + IssueCommand(request); + } + } + } + else + { + // for all other command results we need to resolve which instance id we are communicating with + var instanceId = _stateProvider.GetInstanceIdForConnection("internal"); + result.InstanceId = instanceId; + + if (!string.IsNullOrWhiteSpace(instanceId)) + { + // action this message from this instance + _logger?.LogInformation("Received instance command result {result}", result.CommandType); + + if (cmd.CommandType == ManagementHubCommands.GetManagedItems) + { + // got items from an instance + var val = System.Text.Json.JsonSerializer.Deserialize(result.Value); + + _stateProvider.UpdateInstanceItemInfo(instanceId, val.Items); + } + else if (cmd.CommandType == ManagementHubCommands.GetStatusSummary && result?.Value != null) + { + // got status summary + var val = System.Text.Json.JsonSerializer.Deserialize(result.Value); + + _stateProvider.UpdateInstanceStatusSummary(instanceId, val); + } + else + { + // store for something else to consume + if (result.IsCommandResponse) + { + _stateProvider.AddAwaitedCommandResult(result); + } + else + { + // item was not requested, queue for processing + if (result.CommandType == ManagementHubCommands.NotificationUpdatedManagedItem) + { + //_uiStatusHub.Clients.All.SendAsync(Providers.StatusHubMessages.SendManagedCertificateUpdateMsg, System.Text.Json.JsonSerializer.Deserialize(result.Value)); + } + else if (result.CommandType == ManagementHubCommands.NotificationManagedItemRequestProgress) + { + //_uiStatusHub.Clients.All.SendAsync(Providers.StatusHubMessages.SendProgressStateMsg, System.Text.Json.JsonSerializer.Deserialize(result.Value)); + } + else if (result.CommandType == ManagementHubCommands.NotificationRemovedManagedItem) + { + // deleted :TODO + //_uiStatusHub.Clients.All.SendAsync(Providers.StatusHubMessages.SendMsg, $"Deleted item {result.Value}"); + } + } + } + } + else + { + _logger?.LogError("Received instance command result for an unknown instance {result}", result.CommandType); + } + } + } + + return Task.CompletedTask; + } + + public Task ReceiveInstanceMessage(InstanceMessage message) + { + + var instanceId = _stateProvider.GetInstanceIdForConnection("internal"); + if (instanceId != null) + { + // action this message from this instance + _logger?.LogInformation("Received instance message {msg}", message); + } + else + { + _logger?.LogError("Received instance command result for an unknown instance {msg}", message); + } + + return Task.CompletedTask; + } + + public Task SendCommandRequest(InstanceCommandRequest cmd) + { + IssueCommand(cmd); + + return Task.CompletedTask; + } + + private void IssueCommand(InstanceCommandRequest cmd) + { + _stateProvider.AddAwaitedCommandRequest(cmd); + + // + } + } + public class DirectManagementServerClient : Client.IManagementServerClient + { + public event Action OnConnectionClosed; + public event Action OnConnectionReconnected; + public event Action OnConnectionReconnecting; + public event Func> OnGetCommandResult; + public event Func OnGetInstanceItems; + + private ICertifyManager _certifyManager; + private IInstanceManagementHub _managementHub; + + private ManagedInstanceInfo _instanceInfo; + public DirectManagementServerClient(ICertifyManager certifyManager, IServiceProvider serviceProvider, IInstanceManagementHub instanceManagementHub) + { + _certifyManager = certifyManager; + _managementHub = instanceManagementHub; + _instanceInfo = certifyManager.GetManagedInstanceInfo(); + } + + Task IManagementServerClient.ConnectAsync() => Task.CompletedTask; + Task IManagementServerClient.Disconnect() => throw new NotImplementedException(); + bool IManagementServerClient.IsConnected() => true; + void IManagementServerClient.SendInstanceInfo(Guid commandId, bool isCommandResponse) + { + System.Diagnostics.Debug.WriteLine("SendInstanceInfo"); + + // send this clients instance ID back to the hub to identify it in the connection: should send a shared secret before this to confirm this client knows and is not impersonating another instance + var result = new InstanceCommandResult + { + CommandId = commandId, + InstanceId = _instanceInfo.InstanceId, + CommandType = ManagementHubCommands.GetInstanceInfo, + Value = System.Text.Json.JsonSerializer.Serialize(_instanceInfo), + IsCommandResponse = isCommandResponse + }; + + result.ObjectValue = _instanceInfo; + + _managementHub.ReceiveCommandResult(result); + } + void IManagementServerClient.SendNotificationToManagementHub(string msgCommandType, object updateMsg) + { + System.Diagnostics.Debug.WriteLine("SendInstanceInfo"); + + } + } +} From 870596309f08bde12652736631e57475a66e9d9e Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Thu, 6 Feb 2025 14:19:26 +0800 Subject: [PATCH 317/328] Hub: implement direct comms with in-process instance back to hub --- .../CertifyManager.ManagementHub.cs | 6 +- .../CertifyManager/ICertifyManager.cs | 4 +- .../Services/ManagementAPI.cs | 13 +- .../Services/DirectInstanceManagementHub.cs | 181 ++++++++++++++++++ .../Services/DirectManagementServerClient.cs | 163 ---------------- 5 files changed, 197 insertions(+), 170 deletions(-) create mode 100644 src/Certify.Server/Certify.Server.HubService/Services/DirectInstanceManagementHub.cs diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs index c845b214d..89b570beb 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.ManagementHub.cs @@ -91,7 +91,7 @@ private async Task StartManagementHubConnection(string hubUri) if (_managementServerClient != null) { - _managementServerClient.OnGetCommandResult -= PerformDirectHubCommandWithResult; + _managementServerClient.OnGetCommandResult -= PerformHubCommandWithResult; _managementServerClient.OnConnectionReconnecting -= _managementServerClient_OnConnectionReconnecting; } @@ -101,7 +101,7 @@ private async Task StartManagementHubConnection(string hubUri) { await _managementServerClient.ConnectAsync(); - _managementServerClient.OnGetCommandResult += PerformDirectHubCommandWithResult; + _managementServerClient.OnGetCommandResult += PerformHubCommandWithResult; _managementServerClient.OnConnectionReconnecting += _managementServerClient_OnConnectionReconnecting; } catch (Exception ex) @@ -112,7 +112,7 @@ private async Task StartManagementHubConnection(string hubUri) } } - public async Task PerformDirectHubCommandWithResult(InstanceCommandRequest arg) + public async Task PerformHubCommandWithResult(InstanceCommandRequest arg) { object val = null; diff --git a/src/Certify.Core/Management/CertifyManager/ICertifyManager.cs b/src/Certify.Core/Management/CertifyManager/ICertifyManager.cs index a1c3bd3d4..ee3ca06e5 100644 --- a/src/Certify.Core/Management/CertifyManager/ICertifyManager.cs +++ b/src/Certify.Core/Management/CertifyManager/ICertifyManager.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading.Tasks; @@ -87,7 +87,7 @@ public interface ICertifyManager Task CleanupManagedChallengeRequest(ManagedChallengeRequest request); Task UpdateManagementHub(string url, string joiningKey); - Task PerformDirectHubCommandWithResult(InstanceCommandRequest arg); + Task PerformHubCommandWithResult(InstanceCommandRequest arg); void SetDirectManagementClient(IManagementServerClient client); ManagedInstanceInfo GetManagedInstanceInfo(); diff --git a/src/Certify.Server/Certify.Server.Hub.Api/Services/ManagementAPI.cs b/src/Certify.Server/Certify.Server.Hub.Api/Services/ManagementAPI.cs index 6029ff610..d24dc00ff 100644 --- a/src/Certify.Server/Certify.Server.Hub.Api/Services/ManagementAPI.cs +++ b/src/Certify.Server/Certify.Server.Hub.Api/Services/ManagementAPI.cs @@ -65,7 +65,16 @@ private async Task SendCommandWithNoResult(string instanceId, InstanceCommandReq _mgmtStateProvider.AddAwaitedCommandRequest(cmd); - await _mgmtHubContext.Clients.Client(connectionId).SendCommandRequest(cmd); + if (_certifyManager != null && instanceId == _mgmtStateProvider.GetManagementHubInstanceId()) + { + // get command result directly from in-process instance + await _certifyManager.PerformHubCommandWithResult(cmd); + } + else + { + + await _mgmtHubContext.Clients.Client(connectionId).SendCommandRequest(cmd); + } } private async Task PerformInstanceCommandTaskWithResult(string instanceId, KeyValuePair[] args, string commandType) @@ -76,7 +85,7 @@ private async Task SendCommandWithNoResult(string instanceId, InstanceCommandReq if (_certifyManager != null && instanceId == _mgmtStateProvider.GetManagementHubInstanceId()) { // get command result directly from in-process instance - result = await _certifyManager.PerformDirectHubCommandWithResult(cmd); + result = await _certifyManager.PerformHubCommandWithResult(cmd); } else { diff --git a/src/Certify.Server/Certify.Server.HubService/Services/DirectInstanceManagementHub.cs b/src/Certify.Server/Certify.Server.HubService/Services/DirectInstanceManagementHub.cs new file mode 100644 index 000000000..bb5d1d323 --- /dev/null +++ b/src/Certify.Server/Certify.Server.HubService/Services/DirectInstanceManagementHub.cs @@ -0,0 +1,181 @@ +using Certify.Management; +using Certify.Models.Hub; +using Certify.Models.Reporting; +using Certify.Server.Hub.Api.SignalR.ManagementHub; + +namespace Certify.Server.HubService.Services +{ + public class DirectInstanceManagementHub : IInstanceManagementHub + { + private IInstanceManagementStateProvider _stateProvider; + private ILogger _logger; + private ICertifyManager _certifyManager; + public DirectInstanceManagementHub(ILogger logger, IInstanceManagementStateProvider stateProvider, ICertifyManager certifyManager) + { + _stateProvider = stateProvider; + _logger = logger; + _certifyManager = certifyManager; + } + + /// + /// Receive results from a previously issued command + /// + /// + /// + public Task ReceiveCommandResult(InstanceCommandResult result) + { + + result.Received = DateTimeOffset.Now; + + // check we are awaiting this result + var cmd = _stateProvider.GetAwaitedCommandRequest(result.CommandId); + + if (cmd == null && !result.IsCommandResponse) + { + // message was not requested and has been sent by the instance (e.g. heartbeat) + cmd = new InstanceCommandRequest { CommandId = result.CommandId, CommandType = result.CommandType }; + } + + if (cmd != null) + { + _stateProvider.RemoveAwaitedCommandRequest(cmd.CommandId); + + if (cmd.CommandType == ManagementHubCommands.GetInstanceInfo) + { + var instanceInfo = System.Text.Json.JsonSerializer.Deserialize(result.Value); + + if (instanceInfo != null) + { + + instanceInfo.LastReported = DateTimeOffset.Now; + _stateProvider.UpdateInstanceConnectionInfo("internal", instanceInfo); + + _logger?.LogInformation("Received instance {instanceId} {instanceTitle} for mgmt hub connection.", instanceInfo.InstanceId, instanceInfo.Title); + + // if we don't yet have any managed items for this instance, ask for them + if (!_stateProvider.HasItemsForManagedInstance(instanceInfo.InstanceId)) + { + var request = new InstanceCommandRequest + { + CommandId = Guid.NewGuid(), + CommandType = ManagementHubCommands.GetManagedItems + }; + + IssueCommand(request); + } + + // if we dont have a status summary, ask for that + if (!_stateProvider.HasStatusSummaryForManagedInstance(instanceInfo.InstanceId)) + { + var request = new InstanceCommandRequest + { + CommandId = Guid.NewGuid(), + CommandType = ManagementHubCommands.GetStatusSummary + }; + + IssueCommand(request); + } + } + } + else + { + // for all other command results we need to resolve which instance id we are communicating with + var instanceId = _stateProvider.GetInstanceIdForConnection("internal"); + result.InstanceId = instanceId; + + if (!string.IsNullOrWhiteSpace(instanceId)) + { + // action this message from this instance + _logger?.LogInformation("Received instance command result {result}", result.CommandType); + + if (cmd.CommandType == ManagementHubCommands.GetManagedItems) + { + // got items from an instance + var val = System.Text.Json.JsonSerializer.Deserialize(result.Value); + + _stateProvider.UpdateInstanceItemInfo(instanceId, val.Items); + } + else if (cmd.CommandType == ManagementHubCommands.GetStatusSummary && result?.Value != null) + { + // got status summary + var val = System.Text.Json.JsonSerializer.Deserialize(result.Value); + + _stateProvider.UpdateInstanceStatusSummary(instanceId, val); + } + else + { + // store for something else to consume + if (result.IsCommandResponse) + { + _stateProvider.AddAwaitedCommandResult(result); + } + else + { + // item was not requested, queue for processing + if (result.CommandType == ManagementHubCommands.NotificationUpdatedManagedItem) + { + //_uiStatusHub.Clients.All.SendAsync(Providers.StatusHubMessages.SendManagedCertificateUpdateMsg, System.Text.Json.JsonSerializer.Deserialize(result.Value)); + } + else if (result.CommandType == ManagementHubCommands.NotificationManagedItemRequestProgress) + { + //_uiStatusHub.Clients.All.SendAsync(Providers.StatusHubMessages.SendProgressStateMsg, System.Text.Json.JsonSerializer.Deserialize(result.Value)); + } + else if (result.CommandType == ManagementHubCommands.NotificationRemovedManagedItem) + { + // deleted :TODO + //_uiStatusHub.Clients.All.SendAsync(Providers.StatusHubMessages.SendMsg, $"Deleted item {result.Value}"); + } + } + } + } + else + { + _logger?.LogError("Received instance command result for an unknown instance {result}", result.CommandType); + } + } + } + + return Task.CompletedTask; + } + + public Task ReceiveInstanceMessage(InstanceMessage message) + { + + var instanceId = _stateProvider.GetInstanceIdForConnection("internal"); + if (instanceId != null) + { + // action this message from this instance + _logger?.LogInformation("Received instance message {msg}", message); + } + else + { + _logger?.LogError("Received instance command result for an unknown instance {msg}", message); + } + + return Task.CompletedTask; + } + + public Task SendCommandRequest(InstanceCommandRequest cmd) + { + IssueCommand(cmd); + + return Task.CompletedTask; + } + + private async void IssueCommand(InstanceCommandRequest cmd) + { + _stateProvider.AddAwaitedCommandRequest(cmd); + + // + var result = await _certifyManager.PerformHubCommandWithResult(cmd); + if (result.IsCommandResponse) + { + result.CommandType = cmd.CommandType; + result.CommandId = cmd.CommandId; + result.InstanceId = _stateProvider.GetManagementHubInstanceId(); + + await ReceiveCommandResult(result); + } + } + } +} diff --git a/src/Certify.Server/Certify.Server.HubService/Services/DirectManagementServerClient.cs b/src/Certify.Server/Certify.Server.HubService/Services/DirectManagementServerClient.cs index 5f316f3fc..fbc3cfb7d 100644 --- a/src/Certify.Server/Certify.Server.HubService/Services/DirectManagementServerClient.cs +++ b/src/Certify.Server/Certify.Server.HubService/Services/DirectManagementServerClient.cs @@ -1,173 +1,10 @@ using Certify.Client; using Certify.Management; using Certify.Models.Hub; -using Certify.Models.Reporting; using Certify.Server.Hub.Api.SignalR.ManagementHub; namespace Certify.Server.HubService.Services { - public class DirectInstanceManagementHub : IInstanceManagementHub - { - private IInstanceManagementStateProvider _stateProvider; - private ILogger _logger; - public DirectInstanceManagementHub(ILogger logger, IInstanceManagementStateProvider stateProvider) - { - _stateProvider = stateProvider; - _logger = logger; - } - - /// - /// Receive results from a previously issued command - /// - /// - /// - public Task ReceiveCommandResult(InstanceCommandResult result) - { - - result.Received = DateTimeOffset.Now; - - // check we are awaiting this result - var cmd = _stateProvider.GetAwaitedCommandRequest(result.CommandId); - - if (cmd == null && !result.IsCommandResponse) - { - // message was not requested and has been sent by the instance (e.g. heartbeat) - cmd = new InstanceCommandRequest { CommandId = result.CommandId, CommandType = result.CommandType }; - } - - if (cmd != null) - { - _stateProvider.RemoveAwaitedCommandRequest(cmd.CommandId); - - if (cmd.CommandType == ManagementHubCommands.GetInstanceInfo) - { - var instanceInfo = System.Text.Json.JsonSerializer.Deserialize(result.Value); - - if (instanceInfo != null) - { - - instanceInfo.LastReported = DateTimeOffset.Now; - _stateProvider.UpdateInstanceConnectionInfo("internal", instanceInfo); - - _logger?.LogInformation("Received instance {instanceId} {instanceTitle} for mgmt hub connection.", instanceInfo.InstanceId, instanceInfo.Title); - - // if we don't yet have any managed items for this instance, ask for them - if (!_stateProvider.HasItemsForManagedInstance(instanceInfo.InstanceId)) - { - var request = new InstanceCommandRequest - { - CommandId = Guid.NewGuid(), - CommandType = ManagementHubCommands.GetManagedItems - }; - - IssueCommand(request); - } - - // if we dont have a status summary, ask for that - if (!_stateProvider.HasStatusSummaryForManagedInstance(instanceInfo.InstanceId)) - { - var request = new InstanceCommandRequest - { - CommandId = Guid.NewGuid(), - CommandType = ManagementHubCommands.GetStatusSummary - }; - - IssueCommand(request); - } - } - } - else - { - // for all other command results we need to resolve which instance id we are communicating with - var instanceId = _stateProvider.GetInstanceIdForConnection("internal"); - result.InstanceId = instanceId; - - if (!string.IsNullOrWhiteSpace(instanceId)) - { - // action this message from this instance - _logger?.LogInformation("Received instance command result {result}", result.CommandType); - - if (cmd.CommandType == ManagementHubCommands.GetManagedItems) - { - // got items from an instance - var val = System.Text.Json.JsonSerializer.Deserialize(result.Value); - - _stateProvider.UpdateInstanceItemInfo(instanceId, val.Items); - } - else if (cmd.CommandType == ManagementHubCommands.GetStatusSummary && result?.Value != null) - { - // got status summary - var val = System.Text.Json.JsonSerializer.Deserialize(result.Value); - - _stateProvider.UpdateInstanceStatusSummary(instanceId, val); - } - else - { - // store for something else to consume - if (result.IsCommandResponse) - { - _stateProvider.AddAwaitedCommandResult(result); - } - else - { - // item was not requested, queue for processing - if (result.CommandType == ManagementHubCommands.NotificationUpdatedManagedItem) - { - //_uiStatusHub.Clients.All.SendAsync(Providers.StatusHubMessages.SendManagedCertificateUpdateMsg, System.Text.Json.JsonSerializer.Deserialize(result.Value)); - } - else if (result.CommandType == ManagementHubCommands.NotificationManagedItemRequestProgress) - { - //_uiStatusHub.Clients.All.SendAsync(Providers.StatusHubMessages.SendProgressStateMsg, System.Text.Json.JsonSerializer.Deserialize(result.Value)); - } - else if (result.CommandType == ManagementHubCommands.NotificationRemovedManagedItem) - { - // deleted :TODO - //_uiStatusHub.Clients.All.SendAsync(Providers.StatusHubMessages.SendMsg, $"Deleted item {result.Value}"); - } - } - } - } - else - { - _logger?.LogError("Received instance command result for an unknown instance {result}", result.CommandType); - } - } - } - - return Task.CompletedTask; - } - - public Task ReceiveInstanceMessage(InstanceMessage message) - { - - var instanceId = _stateProvider.GetInstanceIdForConnection("internal"); - if (instanceId != null) - { - // action this message from this instance - _logger?.LogInformation("Received instance message {msg}", message); - } - else - { - _logger?.LogError("Received instance command result for an unknown instance {msg}", message); - } - - return Task.CompletedTask; - } - - public Task SendCommandRequest(InstanceCommandRequest cmd) - { - IssueCommand(cmd); - - return Task.CompletedTask; - } - - private void IssueCommand(InstanceCommandRequest cmd) - { - _stateProvider.AddAwaitedCommandRequest(cmd); - - // - } - } public class DirectManagementServerClient : Client.IManagementServerClient { public event Action OnConnectionClosed; From b01c261460c96de2971faf2eb6668793834c76d7 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Thu, 6 Feb 2025 14:22:57 +0800 Subject: [PATCH 318/328] Aspire: conditionally launch either combined hubservice or individual services --- .../Certify.Aspire.AppHost.csproj | 1 + .../Certify.Aspire.AppHost/Program.cs | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/Certify.Aspire/Certify.Aspire.AppHost/Certify.Aspire.AppHost.csproj b/src/Certify.Aspire/Certify.Aspire.AppHost/Certify.Aspire.AppHost.csproj index 2dde524f8..a8688f6a1 100644 --- a/src/Certify.Aspire/Certify.Aspire.AppHost/Certify.Aspire.AppHost.csproj +++ b/src/Certify.Aspire/Certify.Aspire.AppHost/Certify.Aspire.AppHost.csproj @@ -16,6 +16,7 @@ + diff --git a/src/Certify.Aspire/Certify.Aspire.AppHost/Program.cs b/src/Certify.Aspire/Certify.Aspire.AppHost/Program.cs index 7519fdf6a..27052143f 100644 --- a/src/Certify.Aspire/Certify.Aspire.AppHost/Program.cs +++ b/src/Certify.Aspire/Certify.Aspire.AppHost/Program.cs @@ -1,7 +1,17 @@ var builder = DistributedApplication.CreateBuilder(args); -builder.AddProject("certifyserverhubapi"); +var useIndependentServices = false; -builder.AddProject("certifyservercore"); +if (useIndependentServices) +{ + builder.AddProject("certifyserverhubapi"); + + builder.AddProject("certifyservercore"); +} +else +{ + // use combined hubservice + builder.AddProject("certify-server-hubservice"); +} builder.Build().Run(); From e4f44e6c1de97344ad3479a9bcd8f8843e9e1ca0 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Thu, 6 Feb 2025 14:28:29 +0800 Subject: [PATCH 319/328] PluginManager: support injecting plugin dependencies --- .../CertifyManager/CertifyManager.cs | 16 +++++++------- src/Certify.Models/Certify.Models.csproj | 1 + .../Plugins/PluginProviderBase.cs | 22 ++++++++++++++++++- .../Management/PluginManager.cs | 22 ++++++++++++++++--- 4 files changed, 49 insertions(+), 12 deletions(-) diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.cs index 8174f2847..feb455e88 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; @@ -100,14 +100,16 @@ public partial class CertifyManager : ICertifyManager, IDisposable private System.Timers.Timer _hourlyTimer; private System.Timers.Timer _dailyTimer; - public CertifyManager() : this(true) + private IServiceProvider _injectedServiceProvider; + public CertifyManager(IServiceProvider injectedServiceProvider) : this() { - + _injectedServiceProvider = injectedServiceProvider; } - public CertifyManager(bool useWindowsNativeFeatures = true) + public CertifyManager() { - _useWindowsNativeFeatures = useWindowsNativeFeatures; + // load setting here so that we know our instance ID etc early on. Other longer tasks are deferred until Init is called. + SettingsManager.LoadAppSettings(); } public async Task Init() @@ -116,13 +118,11 @@ public async Task Init() _serverConfig = SharedUtils.ServiceConfigManager.GetAppServiceConfig(); - SettingsManager.LoadAppSettings(); - InitLogging(_serverConfig); Util.SetSupportedTLSVersions(); - _pluginManager = new PluginManager + _pluginManager = new PluginManager(_injectedServiceProvider) { EnableExternalPlugins = CoreAppSettings.Current.IncludeExternalPlugins }; diff --git a/src/Certify.Models/Certify.Models.csproj b/src/Certify.Models/Certify.Models.csproj index de7677b82..ef6670264 100644 --- a/src/Certify.Models/Certify.Models.csproj +++ b/src/Certify.Models/Certify.Models.csproj @@ -21,6 +21,7 @@ all runtime; build; native; contentfiles; analyzers + all diff --git a/src/Certify.Models/Plugins/PluginProviderBase.cs b/src/Certify.Models/Plugins/PluginProviderBase.cs index 8e3faf146..f58a75fdb 100644 --- a/src/Certify.Models/Plugins/PluginProviderBase.cs +++ b/src/Certify.Models/Plugins/PluginProviderBase.cs @@ -3,12 +3,25 @@ using System.Linq; using Certify.Models.Config; using Certify.Models.Plugins; +using Microsoft.Extensions.DependencyInjection; namespace Certify.Plugins { public class PluginProviderBase : IProviderPlugin { + public PluginProviderBase() + { + } + + // optionally support dependency injection + public PluginProviderBase(IServiceProvider serviceProvider) + { + _services = serviceProvider; + } + + private IServiceProvider? _services { get; } + public TProviderInterface GetProvider(Type pluginType, string? id) { @@ -26,7 +39,14 @@ public TProviderInterface GetProvider(Type pluginType, string? id) { if ((def as ProviderDefinition)?.Id?.ToLowerInvariant() == id) { - return (TProviderInterface)Activator.CreateInstance(t); + if (_services == null) + { + return (TProviderInterface)Activator.CreateInstance(t); + } + else + { + return (TProviderInterface)ActivatorUtilities.CreateInstance(_services, t); + } } } } diff --git a/src/Certify.Shared/Management/PluginManager.cs b/src/Certify.Shared/Management/PluginManager.cs index 45570ced7..227e56c74 100644 --- a/src/Certify.Shared/Management/PluginManager.cs +++ b/src/Certify.Shared/Management/PluginManager.cs @@ -7,6 +7,7 @@ using Certify.Models; using Certify.Models.Config; using Certify.Models.Plugins; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Serilog; using Serilog.Extensions.Logging; @@ -52,6 +53,16 @@ public class PluginManager private Models.Providers.ILog _log = null; + private IServiceProvider _services; + + /// + /// + /// + /// + public PluginManager(IServiceProvider serviceProvider) : this() + { + _services = serviceProvider; + } public PluginManager() { var serilogLogger = new LoggerConfiguration() @@ -124,9 +135,14 @@ private T LoadPlugin(string dllFileName, string pluginFolder = null) if (pluginType != null) { - var obj = (T)Activator.CreateInstance(pluginType); - - return obj; + if (_services == null) + { + return (T)Activator.CreateInstance(pluginType); + } + else + { + return (T)ActivatorUtilities.CreateInstance(_services, pluginType); + } } else { From 6b5f8c2b10f11a615b5a09b22c413efdc08068e6 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Thu, 6 Feb 2025 14:31:09 +0800 Subject: [PATCH 320/328] Log to MS logging (in service) when logging to serilog --- src/Certify.Core/Certify.Core.csproj | 2 ++ .../Management/CertifyManager/CertifyManager.cs | 3 ++- .../Management/CertifyManager/ICertifyManager.cs | 2 +- .../Certify.Server.HubService.csproj | 1 + .../Certify.Server.HubService/Program.cs | 14 ++++++++++---- 5 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/Certify.Core/Certify.Core.csproj b/src/Certify.Core/Certify.Core.csproj index e9a382c5d..c6fcbb64a 100644 --- a/src/Certify.Core/Certify.Core.csproj +++ b/src/Certify.Core/Certify.Core.csproj @@ -53,6 +53,8 @@ + + diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.cs index feb455e88..5e76ddbbd 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.cs @@ -381,10 +381,11 @@ private void InitLogging(Shared.ServiceConfig serverConfig) var serilogLog = new Serilog.LoggerConfiguration() .Enrich.FromLogContext() .MinimumLevel.ControlledBy(ManagedCertificateLog.LogLevelSwitchFromLogLevel(_loggingLevelSwitch)) + .WriteTo.Console() .WriteTo.File(Path.Combine(EnvironmentUtil.CreateAppDataPath("logs"), "session.log"), shared: true, flushToDiskInterval: new TimeSpan(0, 0, 10), rollOnFileSizeLimit: true, fileSizeLimitBytes: 5 * 1024 * 1024) .CreateLogger(); - var msLogger = new Serilog.Extensions.Logging.SerilogLoggerFactory(serilogLog).CreateLogger(); + var msLogger = new Serilog.Extensions.Logging.SerilogLoggerFactory(serilogLog).CreateLogger(); _serviceLog = new Loggy(msLogger); diff --git a/src/Certify.Core/Management/CertifyManager/ICertifyManager.cs b/src/Certify.Core/Management/CertifyManager/ICertifyManager.cs index ee3ca06e5..cb77732f9 100644 --- a/src/Certify.Core/Management/CertifyManager/ICertifyManager.cs +++ b/src/Certify.Core/Management/CertifyManager/ICertifyManager.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading.Tasks; diff --git a/src/Certify.Server/Certify.Server.HubService/Certify.Server.HubService.csproj b/src/Certify.Server/Certify.Server.HubService/Certify.Server.HubService.csproj index 834a5a0ac..356fbcc32 100644 --- a/src/Certify.Server/Certify.Server.HubService/Certify.Server.HubService.csproj +++ b/src/Certify.Server/Certify.Server.HubService/Certify.Server.HubService.csproj @@ -14,6 +14,7 @@ + diff --git a/src/Certify.Server/Certify.Server.HubService/Program.cs b/src/Certify.Server/Certify.Server.HubService/Program.cs index 44c68c9dd..03ba26e22 100644 --- a/src/Certify.Server/Certify.Server.HubService/Program.cs +++ b/src/Certify.Server/Certify.Server.HubService/Program.cs @@ -1,4 +1,5 @@ using Certify.Client; +using Certify.Management; using Certify.Server.Hub.Api.Middleware; using Certify.Server.Hub.Api.Services; using Certify.Server.Hub.Api.SignalR; @@ -7,6 +8,7 @@ using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.AspNetCore.SignalR; using Microsoft.AspNetCore.StaticFiles; +using Serilog; var builder = WebApplication.CreateBuilder(args); @@ -39,11 +41,11 @@ // Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi builder.Services.AddOpenApi(); -var certifyManager = new Certify.Management.CertifyManager(); -await certifyManager.Init(); +builder.Services.AddLogging(loggingBuilder => + loggingBuilder.AddSerilog(dispose: true)); // setup public/hub api -builder.Services.AddSingleton(certifyManager); +builder.Services.AddSingleton(); builder.Services.AddTransient(typeof(ICertifyInternalApiClient), typeof(CertifyHubService)); @@ -114,7 +116,11 @@ // setup signalr message forwarding, message received from internal service will be resent to our connected clients via our own SignalR hub var statusReporting = new UserInterfaceStatusHubReporting(statusHubContext); -//UserInterfaceStatusHubReporting _statusReporting = new UserInterfaceStatusHubReporting(); + +// wire up internal service to our hub + +var certifyManager = app.Services.GetRequiredService(); +await certifyManager.Init(); var directServerClient = app.Services.GetRequiredService(); certifyManager.SetDirectManagementClient(directServerClient); From c80a26d2812215956207d92d9d1498d701a42bb5 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Thu, 6 Feb 2025 14:35:59 +0800 Subject: [PATCH 321/328] Managed Challenge: log result code on failure --- .../DNS/CertifyManaged/DnsProviderCertifyManaged.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Certify.Providers/DNS/CertifyManaged/DnsProviderCertifyManaged.cs b/src/Certify.Providers/DNS/CertifyManaged/DnsProviderCertifyManaged.cs index a68ce4b86..8f5650536 100644 --- a/src/Certify.Providers/DNS/CertifyManaged/DnsProviderCertifyManaged.cs +++ b/src/Certify.Providers/DNS/CertifyManaged/DnsProviderCertifyManaged.cs @@ -141,7 +141,7 @@ public async Task CreateRecord(DnsRecord request) } else { - return new ActionResult { IsSuccess = false, Message = $"Update failed: API URL is valid [{apiUri}], auth credentials are correct and authorised for a matching managed challenge." }; + return new ActionResult { IsSuccess = false, Message = $"Update failed [{result.StatusCode}] : check API URL is valid [{apiUri}], auth credentials are correct and authorised for a matching managed challenge." }; } } catch (Exception exp) @@ -185,7 +185,7 @@ public async Task DeleteRecord(DnsRecord request) } else { - return new ActionResult { IsSuccess = false, Message = $"Cleanup failed: API URL is valid [{apiUri}], auth credentials are correct and authorised for a matching managed challenge." }; + return new ActionResult { IsSuccess = false, Message = $"Cleanup failed [{result.StatusCode}] : check API URL is valid [{apiUri}], auth credentials are correct and authorised for a matching managed challenge." }; } } catch (Exception exp) From 7bdfdeec3152247b88e99bbea6ec685c67763bd6 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Thu, 6 Feb 2025 14:37:39 +0800 Subject: [PATCH 322/328] Cloudflare: log warning if zones fail to fetch --- src/Certify.Providers/DNS/Cloudflare/DnsProviderCloudflare.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Certify.Providers/DNS/Cloudflare/DnsProviderCloudflare.cs b/src/Certify.Providers/DNS/Cloudflare/DnsProviderCloudflare.cs index a1d68ebf7..29624badc 100644 --- a/src/Certify.Providers/DNS/Cloudflare/DnsProviderCloudflare.cs +++ b/src/Certify.Providers/DNS/Cloudflare/DnsProviderCloudflare.cs @@ -409,6 +409,7 @@ public async Task> GetZones() } else { + _log?.Warning("{provider} Failed to fetching DNS Zones: {resultStatus}", nameof(DnsProviderCloudflare), result.StatusCode); return new List(); } } @@ -428,7 +429,6 @@ public async Task InitProvider(Dictionary credentials, Dic { throw new ArgumentException(credentialError); } - ; _authKey = credentials.ContainsKey("authkey") ? credentials["authkey"] : null; _apiToken = credentials.ContainsKey("apitoken") ? credentials["apitoken"] : null; From 55e014f85d2a5456f78a4e52b50853eabb9460db Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Thu, 6 Feb 2025 18:07:13 +0800 Subject: [PATCH 323/328] Package updates --- src/Certify.Client/Certify.Client.csproj | 2 +- src/Certify.Core/Certify.Core.csproj | 2 +- .../DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj | 2 +- .../Certify.Server.Core/Certify.Server.Core.csproj | 2 +- .../Certify.Core.Tests.Integration.csproj | 2 +- .../Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj | 2 +- .../Certify.Service.Tests.Integration.csproj | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Certify.Client/Certify.Client.csproj b/src/Certify.Client/Certify.Client.csproj index 3c594a1eb..35bfd602b 100644 --- a/src/Certify.Client/Certify.Client.csproj +++ b/src/Certify.Client/Certify.Client.csproj @@ -20,7 +20,7 @@ - + diff --git a/src/Certify.Core/Certify.Core.csproj b/src/Certify.Core/Certify.Core.csproj index c6fcbb64a..9b0d635ec 100644 --- a/src/Certify.Core/Certify.Core.csproj +++ b/src/Certify.Core/Certify.Core.csproj @@ -52,7 +52,7 @@ 1701;1702;CA1068 - + diff --git a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj index 9b0ab2a0e..904f0da27 100644 --- a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj +++ b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj index de14495b5..3e27a1986 100644 --- a/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj +++ b/src/Certify.Server/Certify.Server.Core/Certify.Server.Core/Certify.Server.Core.csproj @@ -10,7 +10,7 @@ - + diff --git a/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj b/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj index 3d30b6d17..4e8fbae1b 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.Core.Tests.Integration/Certify.Core.Tests.Integration.csproj @@ -80,7 +80,7 @@ - + diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj index c8ad54a68..b09488563 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj +++ b/src/Certify.Tests/Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj @@ -112,7 +112,7 @@ - + diff --git a/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj b/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj index 671368bc9..afa226f31 100644 --- a/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj +++ b/src/Certify.Tests/Certify.Service.Tests.Integration/Certify.Service.Tests.Integration.csproj @@ -64,7 +64,7 @@ - + From 00af687f09cb2d964bcc285782b391ba5edffe5d Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Thu, 6 Feb 2025 18:07:41 +0800 Subject: [PATCH 324/328] Access Control Boostrap: skip integrity checks for first run --- .../Management/CertifyManager/CertifyManager.Maintenance.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.Maintenance.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.Maintenance.cs index f86babc42..0f33bc531 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.Maintenance.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.Maintenance.cs @@ -90,7 +90,7 @@ private static async Task BootstrapAdminUserAndRoles(IAccessControl access) foreach (var r in assignedRoles) { // add roles and policy assignments to store - await access.AddAssignedRole(adminSp.Id, r); + await access.AddAssignedRole(adminSp.Id, r, bypassIntegrityCheck: true); } } @@ -109,7 +109,7 @@ private static async Task UpdateStandardRoles(IAccessControl access) foreach (var action in actions) { - await access.AddResourceAction(adminSvcPrinciple, action); + await access.AddResourceAction(adminSvcPrinciple, action, bypassIntegrityCheck: true); } // setup policies with actions @@ -128,7 +128,7 @@ private static async Task UpdateStandardRoles(IAccessControl access) foreach (var r in roles) { // add roles and policy assignments to store - await access.AddRole(adminSvcPrinciple, r); + await access.AddRole(adminSvcPrinciple, r, bypassIntegrityCheck: true); } } From 1cafe1e2c591e7baad6b9d413f7ea3a790129e60 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Fri, 7 Feb 2025 16:21:02 +0800 Subject: [PATCH 325/328] Managed Challenge plugin - log missing config --- .../DNS/CertifyManaged/DnsProviderCertifyManaged.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/Certify.Providers/DNS/CertifyManaged/DnsProviderCertifyManaged.cs b/src/Certify.Providers/DNS/CertifyManaged/DnsProviderCertifyManaged.cs index 8f5650536..76588d6af 100644 --- a/src/Certify.Providers/DNS/CertifyManaged/DnsProviderCertifyManaged.cs +++ b/src/Certify.Providers/DNS/CertifyManaged/DnsProviderCertifyManaged.cs @@ -200,6 +200,12 @@ public async Task InitProvider(Dictionary credentials, Dic _log = log; _parameters = parameters; + if (_credentials == null || _credentials.Count == 0) + { + _log.Error("Certify Managed Challenge DNS Provider could not be created: credentials missing or not set for managed challenge API."); + return false; + } + if (parameters?.ContainsKey("propagationdelay") == true) { if (int.TryParse(parameters["propagationdelay"], out var customPropDelay)) @@ -219,6 +225,11 @@ public async Task InitProvider(Dictionary credentials, Dic _client.BaseAddress = _apiBaseUri; } + else + { + _log.Error("Certify Managed Challenge DNS Provider could not be created: managed challenge API URL not set."); + return false; + } return await Task.FromResult(true); } From 0bb93d8b85d6887410e7bb17996a08f5fa4fcfc2 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Sat, 8 Feb 2025 11:31:21 +0800 Subject: [PATCH 326/328] Update README.md --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index cb6b87835..2e3612c3a 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,7 @@ Windows ACME Certificate Manager, powered by [Let's Encrypt](https://letsencrypt **Certify The Web is used by hundreds of thousands of organisations to manage millions of certificates each month** and is the perfect solution for administrators who want visibility of certificate management for their domains. Centralised dashboard status reporting is also available. -![Stars]( -https://img.shields.io/github/stars/webprofusion/certify.svg) +**If you use our app, spread the word and don't forget to Star us on GitHub!** ![Certify App Screenshot](docs/images/app-screenshot.png) From f0f978c8f66c48f8bcb65b2480b4d243a2ceb713 Mon Sep 17 00:00:00 2001 From: Mark Cilia Vincenti Date: Sat, 8 Feb 2025 10:28:45 +0100 Subject: [PATCH 327/328] net9locking with source generator --- Directory.Build.props | 1 + src/Certify.Core/Certify.Core.csproj | 10 ++++++++++ .../CertifyManager/CertifyManager.Account.cs | 2 +- .../CertifyManager/CertifyManager.DataStores.cs | 2 +- .../Management/Servers/ServerProviderIIS.cs | 2 +- src/Certify.Core/Management/SettingsManager.cs | 4 ++-- src/Certify.Shared/Certify.Shared.Core.csproj | 10 ++++++++++ src/Certify.Shared/Utils/HttpChallengeServer.cs | 4 ++-- 8 files changed, 28 insertions(+), 7 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 5a4901c12..49a4da4fe 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -10,6 +10,7 @@ false true full + latest diff --git a/src/Certify.Core/Certify.Core.csproj b/src/Certify.Core/Certify.Core.csproj index 9b0d635ec..54b5481ba 100644 --- a/src/Certify.Core/Certify.Core.csproj +++ b/src/Certify.Core/Certify.Core.csproj @@ -71,4 +71,14 @@ + + + all + analyzers + + + + + + \ No newline at end of file diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.Account.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.Account.cs index bc7cf9347..8ea57b480 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.Account.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.Account.cs @@ -14,7 +14,7 @@ namespace Certify.Management { public partial class CertifyManager { - private static object _accountsLock = new object(); + private static Lock _accountsLock = LockFactory.Create(); private List _accounts; /// diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.DataStores.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.DataStores.cs index c9fc8044c..dbc88ae22 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.DataStores.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.DataStores.cs @@ -11,7 +11,7 @@ namespace Certify.Management { public partial class CertifyManager { - private object _dataStoreLocker = new object(); + private Lock _dataStoreLocker = LockFactory.Create(); private async Task GetManagedItemStoreProvider(DataStoreConnection dataStore) { diff --git a/src/Certify.Core/Management/Servers/ServerProviderIIS.cs b/src/Certify.Core/Management/Servers/ServerProviderIIS.cs index bcb78fc80..bf30e946f 100644 --- a/src/Certify.Core/Management/Servers/ServerProviderIIS.cs +++ b/src/Certify.Core/Management/Servers/ServerProviderIIS.cs @@ -23,7 +23,7 @@ public class ServerProviderIIS : ITargetWebServer /// /// We use a lock on any method that uses CommitChanges, to avoid writing changes at the same time /// - private static readonly object _iisAPILock = new object(); + private static readonly Lock _iisAPILock = LockFactory.Create(); private ILog _log; diff --git a/src/Certify.Core/Management/SettingsManager.cs b/src/Certify.Core/Management/SettingsManager.cs index 7f31f977d..326550521 100644 --- a/src/Certify.Core/Management/SettingsManager.cs +++ b/src/Certify.Core/Management/SettingsManager.cs @@ -8,7 +8,7 @@ namespace Certify.Management public sealed class CoreAppSettings { private static volatile CoreAppSettings instance; - private static object syncRoot = new object(); + private static Lock syncRoot = LockFactory.Create(); private CoreAppSettings() { @@ -190,7 +190,7 @@ public static CoreAppSettings Current public class SettingsManager { private const string COREAPPSETTINGSFILE = "appsettings.json"; - private static Object settingsLocker = new Object(); + private static Lock settingsLocker = LockFactory.Create(); public static bool FromPreferences(Models.Preferences prefs) { diff --git a/src/Certify.Shared/Certify.Shared.Core.csproj b/src/Certify.Shared/Certify.Shared.Core.csproj index fc3524813..26a2917af 100644 --- a/src/Certify.Shared/Certify.Shared.Core.csproj +++ b/src/Certify.Shared/Certify.Shared.Core.csproj @@ -39,4 +39,14 @@ + + + all + analyzers + + + + + + diff --git a/src/Certify.Shared/Utils/HttpChallengeServer.cs b/src/Certify.Shared/Utils/HttpChallengeServer.cs index e0c45a2c2..f9799c8a7 100644 --- a/src/Certify.Shared/Utils/HttpChallengeServer.cs +++ b/src/Certify.Shared/Utils/HttpChallengeServer.cs @@ -51,8 +51,8 @@ public class HttpChallengeServer private int _autoCloseSeconds = 60; private string _baseUri = ""; private Timer _autoCloseTimer; - private readonly object _challengeServerStartLock = new object(); - private readonly object _challengeServerStopLock = new object(); + private readonly Lock _challengeServerStartLock = LockFactory.Create(); + private readonly Lock _challengeServerStopLock = LockFactory.Create(); /// /// If true, challenge server has been started or a start has been attempted From f4cfb735bb7aa601a1050587b9b4071328c9e2c5 Mon Sep 17 00:00:00 2001 From: Mark Cilia Vincenti Date: Sat, 8 Feb 2025 10:36:32 +0100 Subject: [PATCH 328/328] Fix Certify.UI.sln --- src/Certify.UI.sln | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Certify.UI.sln b/src/Certify.UI.sln index e202963f1..5c542e462 100644 --- a/src/Certify.UI.sln +++ b/src/Certify.UI.sln @@ -17,6 +17,7 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{12876723-F648-4E76-9242-110F5635A4B1}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig + ..\Directory.Build.props = ..\Directory.Build.props EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{9A70DEC0-70C4-42A7-B15A-647EC432E7F7}"