Skip to content

Commit

Permalink
Let all modules stateless
Browse files Browse the repository at this point in the history
  • Loading branch information
arenekosreal committed Nov 24, 2024
1 parent 439ed86 commit 6063b5b
Show file tree
Hide file tree
Showing 6 changed files with 95 additions and 50 deletions.
12 changes: 12 additions & 0 deletions E5Renewer.Models.GraphAPIs/IUserClientProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using E5Renewer.Models.Secrets;

using Microsoft.Graph;

namespace E5Renewer.Models.GraphAPIs;

/// <summary>The api interface to keep <see cref="GraphServiceClient"/> for <see cref="User"/></summary>
public interface IUserClientProvider
{
/// <summary>Get client for user.</summary>
public Task<GraphServiceClient> GetClientForUserAsync(User user);
}
51 changes: 6 additions & 45 deletions E5Renewer.Models.GraphAPIs/RandomGraphAPICaller.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
using System.Security.Cryptography.X509Certificates;

using Azure.Core;
using Azure.Identity;

using E5Renewer.Models.Modules;
using E5Renewer.Models.Secrets;
Expand All @@ -22,68 +19,31 @@ public class RandomGraphAPICaller : BasicModule, IGraphAPICaller
private readonly ILogger<RandomGraphAPICaller> logger;
private readonly IEnumerable<IAPIFunction> apiFunctions;
private readonly IStatusManager statusManager;
private readonly ISecretProvider secretProvider;
private readonly Dictionary<User, GraphServiceClient> clients = new();
private readonly IUserClientProvider clientProvider;

/// <summary>Initialize <c>RandomGraphAPICaller</c> with parameters given.</summary>
/// <param name="logger">The logger to generate log.</param>
/// <param name="apiFunctions">All known api functions with their id.</param>
/// <param name="statusManager"><see cref="IStatusManager"/> implementation.</param>
/// <param name="secretProvider"><see cref="ISecretProvider"/> implementations.</param>
/// <param name="clientProvider"><see cref="IUserClientProvider"/> implementation.</param>
/// <remarks>All parameters should be injected by Asp.Net Core.</remarks>
public RandomGraphAPICaller(
ILogger<RandomGraphAPICaller> logger,
IEnumerable<IAPIFunction> apiFunctions,
IStatusManager statusManager,
ISecretProvider secretProvider
IUserClientProvider clientProvider
)
{
this.logger = logger;
this.apiFunctions = apiFunctions;
this.statusManager = statusManager;
this.secretProvider = secretProvider;
this.clientProvider = clientProvider;
this.logger.LogDebug("Found {0} api functions", this.apiFunctions.Count());
}

/// <inheritdoc/>
public async Task CallNextAPIAsync(User user)
{
if (!this.clients.ContainsKey(user))
{
ClientCertificateCredentialOptions options = new()
{
AuthorityHost = AzureAuthorityHosts.AzurePublicCloud
};
TokenCredential credential;
if (user.certificate?.Exists ?? false)
{
this.logger.LogDebug("Using certificate to get user token.");
string? password = await this.secretProvider.GetPasswordForCertificateAsync(user.certificate);
if (password is not null)
{
this.logger.LogDebug("Found password for certificate given.");
}
using (FileStream fileStream = user.certificate.OpenRead())
{
byte[] buffer = new byte[user.certificate.Length];
int size = await fileStream.ReadAsync(buffer);
X509Certificate2 certificate = new(buffer.Take(size).ToArray(), password);
credential = new ClientCertificateCredential(user.tenantId, user.clientId, certificate, options);
}
}
else if (!string.IsNullOrEmpty(user.secret))
{
this.logger.LogDebug("Using secret to get user token.");
credential = new ClientSecretCredential(user.tenantId, user.clientId, user.secret, options);
}
else
{
throw new NullReferenceException($"{nameof(user.certificate)} and {nameof(user.secret)} are both invalid.");
}
GraphServiceClient client = new(credential, ["https://graph.microsoft.com/.default"]);
this.clients[user] = client;
}

if (this.apiFunctions.Count() <= 0)
{
this.logger.LogError("No {0} is found.", nameof(IAPIFunction));
Expand All @@ -99,8 +59,9 @@ int GetFunctionWeightOfCurrentUser(IAPIFunction function)
return successCount + 1; // let weight greater than zero
}

GraphServiceClient client = await this.clientProvider.GetClientForUserAsync(user);
IAPIFunction apiFunction = this.apiFunctions.GetDifferentItemsByWeight(GetFunctionWeightOfCurrentUser, 1).First();
APICallResult result = await apiFunction.SafeCallAsync(this.clients[user], user.name);
APICallResult result = await apiFunction.SafeCallAsync(client, user.name);
await this.statusManager.SetResultAsync(user.name, apiFunction.id, result.ToString());
}

Expand Down
67 changes: 67 additions & 0 deletions E5Renewer.Models.GraphAPIs/SimpleUserClientProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using E5Renewer.Models.Secrets;

using Microsoft.Graph;
using Microsoft.Extensions.Logging;
using System.Security.Cryptography.X509Certificates;

using Azure.Core;
using Azure.Identity;

namespace E5Renewer.Models.GraphAPIs;

