From bd67ad6a6be9db2c6715003037ff886e02848d9b Mon Sep 17 00:00:00 2001 From: Maksym Sadovnychyy Date: Sat, 22 Jun 2024 21:31:17 +0200 Subject: [PATCH] (refactor): cache service review --- .../Services/LetsEncryptService.cs | 5 +- .../BackgroundServices/AutoRenewal.cs | 26 ++--- ...acheController.cs => AccountController.cs} | 53 +++++++--- .../Controllers/AccountsController.cs | 29 +++++ .../Controllers/CertsFlowController.cs | 11 +- .../Services/CacheService.cs | 8 +- .../Services/CertsFlowService.cs | 100 +++++++++++------- .../Cache/Requests/PostAccountRequest.cs | 20 ++++ 8 files changed, 171 insertions(+), 81 deletions(-) rename src/LetsEncryptServer/Controllers/{CacheController.cs => AccountController.cs} (58%) create mode 100644 src/LetsEncryptServer/Controllers/AccountsController.cs create mode 100644 src/Models/LetsEncryptServer/Cache/Requests/PostAccountRequest.cs diff --git a/src/LetsEncrypt/Services/LetsEncryptService.cs b/src/LetsEncrypt/Services/LetsEncryptService.cs index 72f0cd4..22f054d 100644 --- a/src/LetsEncrypt/Services/LetsEncryptService.cs +++ b/src/LetsEncrypt/Services/LetsEncryptService.cs @@ -18,7 +18,7 @@ namespace MaksIT.LetsEncrypt.Services; public interface ILetsEncryptService { Task ConfigureClient(Guid sessionId, string url); - Task Init(Guid sessionId,Guid accountId, string[] contacts, RegistrationCache? registrationCache); + Task Init(Guid sessionId,Guid accountId, string description, string[] contacts, RegistrationCache? registrationCache); (RegistrationCache?, IDomainResult) GetRegistrationCache(Guid sessionId); (string?, IDomainResult) GetTermsOfServiceUri(Guid sessionId); Task<(Dictionary?, IDomainResult)> NewOrder(Guid sessionId, string[] hostnames, string challengeType); @@ -76,7 +76,7 @@ public class LetsEncryptService : ILetsEncryptService { #endregion #region Init - public async Task Init(Guid sessionId, Guid accountId, string[] contacts, RegistrationCache? cache) { + public async Task Init(Guid sessionId, Guid accountId, string description, string[] contacts, RegistrationCache? cache) { if (sessionId == Guid.Empty) { _logger.LogError("Invalid sessionId"); return IDomainResult.Failed(); @@ -129,6 +129,7 @@ public class LetsEncryptService : ILetsEncryptService { state.Cache = new RegistrationCache { AccountId = accountId, + Description = description, Contacts = contacts, Location = account.Result.Location, diff --git a/src/LetsEncryptServer/BackgroundServices/AutoRenewal.cs b/src/LetsEncryptServer/BackgroundServices/AutoRenewal.cs index 9ffef1b..ebfc809 100644 --- a/src/LetsEncryptServer/BackgroundServices/AutoRenewal.cs +++ b/src/LetsEncryptServer/BackgroundServices/AutoRenewal.cs @@ -12,8 +12,8 @@ namespace MaksIT.LetsEncryptServer.BackgroundServices { private readonly IOptions _appSettings; private readonly ILogger _logger; - private readonly ICacheService _cacheService; - private readonly ICertsFlowService _certsFlowService; + private readonly ICacheInternalService _cacheService; + private readonly ICertsInternalService _certsFlowService; public AutoRenewal( IOptions appSettings, @@ -79,19 +79,13 @@ namespace MaksIT.LetsEncryptServer.BackgroundServices { var sessionIdValue = sessionId.Value; - var (_, initResult) = await _certsFlowService.InitAsync(sessionIdValue, accountId, new InitRequest { - Description = description, - Contacts = contacts - }); + var (_, initResult) = await _certsFlowService.InitAsync(sessionIdValue, accountId, description, contacts); if (!initResult.IsSuccess) { LogErrors(initResult.Errors); return initResult; } - var (_, newOrderResult) = await _certsFlowService.NewOrderAsync(sessionIdValue, new NewOrderRequest { - Hostnames = hostnames, - ChallengeType = "http-01" - }); + var (_, newOrderResult) = await _certsFlowService.NewOrderAsync(sessionIdValue, hostnames, "http-01"); if (!newOrderResult.IsSuccess) { LogErrors(newOrderResult.Errors); return newOrderResult; @@ -103,25 +97,19 @@ namespace MaksIT.LetsEncryptServer.BackgroundServices { return challengeResult; } - var getOrderResult = await _certsFlowService.GetOrderAsync(sessionIdValue, new GetOrderRequest { - Hostnames = hostnames - }); + var getOrderResult = await _certsFlowService.GetOrderAsync(sessionIdValue, hostnames); if (!getOrderResult.IsSuccess) { LogErrors(getOrderResult.Errors); return getOrderResult; } - var certs = await _certsFlowService.GetCertificatesAsync(sessionIdValue, new GetCertificatesRequest { - Hostnames = hostnames - }); + var certs = await _certsFlowService.GetCertificatesAsync(sessionIdValue, hostnames); if (!certs.IsSuccess) { LogErrors(certs.Errors); return certs; } - var (_, applyCertsResult) = await _certsFlowService.ApplyCertificatesAsync(sessionIdValue, new GetCertificatesRequest { - Hostnames = hostnames - }); + var (_, applyCertsResult) = await _certsFlowService.ApplyCertificatesAsync(sessionIdValue, hostnames); if (!applyCertsResult.IsSuccess) { LogErrors(applyCertsResult.Errors); return applyCertsResult; diff --git a/src/LetsEncryptServer/Controllers/CacheController.cs b/src/LetsEncryptServer/Controllers/AccountController.cs similarity index 58% rename from src/LetsEncryptServer/Controllers/CacheController.cs rename to src/LetsEncryptServer/Controllers/AccountController.cs index 539bffc..753dc86 100644 --- a/src/LetsEncryptServer/Controllers/CacheController.cs +++ b/src/LetsEncryptServer/Controllers/AccountController.cs @@ -1,5 +1,4 @@ using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Options; using DomainResults.Mvc; @@ -9,55 +8,67 @@ using MaksIT.Models.LetsEncryptServer.Cache.Requests; namespace MaksIT.LetsEncryptServer.Controllers; [ApiController] -[Route("api/cache")] -public class CacheController : ControllerBase { +[Route("api/account")] +public class AccountController : ControllerBase { private readonly ICacheRestService _cacheService; + private readonly ICertsFlowService _certsFlowService; - public CacheController( - ICacheService cacheService + public AccountController( + ICacheService cacheService, + ICertsFlowService certsFlowService ) { _cacheService = cacheService; + _certsFlowService = certsFlowService; } - [HttpGet("accounts")] - public async Task GetAccounts() { - var result = await _cacheService.GetAccountsAsync(); - return result.ToActionResult(); + + [HttpPost] + public async Task PostAccount([FromBody] PostAccountRequest requestData) { + //var result = await _cacheService.PostAccountAsync(requestData); + //return result.ToActionResult(); + + return BadRequest("Not implemented"); } - [HttpPut("account/{accountId:guid}")] + [HttpPut("{accountId:guid}")] public async Task PutAccount(Guid accountId, [FromBody] PutAccountRequest requestData) { var result = await _cacheService.PutAccountAsync(accountId, requestData); return result.ToActionResult(); } - [HttpPatch("account/{accountId:guid}")] + [HttpPatch("{accountId:guid}")] public async Task PatchAccount(Guid accountId, [FromBody] PatchAccountRequest requestData) { var result = await _cacheService.PatchAccountAsync(accountId, requestData); return result.ToActionResult(); } + [HttpDelete("{accountd:guid}")] + public async Task DeleteAccount(Guid accountId) { + var result = await _cacheService.DeleteAccountAsync(accountId); + return result.ToActionResult(); + } + #region Contacts - [HttpGet("account/{accountId:guid}/contacts")] + [HttpGet("{accountId:guid}/contacts")] public async Task GetContacts(Guid accountId) { var result = await _cacheService.GetContactsAsync(accountId); return result.ToActionResult(); } - [HttpPut("account/{accountId:guid}/contacts")] + [HttpPut("{accountId:guid}/contacts")] public async Task PutContacts(Guid accountId, [FromBody] PutContactsRequest requestData) { var result = await _cacheService.PutContactsAsync(accountId, requestData); return result.ToActionResult(); } - [HttpPatch("account/{accountId:guid}/contacts")] + [HttpPatch("{accountId:guid}/contacts")] public async Task PatchContacts(Guid accountId, [FromBody] PatchContactsRequest requestData) { var result = await _cacheService.PatchContactsAsync(accountId, requestData); return result.ToActionResult(); } - [HttpDelete("account/{accountId:guid}/contact/{index:int}")] + [HttpDelete("{accountId:guid}/contact/{index:int}")] public async Task DeleteContact(Guid accountId, int index) { var result = await _cacheService.DeleteContactAsync(accountId, index); return result.ToActionResult(); @@ -66,11 +77,21 @@ public class CacheController : ControllerBase { #region Hostnames - [HttpGet("account/{accountId:guid}/hostnames")] + [HttpGet("{accountId:guid}/hostnames")] public async Task GetHostnames(Guid accountId) { var result = await _cacheService.GetHostnames(accountId); return result.ToActionResult(); } + [HttpPost("{accountId:guid}")] + + [HttpDelete("{accountId:guid}/hostname/{index:int}")] + public async Task DeleteHostname(Guid accountId, int index) { + //var result = await _cacheService.DeleteHostnameAsync(accountId, index); + //return result.ToActionResult(); + + return BadRequest("Not implemented"); + } + #endregion } diff --git a/src/LetsEncryptServer/Controllers/AccountsController.cs b/src/LetsEncryptServer/Controllers/AccountsController.cs new file mode 100644 index 0000000..323990a --- /dev/null +++ b/src/LetsEncryptServer/Controllers/AccountsController.cs @@ -0,0 +1,29 @@ +using Microsoft.AspNetCore.Mvc; + +using DomainResults.Mvc; + +using MaksIT.LetsEncryptServer.Services; +using MaksIT.Models.LetsEncryptServer.Cache.Requests; + +namespace MaksIT.LetsEncryptServer.Controllers; + +[ApiController] +[Route("api/accounts")] +public class AccountsController : ControllerBase { + private readonly ICacheRestService _cacheService; + private readonly ICertsFlowService _certsFlowService; + + public AccountsController( + ICacheService cacheService, + ICertsFlowService certsFlowService + ) { + _cacheService = cacheService; + _certsFlowService = certsFlowService; + } + + [HttpGet] + public async Task GetAccounts() { + var result = await _cacheService.GetAccountsAsync(); + return result.ToActionResult(); + } +} diff --git a/src/LetsEncryptServer/Controllers/CertsFlowController.cs b/src/LetsEncryptServer/Controllers/CertsFlowController.cs index 4b8a76f..6602849 100644 --- a/src/LetsEncryptServer/Controllers/CertsFlowController.cs +++ b/src/LetsEncryptServer/Controllers/CertsFlowController.cs @@ -1,21 +1,24 @@ using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Options; + using DomainResults.Mvc; + using MaksIT.LetsEncryptServer.Services; using MaksIT.Models.LetsEncryptServer.CertsFlow.Requests; namespace MaksIT.LetsEncryptServer.Controllers { + + /// + /// Certificates flow controller, used for granular testing purposes + /// [ApiController] [Route("api/certs")] public class CertsFlowController : ControllerBase { - private readonly Configuration _appSettings; + private readonly ICertsFlowService _certsFlowService; public CertsFlowController( - IOptions appSettings, ICertsFlowService certsFlowService ) { - _appSettings = appSettings.Value; _certsFlowService = certsFlowService; } diff --git a/src/LetsEncryptServer/Services/CacheService.cs b/src/LetsEncryptServer/Services/CacheService.cs index fed2a68..2bbf835 100644 --- a/src/LetsEncryptServer/Services/CacheService.cs +++ b/src/LetsEncryptServer/Services/CacheService.cs @@ -10,7 +10,7 @@ using MaksIT.Models.LetsEncryptServer.Cache.Responses; namespace MaksIT.LetsEncryptServer.Services; -public interface ICacheInternalsService { +public interface ICacheInternalService { Task<(RegistrationCache[]?, IDomainResult)> LoadAccountsFromCacheAsync(); Task<(RegistrationCache?, IDomainResult)> LoadAccountFromCacheAsync(Guid accountId); Task SaveToCacheAsync(Guid accountId, RegistrationCache cache); @@ -22,6 +22,7 @@ public interface ICacheRestService { Task<(GetAccountResponse?, IDomainResult)> GetAccountAsync(Guid accountId); Task<(GetAccountResponse?, IDomainResult)> PutAccountAsync(Guid accountId, PutAccountRequest requestData); Task<(GetAccountResponse?, IDomainResult)> PatchAccountAsync(Guid accountId, PatchAccountRequest requestData); + Task DeleteAccountAsync(Guid accountId); Task<(GetContactsResponse?, IDomainResult)> GetContactsAsync(Guid accountId); Task<(GetAccountResponse?, IDomainResult)> PutContactsAsync(Guid accountId, PutContactsRequest requestData); Task<(GetAccountResponse?, IDomainResult)> PatchContactsAsync(Guid accountId, PatchContactsRequest requestData); @@ -29,7 +30,7 @@ public interface ICacheRestService { Task<(GetHostnamesResponse?, IDomainResult)> GetHostnames(Guid accountId); } -public interface ICacheService : ICacheInternalsService, ICacheRestService {} +public interface ICacheService : ICacheInternalService, ICacheRestService {} public class CacheService : ICacheService, IDisposable { private readonly ILogger _logger; @@ -237,6 +238,9 @@ public class CacheService : ICacheService, IDisposable { return CreateGetAccountResponse(accountId, cache); } + public async Task DeleteAccountAsync(Guid accountId) { + return await DeleteFromCacheAsync(accountId); + } #endregion #region Contacts Operations diff --git a/src/LetsEncryptServer/Services/CertsFlowService.cs b/src/LetsEncryptServer/Services/CertsFlowService.cs index 921303f..0d7e5be 100644 --- a/src/LetsEncryptServer/Services/CertsFlowService.cs +++ b/src/LetsEncryptServer/Services/CertsFlowService.cs @@ -1,6 +1,4 @@ -using System.Text; -using System.Net.Sockets; -using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options; using DomainResults.Common; @@ -11,26 +9,32 @@ using MaksIT.Models.LetsEncryptServer.CertsFlow.Requests; namespace MaksIT.LetsEncryptServer.Services; -public interface ICertsInternalService { - -} - - -public interface ICertsRestChallengeService { - (string?, IDomainResult) AcmeChallenge(string fileName); -} - -public interface ICertsRestService { +public interface ICertsCommonService { Task<(Guid?, IDomainResult)> ConfigureClientAsync(); (string?, IDomainResult) GetTermsOfService(Guid sessionId); + Task CompleteChallengesAsync(Guid sessionId); +} + +public interface ICertsInternalService : ICertsCommonService { + Task<(Guid?, IDomainResult)> InitAsync(Guid sessionId, Guid? accountId, string description, string[] contacts); + Task<(List?, IDomainResult)> NewOrderAsync(Guid sessionId, string[] hostnames, string challengeType); + Task GetOrderAsync(Guid sessionId, string[] hostnames); + Task GetCertificatesAsync(Guid sessionId, string[] hostnames); + Task<(Dictionary?, IDomainResult)> ApplyCertificatesAsync(Guid sessionId, string[] hostnames); +} + +public interface ICertsRestService : ICertsCommonService { Task<(Guid?, IDomainResult)> InitAsync(Guid sessionId, Guid? accountId, InitRequest requestData); Task<(List?, IDomainResult)> NewOrderAsync(Guid sessionId, NewOrderRequest requestData); - Task CompleteChallengesAsync(Guid sessionId); Task GetOrderAsync(Guid sessionId, GetOrderRequest requestData); Task GetCertificatesAsync(Guid sessionId, GetCertificatesRequest requestData); Task<(Dictionary?, IDomainResult)> ApplyCertificatesAsync(Guid sessionId, GetCertificatesRequest requestData); } +public interface ICertsRestChallengeService { + (string?, IDomainResult) AcmeChallenge(string fileName); +} + public interface ICertsFlowService : ICertsInternalService, ICertsRestService, @@ -64,6 +68,8 @@ public class CertsFlowService : ICertsFlowService { Directory.CreateDirectory(_acmePath); } + #region Common methods + public async Task<(Guid?, IDomainResult)> ConfigureClientAsync() { var sessionId = Guid.NewGuid(); @@ -86,7 +92,15 @@ public class CertsFlowService : ICertsFlowService { return IDomainResult.Success(terms); } - public async Task<(Guid?, IDomainResult)> InitAsync(Guid sessionId, Guid? accountId, InitRequest requestData) { + public async Task CompleteChallengesAsync(Guid sessionId) { + return await _letsEncryptService.CompleteChallenges(sessionId); + } + + #endregion + + #region Internal methods + + public async Task<(Guid?, IDomainResult)> InitAsync(Guid sessionId, Guid? accountId, string description, string[] contacts) { RegistrationCache? cache = null; if (accountId == null) { @@ -102,13 +116,13 @@ public class CertsFlowService : ICertsFlowService { } } - var result = await _letsEncryptService.Init(sessionId, accountId.Value, requestData.Contacts, cache); + var result = await _letsEncryptService.Init(sessionId, accountId.Value, description, contacts, cache); return result.IsSuccess ? IDomainResult.Success(accountId.Value) : (null, result); } - public async Task<(List?, IDomainResult)> NewOrderAsync(Guid sessionId, NewOrderRequest requestData) { - var (results, newOrderResult) = await _letsEncryptService.NewOrder(sessionId, requestData.Hostnames, requestData.ChallengeType); - if (!newOrderResult.IsSuccess || results == null) + public async Task<(List?, IDomainResult)> NewOrderAsync(Guid sessionId, string[] hostnames, string challengeType) { + var (results, newOrderResult) = await _letsEncryptService.NewOrder(sessionId, hostnames, challengeType); + if (!newOrderResult.IsSuccess || results == null) return (null, newOrderResult); var challenges = new List(); @@ -121,16 +135,8 @@ public class CertsFlowService : ICertsFlowService { return IDomainResult.Success(challenges); } - public async Task CompleteChallengesAsync(Guid sessionId) { - return await _letsEncryptService.CompleteChallenges(sessionId); - } - - public async Task GetOrderAsync(Guid sessionId, GetOrderRequest requestData) { - return await _letsEncryptService.GetOrder(sessionId, requestData.Hostnames); - } - - public async Task GetCertificatesAsync(Guid sessionId, GetCertificatesRequest requestData) { - foreach (var subject in requestData.Hostnames) { + public async Task GetCertificatesAsync(Guid sessionId, string[] hostnames) { + foreach (var subject in hostnames) { var result = await _letsEncryptService.GetCertificate(sessionId, subject); if (!result.IsSuccess) return result; @@ -144,16 +150,20 @@ public class CertsFlowService : ICertsFlowService { return getCacheResult; var saveResult = await _cacheService.SaveToCacheAsync(cache.AccountId, cache); - if(!saveResult.IsSuccess) + if (!saveResult.IsSuccess) return saveResult; return IDomainResult.Success(); } - public async Task<(Dictionary?, IDomainResult)> ApplyCertificatesAsync(Guid sessionId, GetCertificatesRequest requestData) { + public async Task GetOrderAsync(Guid sessionId, string[] hostnames) { + return await _letsEncryptService.GetOrder(sessionId, hostnames); + } + + public async Task<(Dictionary?, IDomainResult)> ApplyCertificatesAsync(Guid sessionId, string[] hostnames) { var results = new Dictionary(); - foreach (var subject in requestData.Hostnames) { + foreach (var subject in hostnames) { var (cert, getCertResult) = _letsEncryptService.TryGetCachedCertificate(sessionId, subject); if (!getCertResult.IsSuccess || cert == null) return (null, getCertResult); @@ -164,26 +174,38 @@ public class CertsFlowService : ICertsFlowService { // TODO: send the certificates to the server var uploadResult = await _agentService.UploadCerts(results); - if(!uploadResult.IsSuccess) + if (!uploadResult.IsSuccess) return (null, uploadResult); var reloadResult = await _agentService.ReloadService(_appSettings.Agent.ServiceToReload); - if(!reloadResult.IsSuccess) + if (!reloadResult.IsSuccess) return (null, reloadResult); return IDomainResult.Success(results); } + + #endregion + #region Webapi specific methods + public Task<(Guid?, IDomainResult)> InitAsync(Guid sessionId, Guid? accountId, InitRequest requestData) => + InitAsync(sessionId, accountId, requestData.Description, requestData.Contacts); + public Task<(List?, IDomainResult)> NewOrderAsync(Guid sessionId, NewOrderRequest requestData) => + NewOrderAsync(sessionId, requestData.Hostnames, requestData.ChallengeType); + public Task GetCertificatesAsync(Guid sessionId, GetCertificatesRequest requestData) => + GetCertificatesAsync(sessionId, requestData.Hostnames); + public Task GetOrderAsync(Guid sessionId, GetOrderRequest requestData) => + GetOrderAsync(sessionId, requestData.Hostnames); + public Task<(Dictionary?, IDomainResult)> ApplyCertificatesAsync(Guid sessionId, GetCertificatesRequest requestData) => + ApplyCertificatesAsync(sessionId, requestData.Hostnames); + + #endregion - - - - + #region Acme Challenge Webapi specific methods public (string?, IDomainResult) AcmeChallenge(string fileName) { DeleteExporedChallenges(); @@ -218,4 +240,6 @@ public class CertsFlowService : ICertsFlowService { } } } + + #endregion } diff --git a/src/Models/LetsEncryptServer/Cache/Requests/PostAccountRequest.cs b/src/Models/LetsEncryptServer/Cache/Requests/PostAccountRequest.cs new file mode 100644 index 0000000..fc6acc1 --- /dev/null +++ b/src/Models/LetsEncryptServer/Cache/Requests/PostAccountRequest.cs @@ -0,0 +1,20 @@ +using System.ComponentModel.DataAnnotations; + +namespace MaksIT.Models.LetsEncryptServer.Cache.Requests { + public class PostAccountRequest : IValidatableObject { + public required string Description { get; set; } + public required string[] Contacts { get; set; } + public required string[] Hostnames { get; set; } + + public IEnumerable Validate(ValidationContext validationContext) { + if (string.IsNullOrWhiteSpace(Description)) + yield return new ValidationResult("Description is required", new[] { nameof(Description) }); + + if (Contacts == null || Contacts.Length == 0) + yield return new ValidationResult("Contacts is required", new[] { nameof(Contacts) }); + + if (Hostnames == null || Hostnames.Length == 0) + yield return new ValidationResult("Hostnames is required", new[] { nameof(Hostnames) }); + } + } +}