Release v2.2.0 #51
@@ -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 {
|
||||
/// </summary>
|
||||
/// <param name="services">The service provider to add the services to</param>
|
||||
/// <param name="configuration">The configuration used to configure HopFrame authentication</param>
|
||||
/// <param name="config">Configuration for how the HopFrame services get set up</param>
|
||||
/// <typeparam name="TDbContext">The data source for all HopFrame entities</typeparam>
|
||||
public static void AddHopFrame<TDbContext>(this IServiceCollection services, ConfigurationManager configuration) where TDbContext : HopDbContextBase {
|
||||
var controllers = new List<Type> { typeof(UserController), typeof(GroupController) };
|
||||
public static void AddHopFrame<TDbContext>(this IServiceCollection services, ConfigurationManager configuration, HopFrameApiModuleConfig config = null) where TDbContext : HopDbContextBase {
|
||||
config ??= new();
|
||||
|
||||
var controllers = new List<Type>();
|
||||
|
||||
if (config.ExposeModelEndpoints)
|
||||
controllers.AddRange([typeof(UserController), typeof(GroupController)]);
|
||||
|
||||
var defaultAuthenticationSection = configuration.GetSection("HopFrame:Authentication:DefaultAuthentication");
|
||||
if (!defaultAuthenticationSection.Exists() || configuration.GetValue<bool>("HopFrame:Authentication:DefaultAuthentication"))
|
||||
@@ -31,29 +38,33 @@ public static class ServiceCollectionExtensions {
|
||||
controllers.Add(typeof(OpenIdController));
|
||||
}
|
||||
|
||||
AddHopFrameNoEndpoints<TDbContext>(services, configuration);
|
||||
AddHopFrameNoEndpoints<TDbContext>(services, configuration, config);
|
||||
services.AddMvcCore().UseSpecificControllers(controllers.ToArray());
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Adds all HopFrame services to the application
|
||||
/// </summary>
|
||||
/// <param name="services">The service provider to add the services to</param>
|
||||
/// <param name="configuration">The configuration used to configure HopFrame authentication</param>
|
||||
/// <param name="config">Configuration for how the HopFrame services get set up</param>
|
||||
/// <typeparam name="TDbContext">The data source for all HopFrame entities</typeparam>
|
||||
public static void AddHopFrameNoEndpoints<TDbContext>(this IServiceCollection services, ConfigurationManager configuration) where TDbContext : HopDbContextBase {
|
||||
public static void AddHopFrameNoEndpoints<TDbContext>(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<TDbContext>();
|
||||
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
|
||||
services.AddScoped<IAuthLogic, AuthLogic>();
|
||||
services.AddScoped<IUserLogic, UserLogic>();
|
||||
services.AddScoped<IGroupLogic, GroupLogic>();
|
||||
|
||||
services.AddHopFrameAuthentication(configuration);
|
||||
services.AddHopFrameAuthentication(configuration, config);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
7
src/HopFrame.Api/Models/HopFrameApiModuleConfig.cs
Normal file
7
src/HopFrame.Api/Models/HopFrameApiModuleConfig.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
using HopFrame.Security.Models;
|
||||
|
||||
namespace HopFrame.Api.Models;
|
||||
|
||||
public class HopFrameApiModuleConfig : HopFrameConfig {
|
||||
public bool ExposeModelEndpoints { get; set; } = true;
|
||||
}
|
||||
@@ -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 {
|
||||
/// </summary>
|
||||
/// <param name="service">The service provider to add the services to</param>
|
||||
/// <param name="configuration">The configuration used to configure HopFrame authentication</param>
|
||||
/// <param name="config">Configuration for how the HopFrame services get set up</param>
|
||||
/// <returns></returns>
|
||||
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<IHttpContextAccessor, HttpContextAccessor>();
|
||||
service.AddScoped<ITokenContext, TokenContextImplementor>();
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace HopFrame.Security.Authentication.OpenID;
|
||||
|
||||
public interface ICacheProvider {
|
||||
Task<TItem> GetOrCreate<TItem>(string key, Func<Task<TItem>> factory) where TItem : class;
|
||||
Task Set<TItem>(string key, TItem value, TimeSpan ttl);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
|
||||
namespace HopFrame.Security.Authentication.OpenID.Implementation;
|
||||
|
||||
public class MemoryCacheProvider(IMemoryCache cache) : ICacheProvider {
|
||||
public Task<TItem> GetOrCreate<TItem>(string key, Func<Task<TItem>> factory) where TItem : class {
|
||||
if (cache.TryGetValue(key, out var value)) {
|
||||
return Task.FromResult(value as TItem);
|
||||
}
|
||||
|
||||
return factory.Invoke();
|
||||
}
|
||||
|
||||
public Task Set<TItem>(string key, TItem value, TimeSpan ttl) {
|
||||
cache.Set(key, value, ttl);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -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<OpenIdOptions> options, IHttpContextAccessor accessor, IMemoryCache cache) : IOpenIdAccessor {
|
||||
internal class OpenIdAccessor(IHttpClientFactory clientFactory, IOptions<OpenIdOptions> 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<OpenIdConfiguration> LoadConfiguration() {
|
||||
if (options.Value.Cache.Enabled && options.Value.Cache.Configuration.Enabled && cache.TryGetValue(ConfigurationCacheKey, out object cachedConfiguration)) {
|
||||
return cachedConfiguration as OpenIdConfiguration;
|
||||
public Task<OpenIdConfiguration> LoadConfiguration() {
|
||||
if (options.Value.Cache.Enabled && options.Value.Cache.Configuration.Enabled) {
|
||||
return cache.GetOrCreate(ConfigurationCacheKey, LoadConfigurationInCache);
|
||||
}
|
||||
|
||||
return LoadConfigurationInCache();
|
||||
}
|
||||
|
||||
internal async Task<OpenIdConfiguration> 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<OpenIdO
|
||||
var config = await JsonSerializer.DeserializeAsync<OpenIdConfiguration>(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<OpenIdToken> 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<OpenIdToken> 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<OpenIdToken> 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<OpenIdO
|
||||
var token = await JsonSerializer.DeserializeAsync<OpenIdToken>(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<OpenIdO
|
||||
return $"{configuration.AuthorizationEndpoint}?response_type=code&client_id={options.Value.ClientId}&redirect_uri={callback}&scope=openid%20profile%20email%20offline_access&state={state}";
|
||||
}
|
||||
|
||||
public async Task<OpenIdIntrospection> 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<OpenIdIntrospection> 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<OpenIdIntrospection> InspectTokenInCache(string token) {
|
||||
var configuration = await LoadConfiguration();
|
||||
|
||||
var client = clientFactory.CreateClient();
|
||||
@@ -97,7 +108,7 @@ internal class OpenIdAccessor(IHttpClientFactory clientFactory, IOptions<OpenIdO
|
||||
var introspection = await JsonSerializer.DeserializeAsync<OpenIdIntrospection>(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;
|
||||
}
|
||||
|
||||
7
src/HopFrame.Security/Models/HopFrameConfig.cs
Normal file
7
src/HopFrame.Security/Models/HopFrameConfig.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
using HopFrame.Security.Authentication.OpenID.Implementation;
|
||||
|
||||
namespace HopFrame.Security.Models;
|
||||
|
||||
public class HopFrameConfig {
|
||||
public Type CacheProvider { get; set; } = typeof(MemoryCacheProvider);
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
@@ -14,18 +14,19 @@ namespace HopFrame.Web;
|
||||
|
||||
public static class ServiceCollectionExtensions {
|
||||
public static IServiceCollection AddHopFrame<TDbContext>(this IServiceCollection services, ConfigurationManager configuration, HopFrameWebModuleConfig config = null) where TDbContext : HopDbContextBase {
|
||||
config ??= new HopFrameWebModuleConfig();
|
||||
services.AddHttpClient();
|
||||
services.AddHopFrameRepositories<TDbContext>();
|
||||
services.AddScoped<IAuthService, AuthService>();
|
||||
services.AddTransient<AuthMiddleware>();
|
||||
services.AddAdminContext<HopAdminContext>();
|
||||
services.AddSingleton(config ?? new HopFrameWebModuleConfig());
|
||||
services.AddSingleton(config);
|
||||
|
||||
// Component library's
|
||||
services.AddSweetAlert2();
|
||||
services.AddBlazorStrap();
|
||||
|
||||
services.AddHopFrameAuthentication(configuration);
|
||||
services.AddHopFrameAuthentication(configuration, config);
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user