From d82872918822d73bef7624c03d4966e8f8768b05 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Mon, 15 May 2023 10:41:51 +0800 Subject: [PATCH 001/237] 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 9ef5a7d926edb07e3d90a92ad7bc861ab55de413 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Wed, 17 May 2023 12:55:36 +0800 Subject: [PATCH 002/237] 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 dca8fac8c9e7fc9a08c1711d52b5597f10eee86f Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Fri, 19 May 2023 11:12:11 +0800 Subject: [PATCH 003/237] 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 ef9a8a5ca..727b93613 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 c0439e6d4..f1d0a157e 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 9d3c8e642..ae0b8939c 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 18b711624..96d8fbd3d 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 97fd3ae55c100af1ea880d0c1bfd19ac3ab0d4c6 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Fri, 19 May 2023 11:49:07 +0800 Subject: [PATCH 004/237] 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 28bb55ac696072fcf2b41973169e6017b9ff7db2 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Fri, 19 May 2023 13:30:11 +0800 Subject: [PATCH 005/237] 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 7a37fc3f9e8ab5d9bf14ceab596d0255d462489a Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 25 May 2023 16:34:48 +0800 Subject: [PATCH 006/237] 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 85d56b298c1eff8af32f284886fc3fd302eef5aa Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Thu, 25 May 2023 17:11:44 +0800 Subject: [PATCH 007/237] 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 bb52af7be21e468cdf3aecec4695e962643760bb Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Tue, 30 May 2023 12:05:21 +0800 Subject: [PATCH 008/237] 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 727b93613..6c3b6449c 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 96d8fbd3d..4cb5cb968 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 89dfed39122ebf5f9a9c85faa4a2108aea9fc086 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Thu, 1 Jun 2023 14:18:05 +0800 Subject: [PATCH 009/237] 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 686a6bebadc0c3911b5971d24f11ae846d3c469c Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Thu, 1 Jun 2023 14:18:55 +0800 Subject: [PATCH 010/237] 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 30966553d2c91ea991428992dab6aed95d0ee16f Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Thu, 1 Jun 2023 14:19:26 +0800 Subject: [PATCH 011/237] 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 eab0ede2dcd26964eb3bbf6aa222234b547d2457 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Thu, 8 Jun 2023 11:48:14 +0800 Subject: [PATCH 012/237] 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 400891b60ffc49d3aff7e2af582ea7cc676ee0b3 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 15 Jun 2023 17:34:01 +0800 Subject: [PATCH 013/237] 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 6c3b6449c..524064b0c 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 f32c1df3bf3116f6e2f8c1682569e7f97f003383 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Mon, 19 Jun 2023 13:34:18 +0800 Subject: [PATCH 014/237] 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 d040edfe618d786f83eda98f7d33453b2ae055b8 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Mon, 19 Jun 2023 15:08:59 +0800 Subject: [PATCH 015/237] 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 5f66afb2659a92fa008ff6964e8599f759ed6b40 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Wed, 28 Jun 2023 17:23:01 +0800 Subject: [PATCH 016/237] 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 0ad4cd514a792fc6cb5eb71705f952f0a77c2d9f Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Wed, 28 Jun 2023 17:33:42 +0800 Subject: [PATCH 017/237] 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 ccf4886c194c20118d00c155926737406d98ebf0 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Fri, 30 Jun 2023 16:54:24 +0800 Subject: [PATCH 018/237] 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 c3293209093cef2a19d7a1427cca0bf33e0a4b8e Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Wed, 17 May 2023 12:55:36 +0800 Subject: [PATCH 019/237] 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 7489345ff598142593d6891ac67506be0aadd357 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Fri, 19 May 2023 11:12:11 +0800 Subject: [PATCH 020/237] 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 f2e179d2833a59a3242b35032a254b9126400482 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Thu, 1 Jun 2023 14:18:05 +0800 Subject: [PATCH 021/237] 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 9e840731d55e6590d3fd7e4970237baf7572f349 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 15 Jun 2023 17:34:01 +0800 Subject: [PATCH 022/237] 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 3c3170ad1ff1b9f14afa72d8622e7baab667a22c Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Fri, 16 Jun 2023 15:19:20 +0800 Subject: [PATCH 023/237] 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 ad92710bc4dccd3cb9080c51f05ea026ac79ff69 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Wed, 28 Jun 2023 17:23:01 +0800 Subject: [PATCH 024/237] 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 27129b4c42c9ed992455817501e83db829d9a153 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Mon, 31 Jul 2023 17:27:17 +0800 Subject: [PATCH 025/237] 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 524064b0c..6c3b6449c 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 ed83c166c9de9d2e58d17dca8d86cd608bf69db6 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Wed, 2 Aug 2023 16:23:10 +0800 Subject: [PATCH 026/237] 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 719a0c07e24dd9a351a138e26bc6ad2e9bcec46f Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Fri, 4 Aug 2023 16:08:43 +0800 Subject: [PATCH 027/237] 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 7348840479ac4a74eb2fc4f6a70bbe324b5a3fdd Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Fri, 4 Aug 2023 16:40:56 +0800 Subject: [PATCH 028/237] 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 69740dcd8c0d6e040ca743a0d8d06b587c51c480 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Mon, 7 Aug 2023 13:52:31 +0800 Subject: [PATCH 029/237] 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 6c3b6449c..e0a05c825 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 f1d0a157e..c0b3c3f83 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 ae0b8939c..554077f07 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 4cb5cb968..7933dedb0 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 cb128aa9b1890f2bf3552c8762f2848500fe8918 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 17 Aug 2023 08:31:06 +0800 Subject: [PATCH 030/237] 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 98b0fe211dc485c0e5661b78cacbd6664f69d47a Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 17 Aug 2023 08:32:20 +0800 Subject: [PATCH 031/237] 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 10a959f601761c13ddc4cf8d42aa44257f57f8ae Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 17 Aug 2023 08:32:48 +0800 Subject: [PATCH 032/237] 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 211818fb027b37fc387efc7e8aeae732ffbcec91 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 17 Aug 2023 12:32:18 +0800 Subject: [PATCH 033/237] 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 a6d6f0fe46c0ab2851b1dbc151dd697d88c36bad Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 17 Aug 2023 12:33:25 +0800 Subject: [PATCH 034/237] 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 c707904803039c2e2b60dd3f38195a28ff0ae9a7 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 17 Aug 2023 12:33:53 +0800 Subject: [PATCH 035/237] 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/237] 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 e0a05c825..90159d5b0 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 7933dedb0..b5b62b9b6 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 de12aa68badb25f6b95c1f53665a939695829940 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Tue, 22 Aug 2023 13:39:20 +0800 Subject: [PATCH 044/237] Package updates --- src/Certify.Models/Certify.Models.csproj | 2 +- .../DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj | 2 +- .../Certify.Server.Api.Public.Tests.csproj | 2 +- .../Certify.Core.Tests.Integration.csproj | 2 +- .../Certify.Core.Tests.Unit/Certify.Core.Tests.Unit.csproj | 2 +- .../Certify.Service.Tests.Integration.csproj | 2 +- .../Certify.UI.Tests.Integration.csproj | 2 +- 7 files changed, 7 insertions(+), 7 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 @@ - + 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 90159d5b0..2ffb0f7a0 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 c0b3c3f83..adf2d424e 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,7 +133,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 554077f07..365a305a6 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,7 +77,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 b5b62b9b6..c92e4ae0d 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 996b508a5ec215ce8ceb6895e2849aedd5a394aa Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Tue, 22 Aug 2023 13:39:36 +0800 Subject: [PATCH 045/237] 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 92870ce373a7fd9df4b2184a5e4ddc146d878de4 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Sat, 26 Aug 2023 09:58:45 +0800 Subject: [PATCH 046/237] 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 1d11fcb928a08b63b544003f5b8568afac0dcff4 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 31 Aug 2023 17:17:42 +0800 Subject: [PATCH 047/237] 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/237] 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 27bc8ba68a9e2fc025ec44b5c5a1a545a9f7a041 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Tue, 5 Sep 2023 13:23:52 +0800 Subject: [PATCH 049/237] 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 | 2 +- .../Certify.Service.Tests.Integration.csproj | 2 +- .../Certify.UI.Tests.Integration.csproj | 2 +- 10 files changed, 11 insertions(+), 11 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 2ffb0f7a0..7a91fef88 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 adf2d424e..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,7 +133,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 365a305a6..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,7 +77,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 c92e4ae0d..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,7 +73,7 @@ - + From 866047832d0f5c7db04048a9d2b46be332b34323 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 7 Sep 2023 17:44:25 +0800 Subject: [PATCH 050/237] 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 1b140211d266f149ef39c9545833e7139886c701 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Tue, 12 Sep 2023 20:52:46 +0800 Subject: [PATCH 051/237] 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 e54a2a06947933d31ef504dc0a467b4dad7d9c5f Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Tue, 12 Sep 2023 20:54:02 +0800 Subject: [PATCH 052/237] 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 322e539933e9054949074abaeb102558807fb350 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Tue, 12 Sep 2023 20:54:27 +0800 Subject: [PATCH 053/237] 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 5058d098bfe8321e50992212dea06e37e9ad2236 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Fri, 22 Sep 2023 16:30:04 +0800 Subject: [PATCH 054/237] 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 cb4a539d7c48a5bef4e528be344bbfa1cacfb963 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Fri, 22 Sep 2023 16:30:21 +0800 Subject: [PATCH 055/237] 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 1b370e33ab0ce71193f4432a316f81803b714803 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Fri, 22 Sep 2023 16:30:33 +0800 Subject: [PATCH 056/237] 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 fe25cab72492e9acd87b76d93285a421f5d18844 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Wed, 4 Oct 2023 17:10:46 +0800 Subject: [PATCH 057/237] 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 a7064f1312c8b7229c99b6d3c24a595926083cb1 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Fri, 13 Oct 2023 18:02:53 +0800 Subject: [PATCH 058/237] 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 7a91fef88..0a5961ba3 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 4bfda096eb4cceab41d1eb1180d23175eba7a811 Mon Sep 17 00:00:00 2001 From: Justin Nelson Date: Fri, 13 Oct 2023 18:10:19 -0700 Subject: [PATCH 059/237] 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 cd5ddfc74ecaa5b03ee77f6e5fcd092e40d37ffd Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Mon, 16 Oct 2023 10:00:08 +0800 Subject: [PATCH 060/237] 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 3825b977091ef4f6b52d3b198c60a8821578b6d2 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Wed, 18 Oct 2023 14:57:30 +0800 Subject: [PATCH 061/237] 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 99414798e4cd5c895ecb8ba026236428bbedb001 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Wed, 18 Oct 2023 15:02:13 +0800 Subject: [PATCH 062/237] 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 bd5f3614e..8fea57666 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 5a569c7e1a644ab1adcc14e6da7f39adee208184 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Wed, 18 Oct 2023 15:15:06 +0800 Subject: [PATCH 063/237] 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 f981279b38b02875ce00799495e9d18bf92d9491 Mon Sep 17 00:00:00 2001 From: Justin Nelson Date: Thu, 19 Oct 2023 08:11:37 -0700 Subject: [PATCH 064/237] 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 36c90af6425128630a1f42f0caf66d5d9ec7e362 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Fri, 20 Oct 2023 17:13:42 +0800 Subject: [PATCH 065/237] 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 e7d8214507b9dccfa176a04f5e9fde8692707036 Mon Sep 17 00:00:00 2001 From: Justin Nelson Date: Thu, 19 Oct 2023 22:26:25 -0700 Subject: [PATCH 066/237] 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 a520490aa88ddfbf03c17cc712673b2fbc04fb95 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Wed, 25 Oct 2023 13:36:16 +0800 Subject: [PATCH 067/237] 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 c9c634656c811b99b767d23193d6f977ed0ea379 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Wed, 25 Oct 2023 17:27:44 +0800 Subject: [PATCH 068/237] 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 0a5961ba3..7aa438dc1 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 0238bca74aea63356f3eee1be401dcb2d9c8e954 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 26 Oct 2023 16:54:04 +0800 Subject: [PATCH 069/237] 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 8fea57666..bd5f3614e 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 55a89e7f2d82ff00b1a229c347f6d18eaf017372 Mon Sep 17 00:00:00 2001 From: Justin Nelson Date: Thu, 26 Oct 2023 00:02:38 -0700 Subject: [PATCH 070/237] Updates and expansions to various tests --- .../ServerManagers/IISManagerTests.cs | 7 + .../Assets/badcert.pfx | 0 .../Assets/dummycert.pfx | Bin 0 -> 3044 bytes .../BindingMatchTests.cs | 317 +++++++++--------- .../Certify.Core.Tests.Unit.csproj | 6 + .../ChallengeConfigMatchTests.cs | 11 +- .../DomainZoneMatchTests.cs | 7 +- .../GetDnsProviderTests.cs | 140 ++++++++ 8 files changed, 328 insertions(+), 160 deletions(-) create mode 100644 src/Certify.Tests/Certify.Core.Tests.Unit/Assets/badcert.pfx create mode 100644 src/Certify.Tests/Certify.Core.Tests.Unit/Assets/dummycert.pfx 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/Assets/badcert.pfx b/src/Certify.Tests/Certify.Core.Tests.Unit/Assets/badcert.pfx new file mode 100644 index 000000000..e69de29bb diff --git a/src/Certify.Tests/Certify.Core.Tests.Unit/Assets/dummycert.pfx b/src/Certify.Tests/Certify.Core.Tests.Unit/Assets/dummycert.pfx new file mode 100644 index 0000000000000000000000000000000000000000..2148c5a292fcad0a6f852ed7c0843a561d5caab8 GIT binary patch literal 3044 zcmZve2{hF07sr3Y7(^(VG6-XhFeH0p7qU&3lL! zzGca4AHDv1|J(aN=l`7Z+;i^t-sd^z`P}E8a}hygAaXu{ph!T_=#2o|< zAzwuV{T~^`i3kC)oo{9c&A(32UI3Bh1}7kbV-ft23;!0+FNJ}bvfpvd3cIp8kfMye z`iZ01KHpbCWMp6fY_Vv7VTO8hO4B#)xU*)ue8nv2XJ~=%`JvOvBQFHMokNzss%Iji ze+@bvnr=^)HN{{&YSH^@+TYEGCQvS(IE9Z2wNWFl0BpZ-1xk9c2fC*f(IcH2@!Q5A(O{~*;l7WLcp&g1ftFlAeT{7^qPifOC2!0;Z;k*dX^(lOW_a1y6W}vytSkh>Mp`VKdgn395K&SxJddCN*9Ks+Az?Vykq&Ger*p zcph2PR+er(n!v@#*k?657Z9**ecdLqIiuvD-c_1zj3wH}$Ckvt{KP1dGPK&i1$}T& zud0sw#bk06Eo0{dIN1GoUXxwT8%;lPp?Gl_Uo)lpK^p6wCMXwc%HA>Nb}boPD>NOL zZN~a?kvuCdM|R79MAlRagAv)P^BmfHSDshg$ShpN%t4oT70aDA(^fKwPjr86Vkg90 zXF)+YMW&RLYG^LD!4yma1Op9ZcPpvZdhU?h6Oj3 zN#h+eORZYfc^j2kPTM?#KEHc?n%<;1aCL!D&AcB7^G-Z993oExFQq{(FGwQ8n^Gko zzWC&W=6j^Ai1RZ6%a{Kte;_Mb@r~07ZGgPv{#5ek^&M^(N$6X* zD6a8R8O;(AOV(OWLo5tTA8QTjp|as_#K<6;IXhQRivW|sAu1_?Z3IyyzZzHSD94KiM~<=qNXJ#R3>2XSuYb^cXvWw%tpH} z1Ikl3R5N^ZV0I2wUPP@dxGM&nWbq&<-p&anuWj6!{Oi6(xv(L z=84?pJ4nnTN+0%%6{8EX=yDZ4|5(f&$Q&>a(ufBG1Oa^%NY?iTKG|037cYFG+iR+_9DrD)R=f7)9e?qqpztz|bLRpc$*%CwD^->4|k2$t~rt`#UO#fB~{3Z$SQYsO{X(Gh* z@JhUr_IdL^%s0eHMEPJkgehd${6Hl;p~!XrXRE}%FpUS^xRayLvyl=KbrkEgQnLpS ze>8dBWY~6mwA*|Ew~4tYr9cb=#G8GF|73WH~U<`JcG?Roi^*e_2idH|oP zn+KNfSDv;`*M+XJ?Kh9AjP9UVtU3u#g$KXKm1$^KNTmjh&!vbZm1NwG$YmJ0iq6hh zIIC$&c~G?&2|4q4oL6);L`)ODy-C6^TTCHyjmQjX;r%-m4`4(2+`(hRcel9QN|)iR zSHJ6w@BPdZ0*-Eids4)y;H!s2?4?p1Jq=WA-u-b$vK{(&E*Czox8C3d@kDD5jAYZ@ z)QHJ>%(86jw|?Upb?A%6!01xyuCPU&7JZ5Dlj*jN4@i5T2@#I7TCg8A8Z3>;OFUnX z?@@YM9ZrauRfVPIEsrJUed}uW&19xmUV7T`evJ|Rr3Z73-8}g7O#T-3sR?B#HUO`S z7$p5l$gG3Q(k$5=;Qix0v2@(l1pl2DDeZR<8&|ILk5!r(Q^mtigNmFayUE=kIPQ#- zvW!qy#ZG9l40(SemRrDBs2DjM^}pFk+E`C{xz5Wv1d(;UT?$4-F@g&>THqx`E)fd2 zswV0&W0iQ#*e?;o7W%oyj_*72C6_+gi+{JRO~V$K;Xssq4kdjnp&j>}slNW&P*L@d zdgfv|Ib+OkWky9DcUJwHFXs%Z5|hYZT+#4^IgFaNM;;6du=-z(=7nncwaFakiYBU8 z;lLH`V1ws@T{d7H=R)Re#_RNak@w@eL;CNJM2*ApI_pJ)*PX&RbLR`TN1UZ2+&>qn zH=&Fcbi1aETBhjL1J9hWLYj^;i?&f^qc$J0tQ)9{XO0R;O|j-2@oZVQ30*YXS&x1G z-omkS6e60Hc2pv^QO{b#3X?^9h_8x8ZTMa4;8^_p!Va3s#YJsTW?nhm#t~@*nO^+` z)yzCC%8`D_M)o%F!y6>q@g%$A?J$vqx;fv=;p+xcd5Z|CdYW`Q!zN?y-c*@8LxLL? z7X^oQYbviP$+JYga%-(4JcAuS3I&IdQC)J;6dBQ#XZ#?r_o`2&!BNb1`ux(l`Ta_*X2;1(BQdDdphkWdv(`6=7GGTP|rfG19v*0cf9@mjgd zZqtEs4_up<`=@&#Z4<{vHhPd6bzO&#yAKm9+T#F83!NpCKhuBq*}YQgY2>|9Tj2JE z6eT-zHrXxxRnHAhTV>ofcIu?oyxhd9JJWCNcB{TXus>&o#?Ep%#F!MYmh(Z1*79x6-bscjv0zSm*3B1C9L=MNd8AOg`f5=Q&_y=`My{-EcjX|aU0iU zORLGSuV+@7zyM*`Zk33W%{1J*ehE#VW0isQgh*$!;0N zN7$F6i1!eAnj6myISY2%RkIkf=hDq(L@u`%QQyr7?jB6!-I;9Jt?(_}niC;?@}%o| zlOpRK8;=-vI~f|)&23@7o+YY4zP8qXIS5_z&++l^7I6o`iJ*p12-1<0!6*PQ(>s$$ wY8R`DmPa8Nu0Wg2Q#Q^eaxl|lrZzT|5%J;9nE0fcSbHUb+GOST_y_?107&|%YXATM literal 0 HcmV?d00001 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 3346e83ee5cc8797dcbf124fc13071ed484a44a0 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Mon, 30 Oct 2023 14:20:00 +0800 Subject: [PATCH 071/237] 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 f45d6a2e4c6c128a8ffac38ae149c4d3fece4163 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Mon, 30 Oct 2023 15:36:49 +0800 Subject: [PATCH 072/237] 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 5fd625bf7260ad83e90a2956424ac0ce95b0a294 Mon Sep 17 00:00:00 2001 From: Justin Nelson Date: Tue, 31 Oct 2023 13:30:19 -0700 Subject: [PATCH 073/237] 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 4d1c8b5e26ed7d3acedb68ca5aaf00a92c6d971e Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 2 Nov 2023 11:59:57 +0800 Subject: [PATCH 074/237] 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 7f0ef517562a67cb1548412e6332a09f710f33a3 Mon Sep 17 00:00:00 2001 From: Justin Nelson Date: Thu, 2 Nov 2023 13:07:36 -0700 Subject: [PATCH 075/237] 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 820eecf001a2ba7e11645cf2c0098e63ac9929c4 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Fri, 3 Nov 2023 15:47:59 +0800 Subject: [PATCH 076/237] 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 47f9f432f..383805912 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 a9d642894..1074da072 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. - + \ 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 cc7d4edd3..af2c187c8 100644 --- a/src/Certify.Locales/SR.ja-JP.resx +++ b/src/Certify.Locales/SR.ja-JP.resx @@ -123,7 +123,7 @@ あなたのメール アドレス (回答が必要な場合): - + はい、同意します @@ -612,7 +612,7 @@ すべての自動更新アイテムの証明書が更新されます。 続行しますか? - + 提供された電子メールアドレスは、必要に応じて今後の証明書の更新を通知するために使用することができます。 無効な電子メールアドレスはLet's Encrypt によって拒否されます。 @@ -642,4 +642,4 @@ 選択したサイトに保存されていない変更があります。 変更を破棄しますか? - + \ 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 e7bb08bcb..1229266cf 100644 --- a/src/Certify.Locales/SR.nb-NO.resx +++ b/src/Certify.Locales/SR.nb-NO.resx @@ -156,13 +156,13 @@ E-postadresse - + Ja, jeg aksepterer Registrer kontakt - + E-postadressen som er oppgitt, kan brukes til å varsle deg om kommende sertifikatfornyelser hvis nødvendig. Ugyldige e-postadresser blir avvist av Let's Encrypt. diff --git a/src/Certify.Locales/SR.resx b/src/Certify.Locales/SR.resx index 1074da072..755cea639 100644 --- a/src/Certify.Locales/SR.resx +++ b/src/Certify.Locales/SR.resx @@ -159,16 +159,16 @@ Email Address - + Yes, I Agree Register Contact - + To request certificates you need to register with each of the Certificate Authorities that you want to use. - + The email address provided may be used to notify you of upcoming certificate renewals if required. Invalid email addresses will be rejected by the Certificate Authority. @@ -216,7 +216,7 @@ There was a problem registering with the Certificate Authority using this email address. Check the email address is valid and that this computer has an open connection to the internet (outgoing https is required for API calls). - + To proceed, confirm that you agree to the current terms and conditions for this Certificate Authority. @@ -516,7 +516,7 @@ You are using the evaluation version of this app. Please purchase a registration key to upgrade. See the Register option on the About tab. - This will renew certificates for all auto-renewed items. Proceed? + This will renew certificates for all auto-renewed items, if applicable. Proceed? IIS Was not detected on this server, important functionality will be unavailable. If you know IIS is installed and working on this server, please report this error to support@certifytheweb.com providing details of your server Operating System version and IIS versions @@ -704,4 +704,16 @@ 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. + + Edit ACME Account + + + Use Staging Mode (Test Certificates) + + + (Display Name for the Certificate Authority) + + + (Url for the production directory endpoint) + diff --git a/src/Certify.Locales/SR.tr-TR.resx b/src/Certify.Locales/SR.tr-TR.resx index eb73c406e..4108843b0 100644 --- a/src/Certify.Locales/SR.tr-TR.resx +++ b/src/Certify.Locales/SR.tr-TR.resx @@ -159,16 +159,16 @@ E-posta Adresi - + Evet katılıyorum Yetkiliyi Kaydet - + Sertifika talep etmek için kullanmak istediğiniz Sertifika Yetkililerinin her birine kaydolmanız gerekir. - + Sağlanan e-posta adresi, gerekirse yaklaşan sertifika yenilemelerini size bildirmek için kullanılabilir. Geçersiz e-posta adresleri Sertifika Yetkilisi tarafından reddedilecektir. @@ -216,7 +216,7 @@ Bu e-posta adresini kullanarak Sertifika Yetkilisine kaydolurken bir sorun oluştu. E-posta adresinin geçerli olduğunu ve bu bilgisayarın internete açık bir bağlantısı olduğunu kontrol edin (API çağrıları için giden https gereklidir). - + Devam etmek için bu Sertifika Yetkilisi için geçerli hüküm ve koşulları kabul ettiğinizi onaylayın. @@ -689,4 +689,4 @@ Sertifika Yetkilileri, güvenilir sertifikalar verebilen kuruluşlardır. Kullanmak istediğiniz her (ACME) Sertifika Yetkilisi için Ayarlar altında bir hesap açmanız gerekir. - + \ No newline at end of file diff --git a/src/Certify.Locales/SR.zh-Hans.resx b/src/Certify.Locales/SR.zh-Hans.resx index 09657f8e2..2b2af61f3 100644 --- a/src/Certify.Locales/SR.zh-Hans.resx +++ b/src/Certify.Locales/SR.zh-Hans.resx @@ -159,13 +159,13 @@ 邮箱地址 - + 是的,我同意 新建联系方式 - + 这个邮箱地址将会在未来可能需要续期的时候通知你,不合法的电子邮箱地址将会被Let's Encrypt拒绝。 diff --git a/src/Certify.UI.Shared/Controls/ManagedCertificate/AdvancedOptions.xaml b/src/Certify.UI.Shared/Controls/ManagedCertificate/AdvancedOptions.xaml index 492f61e0d..a0b9eebdb 100644 --- a/src/Certify.UI.Shared/Controls/ManagedCertificate/AdvancedOptions.xaml +++ b/src/Certify.UI.Shared/Controls/ManagedCertificate/AdvancedOptions.xaml @@ -40,7 +40,7 @@ Height="32" MinWidth="140" Controls:HeaderedControlHelper.HeaderFontSize="12" - Header="Certificate Authority" + Header="{x:Static Resources:SR.ManagedCertificateSettings_CertificateAuthority_Title}" IsSelected="true"> @@ -70,7 +70,7 @@ diff --git a/src/Certify.UI.Shared/Windows/EditAccountDialog.xaml b/src/Certify.UI.Shared/Windows/EditAccountDialog.xaml index 2ab1d51b4..500cd180a 100644 --- a/src/Certify.UI.Shared/Windows/EditAccountDialog.xaml +++ b/src/Certify.UI.Shared/Windows/EditAccountDialog.xaml @@ -7,7 +7,7 @@ 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" + Title="{x:Static res:SR.Account_Edit_SectionTitle}" Width="514" Height="416" ResizeMode="CanResizeWithGrip" @@ -43,7 +43,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 63b6731f5..a597f783b 100644 --- a/src/Certify.UI.Shared/Windows/EditAccountDialog.xaml.cs +++ b/src/Certify.UI.Shared/Windows/EditAccountDialog.xaml.cs @@ -141,7 +141,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 77bf8ceab36a0db310b0cdff34745091fd5386f1 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Tue, 7 Nov 2023 18:00:01 +0800 Subject: [PATCH 080/237] 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 accb35d881c14e9e434cd6507ff2d052aa58959b Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 9 Nov 2023 09:08:33 +0800 Subject: [PATCH 081/237] 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 8cdd3afa8937e223bad68ce59bba1693a871468a Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Thu, 9 Nov 2023 12:16:34 +0800 Subject: [PATCH 082/237] 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 7aa438dc1..a858c5861 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 c3a72e81be87963807f47c1397e2e8522c1c158b Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 9 Nov 2023 16:45:27 +0800 Subject: [PATCH 083/237] 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 5a9fa88e799c88c1e8dec31e3164fc7637c1781b Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 9 Nov 2023 16:45:48 +0800 Subject: [PATCH 084/237] 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 0dec563a55ce9a87a926697c1813de61c1c48b8b Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Fri, 10 Nov 2023 15:36:20 +0800 Subject: [PATCH 085/237] 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 0f22e70ca414211a2a8508693b524ba3a541f654 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Tue, 14 Nov 2023 15:40:45 +0800 Subject: [PATCH 086/237] 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 9076e2510..a54c4900b 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 755cea639..e20402ad3 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. + 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/237] 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 a858c5861..0edf6955c 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 c3c4eec14723dd8f7a5ba696288f92f56fa64292 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Thu, 16 Nov 2023 15:09:35 +0800 Subject: [PATCH 088/237] 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 8df5464930377d73927c00eca7e3fdb5ea24dc88 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 16 Nov 2023 17:36:08 +0800 Subject: [PATCH 089/237] 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 68aa9a058f4a32c2fb74aaeadba7f164fe0d8024 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 16 Nov 2023 17:39:41 +0800 Subject: [PATCH 090/237] 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 497d5fb431b8ed26a15f94781f7f52fbc9f17326 Mon Sep 17 00:00:00 2001 From: Justin Nelson Date: Thu, 30 Nov 2023 14:56:23 -0800 Subject: [PATCH 091/237] 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 e734e6f2462463cac7793163af235ac17b636855 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Mon, 11 Dec 2023 13:35:52 +0800 Subject: [PATCH 092/237] Package updates --- src/Certify.Models/Certify.Models.csproj | 2 +- .../DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj | 2 +- .../Certify.Server.Api.Public/Certify.Server.Api.Public.csproj | 2 +- src/Certify.Service/App.config | 2 +- src/Certify.Service/Certify.Service.csproj | 2 +- src/Certify.Shared.Extensions/Certify.Shared.Extensions.csproj | 2 +- src/Certify.Shared/Certify.Shared.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 +- .../Certify.UI.Tests.Integration.csproj | 2 +- 11 files changed, 11 insertions(+), 11 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 0edf6955c..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,7 +92,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 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 633d8191111231c6d9237183b8fb3bbb025f0347 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Tue, 12 Dec 2023 15:54:03 +0800 Subject: [PATCH 093/237] 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 463c2ce0e24d16446e2da6f543be09d070aaf170 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Tue, 12 Dec 2023 17:17:55 +0800 Subject: [PATCH 094/237] 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 d15df1df8..fcbc5f1aa 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.0.exe' From 99ae4af47a8c3ede58b55d125e06eb393979270f Mon Sep 17 00:00:00 2001 From: Justin Nelson Date: Wed, 13 Dec 2023 20:09:16 -0800 Subject: [PATCH 095/237] 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 3af000cd7..3022f1216 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Integration/DeploymentTaskTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Integration/DeploymentTaskTests.cs @@ -36,6 +36,12 @@ public DeploymentTaskTests() PrimaryTestDomain = ConfigSettings["Cloudflare_TestDomain"]; } + [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 38481cfca0a99acae5c13e7d4fa56b9d59169ebd Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Fri, 15 Dec 2023 17:06:27 +0800 Subject: [PATCH 096/237] 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 12d17be553fd4a7811676da1f38a5af98e187f9c Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Fri, 15 Dec 2023 17:06:52 +0800 Subject: [PATCH 097/237] 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 47ed9502c45f15a017948af2f13736ef0b0c40f9 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Mon, 18 Dec 2023 11:45:09 +0800 Subject: [PATCH 098/237] 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 190c18d2fc52752a29e0006bc788747f7a34ca5f Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Mon, 18 Dec 2023 11:45:43 +0800 Subject: [PATCH 099/237] 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 5ed1e2d4f367fa269fe127f4ae16684909839b6a Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Mon, 18 Dec 2023 16:29:33 +0800 Subject: [PATCH 100/237] 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 7fb65a42d3228aedf476326711d4e8d55ae0bb1e Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Mon, 18 Dec 2023 16:51:18 +0800 Subject: [PATCH 101/237] 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 e7aaaccfa4bb131051e3482caf896c2df3c6919b Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Tue, 19 Dec 2023 15:37:00 +0800 Subject: [PATCH 102/237] 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 78a72ff23bd13db85e707d1c767b86246e283416 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Tue, 19 Dec 2023 15:44:35 +0800 Subject: [PATCH 103/237] 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 2579caf8ab37dd19ac0cc4cf4a95f8bf7e9db035 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Wed, 20 Dec 2023 17:05:28 +0800 Subject: [PATCH 104/237] 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 e4eb13c9e46ee7d32f442adc5705c76f70c1899c Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 21 Dec 2023 17:11:32 +0800 Subject: [PATCH 105/237] 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 475e761edcd9f39b595ecd6e726a11d4dd819b86 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 21 Dec 2023 17:12:12 +0800 Subject: [PATCH 106/237] 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 10690061e953b56b8bf20123a0ef75c66835123c Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 21 Dec 2023 17:22:42 +0800 Subject: [PATCH 107/237] 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 070a63b93902706123bc99135c0511bdaef32bba Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 21 Dec 2023 17:48:18 +0800 Subject: [PATCH 108/237] 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 1befa4792b409d7b324321ef3ff93d7bea758b9f Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Mon, 8 Jan 2024 16:21:18 +0800 Subject: [PATCH 109/237] 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 3022f1216..c6f2bdd50 100644 --- a/src/Certify.Tests/Certify.Core.Tests.Integration/DeploymentTaskTests.cs +++ b/src/Certify.Tests/Certify.Core.Tests.Integration/DeploymentTaskTests.cs @@ -36,7 +36,7 @@ public DeploymentTaskTests() PrimaryTestDomain = ConfigSettings["Cloudflare_TestDomain"]; } - [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 3085f8862514c37f1221086c86d1ef9355b6a2bc Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Mon, 8 Jan 2024 17:05:43 +0800 Subject: [PATCH 110/237] 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 96ae461f2b5ae74ea56266c3de708f3c81a0c367 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Mon, 8 Jan 2024 17:07:21 +0800 Subject: [PATCH 111/237] 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 376b27c1986b86e11fa51e8a26983f44a6ac72f4 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Mon, 8 Jan 2024 17:11:10 +0800 Subject: [PATCH 112/237] 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 b89e35d25fb1abb10b510be3e4dfc39c311afa3b Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Mon, 8 Jan 2024 17:27:07 +0800 Subject: [PATCH 113/237] 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 6d968ad507ea0479f58c6e146a26b0bc65c516ba Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Mon, 15 Jan 2024 13:55:05 +0800 Subject: [PATCH 114/237] 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 3817aaf7d7cac025a70b50470e4ad5ab3708896f Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Mon, 15 Jan 2024 13:59:29 +0800 Subject: [PATCH 115/237] 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 68b5ad2dc8592825c8efd33d443974244dcab25a Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 1 Feb 2024 17:29:58 +0800 Subject: [PATCH 117/237] 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 8a42c0dcda8e9e4f172cf2cbe84edead7ce8dee0 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 1 Feb 2024 17:32:30 +0800 Subject: [PATCH 118/237] 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 157f5386191224750654c4328852a2123aca9187 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 1 Feb 2024 17:33:06 +0800 Subject: [PATCH 119/237] 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 355500a8e128aed6a81a8f4b4d661c632eeff79d Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Fri, 2 Feb 2024 18:02:10 +0800 Subject: [PATCH 120/237] 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 ca50be0f55e60b43e3c8a3692f46b6c0dd9d765b Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Mon, 5 Feb 2024 16:37:05 +0800 Subject: [PATCH 121/237] 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 5c306aebb89d2442207073e9e83bc08ada35f19d Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Tue, 6 Feb 2024 17:56:56 +0800 Subject: [PATCH 122/237] 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 c14ec8f16887f69293fe1f9d8586c13ee33bccbb Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 8 Feb 2024 10:59:17 +0800 Subject: [PATCH 123/237] 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 18abf6fcfbe774998f432ce7e0983b44d9af7275 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 8 Feb 2024 12:38:31 +0800 Subject: [PATCH 124/237] 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 a7a0d211e569c0e3f60f7bdbf219dd64bf462fd7 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 8 Feb 2024 12:41:37 +0800 Subject: [PATCH 125/237] 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 9530e26e3f99e8ae30ef0e45b32773f4e2d12391 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 8 Feb 2024 12:43:29 +0800 Subject: [PATCH 126/237] 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 eebdfaf670067c18c5d9e5a17b5a9973be752311 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 8 Feb 2024 12:44:59 +0800 Subject: [PATCH 127/237] 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 589fd226020639d4f1c128b9d1bc6a08cc9a18fd Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 8 Feb 2024 13:35:53 +0800 Subject: [PATCH 128/237] 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 b2a4cb1ea9bade339dd9d38b6f891121bb080319 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 8 Feb 2024 13:36:28 +0800 Subject: [PATCH 129/237] 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 2aff665660bd140fd94b2a74c98606e3928aa803 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 8 Feb 2024 13:36:54 +0800 Subject: [PATCH 130/237] 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 524f2f159617e671aeae6c84fed5aa3cb3c15150 Mon Sep 17 00:00:00 2001 From: Fritz Otlinghaus Date: Wed, 31 Jan 2024 10:29:26 +0100 Subject: [PATCH 131/237] 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 d343b2ff2..8c3b00765 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 106eb9eadf6698f430d67353d0a00562b3f3b39b Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 8 Feb 2024 17:39:35 +0800 Subject: [PATCH 132/237] 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 b112b74e2651d64ef44d77423673f0f15ab40bda Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Fri, 9 Feb 2024 18:15:51 +0800 Subject: [PATCH 133/237] 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 2514ae254fe37701648773f3788354dfc390316d Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 15 Feb 2024 14:12:26 +0800 Subject: [PATCH 134/237] 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 f30db54f0b2f50f2c5bfb677a564836b7d620bf3 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Thu, 15 Feb 2024 14:14:04 +0800 Subject: [PATCH 135/237] 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 a5245da3f9d21070fee706d3fc535ae054646229 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Tue, 6 Aug 2024 12:35:47 +0800 Subject: [PATCH 222/237] 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 dadd5747a3fb9eb102403fb2e18c51bb9b57cd51 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Fri, 9 Aug 2024 16:12:54 +0800 Subject: [PATCH 223/237] 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 0287256a2df9220e7ca8fc60ae9c915b052c1221 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Fri, 9 Aug 2024 17:05:28 +0800 Subject: [PATCH 224/237] 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 0b3acfc42d925f9adb5d0b679e38237e058ce194 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Mon, 12 Aug 2024 12:40:20 +0800 Subject: [PATCH 225/237] 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 97d751e0d939454b3e8f21816cc999d67927d480 Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Mon, 12 Aug 2024 15:28:34 +0800 Subject: [PATCH 226/237] Package updates --- src/Certify.Service/App.config | 4 ++-- src/Certify.Service/Certify.Service.csproj | 4 ++-- .../Certify.UI.Tests.Integration.csproj | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Certify.Service/App.config b/src/Certify.Service/App.config index 69a44a551..61ccedc5d 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..dc85a3e3e 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.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..b25b95ee9 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 da58b0f4f9ea54181faf1479dc19a9b09820e32e Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Mon, 12 Aug 2024 14:10:32 +0800 Subject: [PATCH 227/237] 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 65ae8d466267eb920cd500580ca36894d79c8e3c Mon Sep 17 00:00:00 2001 From: webprofusion-chrisc Date: Wed, 14 Aug 2024 17:20:59 +0800 Subject: [PATCH 228/237] Package updates --- .../Certify.Aspire.ServiceDefaults.csproj | 2 +- src/Certify.Client/Certify.Client.csproj | 4 ++-- .../CertifyManager.ManagedCertificates.cs | 2 +- .../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 | 4 ++-- .../Certify.Server.Core/Certify.Server.Core.csproj | 10 +++++----- .../Certify.Core.Tests.Integration.csproj | 4 ++-- .../Certify.Core.Tests.Unit.csproj | 4 ++-- .../Certify.Service.Tests.Integration.csproj | 4 ++-- .../Certify.UI.Tests.Integration.csproj | 4 ++-- 12 files changed, 25 insertions(+), 25 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 32cfa7b8e..a7ad86324 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 60c695454..34aec9361 100644 --- a/src/Certify.Client/Certify.Client.csproj +++ b/src/Certify.Client/Certify.Client.csproj @@ -16,8 +16,8 @@ - - + + 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.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj index b94ee00d3..d6b4f3f8d 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..fb115bf15 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..baf2fdd13 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..675278e40 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.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..8060d469b 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 e00341169..bb751ef66 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,8 +108,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 aef71dfbb..a5311fdb9 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 b25b95ee9..85790061e 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 2746e2f6f729daf0ec83c6132e7c1af0aa17ffb8 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Sun, 18 Aug 2024 17:50:07 +0800 Subject: [PATCH 229/237] Package updates --- src/Certify.Models/Certify.Models.csproj | 2 +- .../DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj | 2 +- src/Certify.SourceGenerators/Certify.SourceGenerators.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 290f326d6..05e1f34cc 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 d6b4f3f8d..c6801c731 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.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 - + From e96f126eec37476d5bf811a15b20fafeb1dc70b9 Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Mon, 19 Aug 2024 12:43:30 +0800 Subject: [PATCH 230/237] Package updates --- .../DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj | 2 +- .../Certify.Server.Api.Public/Certify.Server.Api.Public.csproj | 2 +- .../Certify.Server.Core/Certify.Server.Core.csproj | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj b/src/Certify.Providers/DNS/AWSRoute53/Plugin.DNS.AWSRoute53.csproj index c6801c731..a06ddd1df 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 baf2fdd13..3abe8bc83 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 @@ -20,7 +20,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 675278e40..8f40f3bbc 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 @@ - + From 83aadc8c705e912709be651fbf87b2734224752c Mon Sep 17 00:00:00 2001 From: Mark Cilia Vincenti Date: Sun, 1 Sep 2024 10:48:38 +0200 Subject: [PATCH 231/237] Improved locking performance on .NET 9.0+ --- src/Certify.Core/Certify.Core.csproj | 2 +- .../Management/CertifyManager/CertifyManager.Account.cs | 5 +++-- .../Management/CertifyManager/CertifyManager.DataStores.cs | 3 ++- src/Certify.Core/Management/Servers/ServerProviderIIS.cs | 3 ++- src/Certify.Core/Management/SettingsManager.cs | 5 +++-- src/Certify.Shared/Certify.Shared.Core.csproj | 5 +++++ src/Certify.Shared/Utils/HttpChallengeServer.cs | 4 ++-- 7 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/Certify.Core/Certify.Core.csproj b/src/Certify.Core/Certify.Core.csproj index 36d40e890..9c212a87c 100644 --- a/src/Certify.Core/Certify.Core.csproj +++ b/src/Certify.Core/Certify.Core.csproj @@ -3,7 +3,7 @@ net462;net9.0 Debug;Release; AnyCPU - latest + preview diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.Account.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.Account.cs index a0baf0e06..272d4f3ce 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.Account.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.Account.cs @@ -1,7 +1,8 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Certify.Models; using Certify.Models.Config; @@ -14,7 +15,7 @@ namespace Certify.Management { public partial class CertifyManager { - private static object _accountsLock = new object(); + private static readonly Lock _accountsLock = new(); 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..222636c72 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.DataStores.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.DataStores.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Certify.Models; using Certify.Models.Config; @@ -11,7 +12,7 @@ namespace Certify.Management { public partial class CertifyManager { - private object _dataStoreLocker = new object(); + private readonly Lock _dataStoreLocker = new(); 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 b7c45a993..2e17c6014 100644 --- a/src/Certify.Core/Management/Servers/ServerProviderIIS.cs +++ b/src/Certify.Core/Management/Servers/ServerProviderIIS.cs @@ -3,6 +3,7 @@ using System.Globalization; using System.Linq; using System.Runtime.InteropServices; +using System.Threading; using System.Threading.Tasks; using Certify.Models; using Certify.Models.Providers; @@ -23,7 +24,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 = new(); private ILog _log; diff --git a/src/Certify.Core/Management/SettingsManager.cs b/src/Certify.Core/Management/SettingsManager.cs index 3425fadb2..d73cfb4f7 100644 --- a/src/Certify.Core/Management/SettingsManager.cs +++ b/src/Certify.Core/Management/SettingsManager.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Threading; using Certify.Models; namespace Certify.Management @@ -8,7 +9,7 @@ namespace Certify.Management public sealed class CoreAppSettings { private static volatile CoreAppSettings instance; - private static object syncRoot = new object(); + private static readonly Lock syncRoot = new(); private CoreAppSettings() { @@ -188,7 +189,7 @@ public static CoreAppSettings Current public class SettingsManager { private const string COREAPPSETTINGSFILE = "appsettings.json"; - private static Object settingsLocker = new Object(); + private static readonly Lock settingsLocker = new(); 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 ad5121fa6..63b129189 100644 --- a/src/Certify.Shared/Certify.Shared.Core.csproj +++ b/src/Certify.Shared/Certify.Shared.Core.csproj @@ -3,6 +3,7 @@ netstandard2.0;net9.0; AnyCPU + preview @@ -38,4 +39,8 @@ + + + + diff --git a/src/Certify.Shared/Utils/HttpChallengeServer.cs b/src/Certify.Shared/Utils/HttpChallengeServer.cs index cecdf3c95..976c54ed4 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 = new(); + private readonly Lock _challengeServerStopLock = new(); /// /// If true, challenge server has been started or a start has been attempted From 56706597fa14d22c4445ae80a8e4eb1f3fef2cfe Mon Sep 17 00:00:00 2001 From: Christopher Cook Date: Wed, 4 Sep 2024 12:04:35 +0800 Subject: [PATCH 232/237] 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 f74aa730ceed290a1ccae4901b4d1399f1c35637 Mon Sep 17 00:00:00 2001 From: Mark Cilia Vincenti Date: Sat, 7 Sep 2024 14:11:37 +0200 Subject: [PATCH 233/237] Hardening against thread aborts. --- .../CertifyManager/CertifyManager.Account.cs | 2 +- .../CertifyManager/CertifyManager.DataStores.cs | 3 +-- .../Management/Servers/ServerProviderIIS.cs | 3 +-- src/Certify.Core/Management/SettingsManager.cs | 5 ++--- src/Certify.Locales/Certify.Locales.csproj | 3 +++ src/Certify.Models/Certify.Models.csproj | 1 - src/Certify.Shared/Certify.Shared.Core.csproj | 8 ++------ src/Certify.Shared/Utils/HttpChallengeServer.cs | 4 ++-- src/Certify.UI.sln | 1 + src/Directory.Build.props | 11 +++++++++++ 10 files changed, 24 insertions(+), 17 deletions(-) create mode 100644 src/Directory.Build.props diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.Account.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.Account.cs index 272d4f3ce..306cbe576 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.Account.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.Account.cs @@ -15,7 +15,7 @@ namespace Certify.Management { public partial class CertifyManager { - private static readonly Lock _accountsLock = new(); + private static readonly 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 222636c72..0358b0d04 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.DataStores.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.DataStores.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Threading; using System.Threading.Tasks; using Certify.Models; using Certify.Models.Config; @@ -12,7 +11,7 @@ namespace Certify.Management { public partial class CertifyManager { - private readonly Lock _dataStoreLocker = new(); + private readonly 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 2e17c6014..204bbadef 100644 --- a/src/Certify.Core/Management/Servers/ServerProviderIIS.cs +++ b/src/Certify.Core/Management/Servers/ServerProviderIIS.cs @@ -3,7 +3,6 @@ using System.Globalization; using System.Linq; using System.Runtime.InteropServices; -using System.Threading; using System.Threading.Tasks; using Certify.Models; using Certify.Models.Providers; @@ -24,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 Lock _iisAPILock = new(); + 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 d73cfb4f7..7f3dfab99 100644 --- a/src/Certify.Core/Management/SettingsManager.cs +++ b/src/Certify.Core/Management/SettingsManager.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Threading; using Certify.Models; namespace Certify.Management @@ -9,7 +8,7 @@ namespace Certify.Management public sealed class CoreAppSettings { private static volatile CoreAppSettings instance; - private static readonly Lock syncRoot = new(); + private static readonly Lock syncRoot = LockFactory.Create(); private CoreAppSettings() { @@ -189,7 +188,7 @@ public static CoreAppSettings Current public class SettingsManager { private const string COREAPPSETTINGSFILE = "appsettings.json"; - private static readonly Lock settingsLocker = new(); + private static readonly Lock settingsLocker = LockFactory.Create(); public static bool FromPreferences(Models.Preferences prefs) { diff --git a/src/Certify.Locales/Certify.Locales.csproj b/src/Certify.Locales/Certify.Locales.csproj index 9acc6809c..eee60e946 100644 --- a/src/Certify.Locales/Certify.Locales.csproj +++ b/src/Certify.Locales/Certify.Locales.csproj @@ -11,6 +11,9 @@ AnyCPU + + + True diff --git a/src/Certify.Models/Certify.Models.csproj b/src/Certify.Models/Certify.Models.csproj index da5b812ee..e01636a2c 100644 --- a/src/Certify.Models/Certify.Models.csproj +++ b/src/Certify.Models/Certify.Models.csproj @@ -3,7 +3,6 @@ netstandard2.0;net462;net9.0 Certify AnyCPU - 10.0 enable False Certify Certificate Manager API Models diff --git a/src/Certify.Shared/Certify.Shared.Core.csproj b/src/Certify.Shared/Certify.Shared.Core.csproj index 63b129189..988b65e77 100644 --- a/src/Certify.Shared/Certify.Shared.Core.csproj +++ b/src/Certify.Shared/Certify.Shared.Core.csproj @@ -1,11 +1,11 @@  - netstandard2.0;net9.0; + netstandard2.0;net9.0 AnyCPU - preview + @@ -39,8 +39,4 @@ - - - - diff --git a/src/Certify.Shared/Utils/HttpChallengeServer.cs b/src/Certify.Shared/Utils/HttpChallengeServer.cs index 976c54ed4..8f3bb0a76 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 Lock _challengeServerStartLock = new(); - private readonly Lock _challengeServerStopLock = new(); + 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 diff --git a/src/Certify.UI.sln b/src/Certify.UI.sln index e202963f1..e312b4c08 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}" diff --git a/src/Directory.Build.props b/src/Directory.Build.props new file mode 100644 index 000000000..78d0ac19d --- /dev/null +++ b/src/Directory.Build.props @@ -0,0 +1,11 @@ + + + preview + true + + + + + + + From dcd8cc412a85a697098a80cda34b6bb0f7f847f5 Mon Sep 17 00:00:00 2001 From: Mark Cilia Vincenti Date: Sat, 7 Sep 2024 14:13:27 +0200 Subject: [PATCH 234/237] Removed unnecessary LangVersion --- src/Certify.Core/Certify.Core.csproj | 1 - src/Certify.Shared.Extensions/Certify.Shared.Extensions.csproj | 1 - 2 files changed, 2 deletions(-) diff --git a/src/Certify.Core/Certify.Core.csproj b/src/Certify.Core/Certify.Core.csproj index 9c212a87c..21b639062 100644 --- a/src/Certify.Core/Certify.Core.csproj +++ b/src/Certify.Core/Certify.Core.csproj @@ -3,7 +3,6 @@ net462;net9.0 Debug;Release; AnyCPU - preview diff --git a/src/Certify.Shared.Extensions/Certify.Shared.Extensions.csproj b/src/Certify.Shared.Extensions/Certify.Shared.Extensions.csproj index f55b256c6..25396eb9f 100644 --- a/src/Certify.Shared.Extensions/Certify.Shared.Extensions.csproj +++ b/src/Certify.Shared.Extensions/Certify.Shared.Extensions.csproj @@ -3,7 +3,6 @@ netstandard2.0;net9.0 AnyCPU - latest From ff3cccc8c2989269e8c9b0ac7b21cd13c6846017 Mon Sep 17 00:00:00 2001 From: Mark Cilia Vincenti Date: Sat, 7 Sep 2024 14:14:11 +0200 Subject: [PATCH 235/237] Removed unnecessary using --- .../Management/CertifyManager/CertifyManager.Account.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Certify.Core/Management/CertifyManager/CertifyManager.Account.cs b/src/Certify.Core/Management/CertifyManager/CertifyManager.Account.cs index 306cbe576..9db9874a8 100644 --- a/src/Certify.Core/Management/CertifyManager/CertifyManager.Account.cs +++ b/src/Certify.Core/Management/CertifyManager/CertifyManager.Account.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Threading; using System.Threading.Tasks; using Certify.Models; using Certify.Models.Config; From b42546b0b7405e3a8842c7f8a97cf3511a554059 Mon Sep 17 00:00:00 2001 From: Mark Cilia Vincenti Date: Sat, 21 Sep 2024 19:38:47 +0200 Subject: [PATCH 236/237] Update Certify.Locales.csproj --- src/Certify.Locales/Certify.Locales.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Certify.Locales/Certify.Locales.csproj b/src/Certify.Locales/Certify.Locales.csproj index eee60e946..23d2cac9b 100644 --- a/src/Certify.Locales/Certify.Locales.csproj +++ b/src/Certify.Locales/Certify.Locales.csproj @@ -12,7 +12,7 @@ AnyCPU - + @@ -81,4 +81,4 @@ SR.resx - \ No newline at end of file + From 13ce84aff16e2342c39a5a2c4e9bcb361623e8d6 Mon Sep 17 00:00:00 2001 From: Mark Cilia Vincenti Date: Sat, 21 Sep 2024 19:39:03 +0200 Subject: [PATCH 237/237] Update Certify.Shared.Core.csproj --- src/Certify.Shared/Certify.Shared.Core.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Certify.Shared/Certify.Shared.Core.csproj b/src/Certify.Shared/Certify.Shared.Core.csproj index 988b65e77..dbfc87e30 100644 --- a/src/Certify.Shared/Certify.Shared.Core.csproj +++ b/src/Certify.Shared/Certify.Shared.Core.csproj @@ -5,7 +5,7 @@ AnyCPU - +