(refactor): cache service review

This commit is contained in:
Maksym Sadovnychyy 2024-06-22 21:31:17 +02:00
parent 322845d82d
commit bd67ad6a6b
8 changed files with 171 additions and 81 deletions

View File

@ -18,7 +18,7 @@ namespace MaksIT.LetsEncrypt.Services;
public interface ILetsEncryptService {
Task<IDomainResult> ConfigureClient(Guid sessionId, string url);
Task<IDomainResult> Init(Guid sessionId,Guid accountId, string[] contacts, RegistrationCache? registrationCache);
Task<IDomainResult> Init(Guid sessionId,Guid accountId, string description, string[] contacts, RegistrationCache? registrationCache);
(RegistrationCache?, IDomainResult) GetRegistrationCache(Guid sessionId);
(string?, IDomainResult) GetTermsOfServiceUri(Guid sessionId);
Task<(Dictionary<string, string>?, IDomainResult)> NewOrder(Guid sessionId, string[] hostnames, string challengeType);
@ -76,7 +76,7 @@ public class LetsEncryptService : ILetsEncryptService {
#endregion
#region Init
public async Task<IDomainResult> Init(Guid sessionId, Guid accountId, string[] contacts, RegistrationCache? cache) {
public async Task<IDomainResult> 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,

View File

@ -12,8 +12,8 @@ namespace MaksIT.LetsEncryptServer.BackgroundServices {
private readonly IOptions<Configuration> _appSettings;
private readonly ILogger<AutoRenewal> _logger;
private readonly ICacheService _cacheService;
private readonly ICertsFlowService _certsFlowService;
private readonly ICacheInternalService _cacheService;
private readonly ICertsInternalService _certsFlowService;
public AutoRenewal(
IOptions<Configuration> 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;

View File

@ -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<IActionResult> GetAccounts() {
var result = await _cacheService.GetAccountsAsync();
return result.ToActionResult();
[HttpPost]
public async Task<IActionResult> 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<IActionResult> 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<IActionResult> PatchAccount(Guid accountId, [FromBody] PatchAccountRequest requestData) {
var result = await _cacheService.PatchAccountAsync(accountId, requestData);
return result.ToActionResult();
}
[HttpDelete("{accountd:guid}")]
public async Task<IActionResult> 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<IActionResult> GetContacts(Guid accountId) {
var result = await _cacheService.GetContactsAsync(accountId);
return result.ToActionResult();
}
[HttpPut("account/{accountId:guid}/contacts")]
[HttpPut("{accountId:guid}/contacts")]
public async Task<IActionResult> 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<IActionResult> 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<IActionResult> 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<IActionResult> GetHostnames(Guid accountId) {
var result = await _cacheService.GetHostnames(accountId);
return result.ToActionResult();
}
[HttpPost("{accountId:guid}")]
[HttpDelete("{accountId:guid}/hostname/{index:int}")]
public async Task<IActionResult> DeleteHostname(Guid accountId, int index) {
//var result = await _cacheService.DeleteHostnameAsync(accountId, index);
//return result.ToActionResult();
return BadRequest("Not implemented");
}
#endregion
}

View File

@ -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<IActionResult> GetAccounts() {
var result = await _cacheService.GetAccountsAsync();
return result.ToActionResult();
}
}

View File

@ -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 {
/// <summary>
/// Certificates flow controller, used for granular testing purposes
/// </summary>
[ApiController]
[Route("api/certs")]
public class CertsFlowController : ControllerBase {
private readonly Configuration _appSettings;
private readonly ICertsFlowService _certsFlowService;
public CertsFlowController(
IOptions<Configuration> appSettings,
ICertsFlowService certsFlowService
) {
_appSettings = appSettings.Value;
_certsFlowService = certsFlowService;
}

View File

@ -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<IDomainResult> 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<IDomainResult> 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<CacheService> _logger;
@ -237,6 +238,9 @@ public class CacheService : ICacheService, IDisposable {
return CreateGetAccountResponse(accountId, cache);
}
public async Task<IDomainResult> DeleteAccountAsync(Guid accountId) {
return await DeleteFromCacheAsync(accountId);
}
#endregion
#region Contacts Operations

View File

@ -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<IDomainResult> CompleteChallengesAsync(Guid sessionId);
}
public interface ICertsInternalService : ICertsCommonService {
Task<(Guid?, IDomainResult)> InitAsync(Guid sessionId, Guid? accountId, string description, string[] contacts);
Task<(List<string>?, IDomainResult)> NewOrderAsync(Guid sessionId, string[] hostnames, string challengeType);
Task<IDomainResult> GetOrderAsync(Guid sessionId, string[] hostnames);
Task<IDomainResult> GetCertificatesAsync(Guid sessionId, string[] hostnames);
Task<(Dictionary<string, string>?, IDomainResult)> ApplyCertificatesAsync(Guid sessionId, string[] hostnames);
}
public interface ICertsRestService : ICertsCommonService {
Task<(Guid?, IDomainResult)> InitAsync(Guid sessionId, Guid? accountId, InitRequest requestData);
Task<(List<string>?, IDomainResult)> NewOrderAsync(Guid sessionId, NewOrderRequest requestData);
Task<IDomainResult> CompleteChallengesAsync(Guid sessionId);
Task<IDomainResult> GetOrderAsync(Guid sessionId, GetOrderRequest requestData);
Task<IDomainResult> GetCertificatesAsync(Guid sessionId, GetCertificatesRequest requestData);
Task<(Dictionary<string, string>?, 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<string>(terms);
}
public async Task<(Guid?, IDomainResult)> InitAsync(Guid sessionId, Guid? accountId, InitRequest requestData) {
public async Task<IDomainResult> 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<Guid>(accountId.Value) : (null, result);
}
public async Task<(List<string>?, 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<string>?, 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<string>();
@ -121,16 +135,8 @@ public class CertsFlowService : ICertsFlowService {
return IDomainResult.Success(challenges);
}
public async Task<IDomainResult> CompleteChallengesAsync(Guid sessionId) {
return await _letsEncryptService.CompleteChallenges(sessionId);
}
public async Task<IDomainResult> GetOrderAsync(Guid sessionId, GetOrderRequest requestData) {
return await _letsEncryptService.GetOrder(sessionId, requestData.Hostnames);
}
public async Task<IDomainResult> GetCertificatesAsync(Guid sessionId, GetCertificatesRequest requestData) {
foreach (var subject in requestData.Hostnames) {
public async Task<IDomainResult> 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<string, string>?, IDomainResult)> ApplyCertificatesAsync(Guid sessionId, GetCertificatesRequest requestData) {
public async Task<IDomainResult> GetOrderAsync(Guid sessionId, string[] hostnames) {
return await _letsEncryptService.GetOrder(sessionId, hostnames);
}
public async Task<(Dictionary<string, string>?, IDomainResult)> ApplyCertificatesAsync(Guid sessionId, string[] hostnames) {
var results = new Dictionary<string, string>();
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<string>?, IDomainResult)> NewOrderAsync(Guid sessionId, NewOrderRequest requestData) =>
NewOrderAsync(sessionId, requestData.Hostnames, requestData.ChallengeType);
public Task<IDomainResult> GetCertificatesAsync(Guid sessionId, GetCertificatesRequest requestData) =>
GetCertificatesAsync(sessionId, requestData.Hostnames);
public Task<IDomainResult> GetOrderAsync(Guid sessionId, GetOrderRequest requestData) =>
GetOrderAsync(sessionId, requestData.Hostnames);
public Task<(Dictionary<string, string>?, 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
}

View File

@ -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<ValidationResult> 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) });
}
}
}