diff --git a/src/HopFrame.Api/Extensions/ServiceCollectionExtensions.cs b/src/HopFrame.Api/Extensions/ServiceCollectionExtensions.cs
index 21ae878..3f7a265 100644
--- a/src/HopFrame.Api/Extensions/ServiceCollectionExtensions.cs
+++ b/src/HopFrame.Api/Extensions/ServiceCollectionExtensions.cs
@@ -1,6 +1,7 @@
using HopFrame.Api.Controller;
using HopFrame.Api.Logic;
using HopFrame.Api.Logic.Implementation;
+using HopFrame.Api.Models;
using HopFrame.Database;
using HopFrame.Security.Authentication;
using HopFrame.Security.Authentication.OpenID;
@@ -18,9 +19,15 @@ public static class ServiceCollectionExtensions {
///
/// The service provider to add the services to
/// The configuration used to configure HopFrame authentication
+ /// Configuration for how the HopFrame services get set up
/// The data source for all HopFrame entities
- public static void AddHopFrame(this IServiceCollection services, ConfigurationManager configuration) where TDbContext : HopDbContextBase {
- var controllers = new List { typeof(UserController), typeof(GroupController) };
+ public static void AddHopFrame(this IServiceCollection services, ConfigurationManager configuration, HopFrameApiModuleConfig config = null) where TDbContext : HopDbContextBase {
+ config ??= new();
+
+ var controllers = new List();
+
+ if (config.ExposeModelEndpoints)
+ controllers.AddRange([typeof(UserController), typeof(GroupController)]);
var defaultAuthenticationSection = configuration.GetSection("HopFrame:Authentication:DefaultAuthentication");
if (!defaultAuthenticationSection.Exists() || configuration.GetValue("HopFrame:Authentication:DefaultAuthentication"))
@@ -31,29 +38,33 @@ public static class ServiceCollectionExtensions {
controllers.Add(typeof(OpenIdController));
}
- AddHopFrameNoEndpoints(services, configuration);
+ AddHopFrameNoEndpoints(services, configuration, config);
services.AddMvcCore().UseSpecificControllers(controllers.ToArray());
}
-
+
///
/// Adds all HopFrame services to the application
///
/// The service provider to add the services to
/// The configuration used to configure HopFrame authentication
+ /// Configuration for how the HopFrame services get set up
/// The data source for all HopFrame entities
- public static void AddHopFrameNoEndpoints(this IServiceCollection services, ConfigurationManager configuration) where TDbContext : HopDbContextBase {
+ public static void AddHopFrameNoEndpoints(this IServiceCollection services, ConfigurationManager configuration, HopFrameApiModuleConfig config = null) where TDbContext : HopDbContextBase {
+ config ??= new();
+
services.AddMvcCore().ConfigureApplicationPartManager(manager => {
var endpoints = manager.ApplicationParts.SingleOrDefault(p => p.Name == typeof(ServiceCollectionExtensions).Namespace!.Replace(".Extensions", ""));
manager.ApplicationParts.Remove(endpoints);
});
-
+
+ services.AddSingleton(config);
services.AddHopFrameRepositories();
services.TryAddSingleton();
services.AddScoped();
services.AddScoped();
services.AddScoped();
- services.AddHopFrameAuthentication(configuration);
+ services.AddHopFrameAuthentication(configuration, config);
}
}
diff --git a/src/HopFrame.Api/Models/HopFrameApiModuleConfig.cs b/src/HopFrame.Api/Models/HopFrameApiModuleConfig.cs
new file mode 100644
index 0000000..85afc5b
--- /dev/null
+++ b/src/HopFrame.Api/Models/HopFrameApiModuleConfig.cs
@@ -0,0 +1,7 @@
+using HopFrame.Security.Models;
+
+namespace HopFrame.Api.Models;
+
+public class HopFrameApiModuleConfig : HopFrameConfig {
+ public bool ExposeModelEndpoints { get; set; } = true;
+}
\ No newline at end of file
diff --git a/src/HopFrame.Security/Authentication/HopFrameAuthenticationExtensions.cs b/src/HopFrame.Security/Authentication/HopFrameAuthenticationExtensions.cs
index a6bf52c..228804e 100644
--- a/src/HopFrame.Security/Authentication/HopFrameAuthenticationExtensions.cs
+++ b/src/HopFrame.Security/Authentication/HopFrameAuthenticationExtensions.cs
@@ -3,6 +3,7 @@ using HopFrame.Security.Authentication.OpenID.Implementation;
using HopFrame.Security.Authentication.OpenID.Options;
using HopFrame.Security.Authorization;
using HopFrame.Security.Claims;
+using HopFrame.Security.Models;
using HopFrame.Security.Options;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
@@ -18,8 +19,13 @@ public static class HopFrameAuthenticationExtensions {
///
/// The service provider to add the services to
/// The configuration used to configure HopFrame authentication
+ /// Configuration for how the HopFrame services get set up
///
- public static IServiceCollection AddHopFrameAuthentication(this IServiceCollection service, ConfigurationManager configuration) {
+ public static IServiceCollection AddHopFrameAuthentication(this IServiceCollection service, ConfigurationManager configuration, HopFrameConfig config = null) {
+ config ??= new HopFrameConfig();
+
+ service.AddSingleton(config);
+ service.AddScoped(typeof(ICacheProvider), config.CacheProvider);
service.TryAddSingleton();
service.AddScoped();
diff --git a/src/HopFrame.Security/Authentication/OpenID/ICacheProvider.cs b/src/HopFrame.Security/Authentication/OpenID/ICacheProvider.cs
new file mode 100644
index 0000000..65a553b
--- /dev/null
+++ b/src/HopFrame.Security/Authentication/OpenID/ICacheProvider.cs
@@ -0,0 +1,6 @@
+namespace HopFrame.Security.Authentication.OpenID;
+
+public interface ICacheProvider {
+ Task GetOrCreate(string key, Func> factory) where TItem : class;
+ Task Set(string key, TItem value, TimeSpan ttl);
+}
\ No newline at end of file
diff --git a/src/HopFrame.Security/Authentication/OpenID/Implementation/MemoryCacheProvider.cs b/src/HopFrame.Security/Authentication/OpenID/Implementation/MemoryCacheProvider.cs
new file mode 100644
index 0000000..2b8ac3e
--- /dev/null
+++ b/src/HopFrame.Security/Authentication/OpenID/Implementation/MemoryCacheProvider.cs
@@ -0,0 +1,18 @@
+using Microsoft.Extensions.Caching.Memory;
+
+namespace HopFrame.Security.Authentication.OpenID.Implementation;
+
+public class MemoryCacheProvider(IMemoryCache cache) : ICacheProvider {
+ public Task GetOrCreate(string key, Func> factory) where TItem : class {
+ if (cache.TryGetValue(key, out var value)) {
+ return Task.FromResult(value as TItem);
+ }
+
+ return factory.Invoke();
+ }
+
+ public Task Set(string key, TItem value, TimeSpan ttl) {
+ cache.Set(key, value, ttl);
+ return Task.CompletedTask;
+ }
+}
\ No newline at end of file
diff --git a/src/HopFrame.Security/Authentication/OpenID/Implementation/OpenIdAccessor.cs b/src/HopFrame.Security/Authentication/OpenID/Implementation/OpenIdAccessor.cs
index 2839d10..7aa1923 100644
--- a/src/HopFrame.Security/Authentication/OpenID/Implementation/OpenIdAccessor.cs
+++ b/src/HopFrame.Security/Authentication/OpenID/Implementation/OpenIdAccessor.cs
@@ -3,21 +3,24 @@ using HopFrame.Security.Authentication.OpenID.Models;
using HopFrame.Security.Authentication.OpenID.Options;
using HopFrame.Security.Claims;
using Microsoft.AspNetCore.Http;
-using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
namespace HopFrame.Security.Authentication.OpenID.Implementation;
-internal class OpenIdAccessor(IHttpClientFactory clientFactory, IOptions options, IHttpContextAccessor accessor, IMemoryCache cache) : IOpenIdAccessor {
+internal class OpenIdAccessor(IHttpClientFactory clientFactory, IOptions options, IHttpContextAccessor accessor, ICacheProvider cache) : IOpenIdAccessor {
private const string ConfigurationCacheKey = "HopFrame:OpenID:Configuration";
private const string AuthCodeCacheKey = "HopFrame:OpenID:Code:";
private const string TokenCacheKey = "HopFrame:OpenID:Token:";
- public async Task LoadConfiguration() {
- if (options.Value.Cache.Enabled && options.Value.Cache.Configuration.Enabled && cache.TryGetValue(ConfigurationCacheKey, out object cachedConfiguration)) {
- return cachedConfiguration as OpenIdConfiguration;
+ public Task LoadConfiguration() {
+ if (options.Value.Cache.Enabled && options.Value.Cache.Configuration.Enabled) {
+ return cache.GetOrCreate(ConfigurationCacheKey, LoadConfigurationInCache);
}
+ return LoadConfigurationInCache();
+ }
+
+ internal async Task LoadConfigurationInCache() {
var client = clientFactory.CreateClient();
var request = new HttpRequestMessage(HttpMethod.Get, Path.Combine(options.Value.Issuer, ".well-known/openid-configuration").Replace("\\", "/"));
var response = await client.SendAsync(request);
@@ -28,16 +31,20 @@ internal class OpenIdAccessor(IHttpClientFactory clientFactory, IOptions(await response.Content.ReadAsStreamAsync());
if (options.Value.Cache.Enabled && options.Value.Cache.Configuration.Enabled)
- cache.Set(ConfigurationCacheKey, config, options.Value.Cache.Configuration.TTL.ConstructTimeSpan);
+ await cache.Set(ConfigurationCacheKey, config, options.Value.Cache.Configuration.TTL.ConstructTimeSpan);
return config;
}
- public async Task RequestToken(string code) {
- if (options.Value.Cache.Enabled && options.Value.Cache.Auth.Enabled && cache.TryGetValue(AuthCodeCacheKey + code, out object cachedToken)) {
- return cachedToken as OpenIdToken;
+ public Task RequestToken(string code) {
+ if (options.Value.Cache.Enabled && options.Value.Cache.Auth.Enabled) {
+ return cache.GetOrCreate(AuthCodeCacheKey + code, () => RequestTokenInCache(code));
}
-
+
+ return RequestTokenInCache(code);
+ }
+
+ internal async Task RequestTokenInCache(string code) {
var protocol = accessor.HttpContext!.Request.IsHttps ? "https" : "http";
var callback = options.Value.Callback ?? Path.Combine($"{protocol}://{accessor.HttpContext!.Request.Host.Value}", IOpenIdAccessor.DefaultCallback).Replace("\\", "/");
@@ -61,7 +68,7 @@ internal class OpenIdAccessor(IHttpClientFactory clientFactory, IOptions(await response.Content.ReadAsStreamAsync());
if (options.Value.Cache.Enabled && options.Value.Cache.Auth.Enabled)
- cache.Set(AuthCodeCacheKey + code, token, options.Value.Cache.Auth.TTL.ConstructTimeSpan);
+ await cache.Set(AuthCodeCacheKey + code, token, options.Value.Cache.Auth.TTL.ConstructTimeSpan);
return token;
}
@@ -74,11 +81,15 @@ internal class OpenIdAccessor(IHttpClientFactory clientFactory, IOptions InspectToken(string token) {
- if (options.Value.Cache.Enabled && options.Value.Cache.Inspection.Enabled && cache.TryGetValue(TokenCacheKey + token, out object cachedToken)) {
- return cachedToken as OpenIdIntrospection;
+ public Task InspectToken(string token) {
+ if (options.Value.Cache.Enabled && options.Value.Cache.Inspection.Enabled) {
+ return cache.GetOrCreate(TokenCacheKey + token, () => InspectTokenInCache(token));
}
-
+
+ return InspectTokenInCache(token);
+ }
+
+ internal async Task InspectTokenInCache(string token) {
var configuration = await LoadConfiguration();
var client = clientFactory.CreateClient();
@@ -97,7 +108,7 @@ internal class OpenIdAccessor(IHttpClientFactory clientFactory, IOptions(await response.Content.ReadAsStreamAsync());
if (options.Value.Cache.Enabled && options.Value.Cache.Inspection.Enabled)
- cache.Set(TokenCacheKey + token, introspection, options.Value.Cache.Inspection.TTL.ConstructTimeSpan);
+ await cache.Set(TokenCacheKey + token, introspection, options.Value.Cache.Inspection.TTL.ConstructTimeSpan);
return introspection;
}
diff --git a/src/HopFrame.Security/Models/HopFrameConfig.cs b/src/HopFrame.Security/Models/HopFrameConfig.cs
new file mode 100644
index 0000000..119b541
--- /dev/null
+++ b/src/HopFrame.Security/Models/HopFrameConfig.cs
@@ -0,0 +1,7 @@
+using HopFrame.Security.Authentication.OpenID.Implementation;
+
+namespace HopFrame.Security.Models;
+
+public class HopFrameConfig {
+ public Type CacheProvider { get; set; } = typeof(MemoryCacheProvider);
+}
\ No newline at end of file
diff --git a/src/HopFrame.Web/Models/HopFrameWebModuleConfig.cs b/src/HopFrame.Web/Models/HopFrameWebModuleConfig.cs
index 226b144..0ff5118 100644
--- a/src/HopFrame.Web/Models/HopFrameWebModuleConfig.cs
+++ b/src/HopFrame.Web/Models/HopFrameWebModuleConfig.cs
@@ -1,5 +1,7 @@
+using HopFrame.Security.Models;
+
namespace HopFrame.Web.Models;
-public class HopFrameWebModuleConfig {
+public class HopFrameWebModuleConfig : HopFrameConfig {
public string AdminLoginPageUri { get; set; } = "/administration/login";
}
\ No newline at end of file
diff --git a/src/HopFrame.Web/ServiceCollectionExtensions.cs b/src/HopFrame.Web/ServiceCollectionExtensions.cs
index f87dc4b..c6b62fa 100644
--- a/src/HopFrame.Web/ServiceCollectionExtensions.cs
+++ b/src/HopFrame.Web/ServiceCollectionExtensions.cs
@@ -14,18 +14,19 @@ namespace HopFrame.Web;
public static class ServiceCollectionExtensions {
public static IServiceCollection AddHopFrame(this IServiceCollection services, ConfigurationManager configuration, HopFrameWebModuleConfig config = null) where TDbContext : HopDbContextBase {
+ config ??= new HopFrameWebModuleConfig();
services.AddHttpClient();
services.AddHopFrameRepositories();
services.AddScoped();
services.AddTransient();
services.AddAdminContext();
- services.AddSingleton(config ?? new HopFrameWebModuleConfig());
+ services.AddSingleton(config);
// Component library's
services.AddSweetAlert2();
services.AddBlazorStrap();
- services.AddHopFrameAuthentication(configuration);
+ services.AddHopFrameAuthentication(configuration, config);
return services;
}