/// <summary><see cref="IUserClientProvider"/> implementation for providing user client in memory.</summary>
public class SimpleUserClientProvider : IUserClientProvider
{
private readonly Dictionary<User, GraphServiceClient> clients = new();

private readonly ILogger<SimpleSecretProvider> logger;
private readonly ISecretProvider secretProvider;

/// <summary>Initialize <see cref="SimpleSecretProvider"/> with arguments given.</summary>
/// <param name="logger">The logger to generate logs.</param>
/// <param name="secretProvider">The <see cref="ISecretProvider"/> implementation.</param>
/// <remarks>All arguments should be injected by Asp.Net Core.</remarks>
public SimpleUserClientProvider(ILogger<SimpleSecretProvider> logger, ISecretProvider secretProvider) =>
(this.logger, this.secretProvider) = (logger, secretProvider);

/// <inheritdoc/>
public async Task<GraphServiceClient> GetClientForUserAsync(User user)
{
if (!this.clients.ContainsKey(user))
{
ClientCertificateCredentialOptions options = new()
{
AuthorityHost = AzureAuthorityHosts.AzurePublicCloud
};
TokenCredential credential;
if (user.certificate?.Exists ?? false)
{
this.logger.LogDebug("Using certificate to get user token.");
string? password = await this.secretProvider.GetPasswordForCertificateAsync(user.certificate);
if (password is not null)
{
this.logger.LogDebug("Found password for certificate given.");
}
using (FileStream fileStream = user.certificate.OpenRead())
{
byte[] buffer = new byte[user.certificate.Length];
int size = await fileStream.ReadAsync(buffer);
X509Certificate2 certificate = new(buffer.Take(size).ToArray(), password);
credential = new ClientCertificateCredential(user.tenantId, user.clientId, certificate, options);
}
}
else if (!string.IsNullOrEmpty(user.secret))
{
this.logger.LogDebug("Using secret to get user token.");
credential = new ClientSecretCredential(user.tenantId, user.clientId, user.secret, options);
}
else
{
throw new NullReferenceException($"{nameof(user.certificate)} and {nameof(user.secret)} are both invalid.");
}
GraphServiceClient client = new(credential, ["https://graph.microsoft.com/.default"]);
this.clients[user] = client;
}
return this.clients[user];
}
}
12 changes: 7 additions & 5 deletions E5Renewer/IServiceCollectionExtends.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ namespace E5Renewer
{
internal static class IServiceCollectionExtends
{
public static IServiceCollection AddUserClientProvider(this IServiceCollection services) =>
services.AddSingleton<IUserClientProvider, SimpleUserClientProvider>();
public static IServiceCollection AddDummyResultGenerator(this IServiceCollection services) =>
services.AddTransient<IDummyResultGenerator, SimpleDummyResultGeneratorV1>();

Expand Down Expand Up @@ -44,23 +46,23 @@ public static IServiceCollection AddModules(this IServiceCollection services, pa
{
if (t.IsAssignableTo(typeof(IModulesChecker)))
{
services.AddSingleton(typeof(IModulesChecker), t);
services.AddTransient(typeof(IModulesChecker), t);
}
else if (t.IsAssignableTo(typeof(IUserSecretLoader)))
{
services.AddSingleton(typeof(IUserSecretLoader), t);
services.AddTransient(typeof(IUserSecretLoader), t);
}
else if (t.IsAssignableTo(typeof(IGraphAPICaller)))
{
services.AddSingleton(typeof(IGraphAPICaller), t);
services.AddTransient(typeof(IGraphAPICaller), t);
}
else if (t.IsAssignableTo(typeof(IAPIFunction)))
{
services.AddSingleton(typeof(IAPIFunction), t);
services.AddTransient(typeof(IAPIFunction), t);
}
else if (t.IsAssignableTo(typeof(IModule)))
{
services.AddSingleton(typeof(IModule), t);
services.AddTransient(typeof(IModule), t);
}
}
}
Expand Down
1 change: 1 addition & 0 deletions E5Renewer/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@
.AddSecretProvider()
.AddStatusManager()
.AddTimeStampGenerator()
.AddUserClientProvider()
.AddHostedServices()
.AddControllers()
.AddJsonOptions(
Expand Down
2 changes: 2 additions & 0 deletions E5Renewer/WebApplicationExtends.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,14 @@ public static IApplicationBuilder UseModulesCheckers(this WebApplication app)
IEnumerable<IModulesChecker> modulesCheckers = app.Services.GetServices<IModulesChecker>();
IEnumerable<IUserSecretLoader> userSecretLoaders = app.Services.GetServices<IUserSecretLoader>();
IEnumerable<IGraphAPICaller> graphAPICallers = app.Services.GetServices<IGraphAPICaller>();
IEnumerable<IAPIFunction> apiFunctions = app.Services.GetServices<IAPIFunction>();
IEnumerable<IModule> otherModules = app.Services.GetServices<IModule>();

List<IModule> modulesToCheck = new();
modulesToCheck.AddRange(modulesCheckers);
modulesToCheck.AddRange(userSecretLoaders);
modulesToCheck.AddRange(graphAPICallers);
modulesToCheck.AddRange(apiFunctions);
modulesToCheck.AddRange(otherModules);

modulesToCheck.ForEach(
Expand Down

0 comments on commit 6063b5b

Please sign in to comment.