Use OpenIddict with real db instead of inmemory (#14465)
* Add OpenIddict tables to database (#14449) * Added migrations to install EF Core OpenIddict tables * Handle Install of ef core data (Needs to be outside of transaction * Cleanup and renaming, as these things will be reused for more than openiddict in the future * Cleanup * Extract db context setup * Minor cleanup --------- Co-authored-by: Nikolaj <nikolajlauridsen@protonmail.ch> * Use OpenIddict from DB instead of InMemoryDb * Do not try to clean up, while not it run mode * Fixed tests * Clean up --------- Co-authored-by: Nikolaj <nikolajlauridsen@protonmail.ch> Co-authored-by: Elitsa <elm@umbraco.dk>
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using OpenIddict.Validation.AspNetCore;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
@@ -10,6 +9,7 @@ using Umbraco.Cms.Api.Management.Security;
|
||||
using Umbraco.Cms.Web.Common.Authorization;
|
||||
using Umbraco.Cms.Infrastructure.HostedServices;
|
||||
using Umbraco.Cms.Infrastructure.Security;
|
||||
using Umbraco.Cms.Web.Common.ApplicationBuilder;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.DependencyInjection;
|
||||
|
||||
@@ -18,43 +18,18 @@ public static class BackOfficeAuthBuilderExtensions
|
||||
public static IUmbracoBuilder AddBackOfficeAuthentication(this IUmbracoBuilder builder)
|
||||
{
|
||||
builder
|
||||
.AddDbContext()
|
||||
.AddOpenIddict()
|
||||
.AddBackOfficeLogin();
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
private static IUmbracoBuilder AddDbContext(this IUmbracoBuilder builder)
|
||||
{
|
||||
builder.Services.AddDbContext<DbContext>(options =>
|
||||
{
|
||||
// Configure the DB context
|
||||
// TODO: use actual Umbraco DbContext once EF is implemented - and remove dependency on Microsoft.EntityFrameworkCore.InMemory
|
||||
options.UseInMemoryDatabase(nameof(DbContext));
|
||||
|
||||
// Register the entity sets needed by OpenIddict.
|
||||
options.UseOpenIddict();
|
||||
});
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
private static IUmbracoBuilder AddOpenIddict(this IUmbracoBuilder builder)
|
||||
{
|
||||
builder.Services.AddAuthentication();
|
||||
builder.Services.AddAuthorization(CreatePolicies);
|
||||
|
||||
builder.Services.AddOpenIddict()
|
||||
|
||||
// Register the OpenIddict core components.
|
||||
.AddCore(options =>
|
||||
{
|
||||
options
|
||||
.UseEntityFrameworkCore()
|
||||
.UseDbContext<DbContext>();
|
||||
})
|
||||
|
||||
// Register the OpenIddict server components.
|
||||
.AddServer(options =>
|
||||
{
|
||||
@@ -118,9 +93,9 @@ public static class BackOfficeAuthBuilderExtensions
|
||||
|
||||
builder.Services.AddTransient<IBackOfficeApplicationManager, BackOfficeApplicationManager>();
|
||||
builder.Services.AddSingleton<BackOfficeAuthorizationInitializationMiddleware>();
|
||||
builder.Services.Configure<UmbracoPipelineOptions>(options => options.AddFilter(new BackofficePipelineFilter("Backoffice")));
|
||||
|
||||
builder.Services.AddHostedService<OpenIddictCleanup>();
|
||||
builder.Services.AddHostedService<DatabaseManager>();
|
||||
|
||||
return builder;
|
||||
}
|
||||
@@ -138,28 +113,6 @@ public static class BackOfficeAuthBuilderExtensions
|
||||
return builder;
|
||||
}
|
||||
|
||||
// TODO: remove this once EF is implemented
|
||||
public class DatabaseManager : IHostedService
|
||||
{
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
|
||||
public DatabaseManager(IServiceProvider serviceProvider) => _serviceProvider = serviceProvider;
|
||||
|
||||
public async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
using IServiceScope scope = _serviceProvider.CreateScope();
|
||||
|
||||
DbContext context = scope.ServiceProvider.GetRequiredService<DbContext>();
|
||||
await context.Database.EnsureCreatedAsync(cancellationToken);
|
||||
|
||||
// TODO: add BackOfficeAuthorizationInitializationMiddleware before UseAuthorization (to make it run for unauthorized API requests) and remove this
|
||||
IBackOfficeApplicationManager backOfficeApplicationManager = scope.ServiceProvider.GetRequiredService<IBackOfficeApplicationManager>();
|
||||
await backOfficeApplicationManager.EnsureBackOfficeApplicationAsync(new Uri("https://" +
|
||||
"localhost:44339/"), cancellationToken);
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
}
|
||||
|
||||
// TODO: move this to an appropriate location and implement the policy scheme that should be used for the new management APIs
|
||||
private static void CreatePolicies(AuthorizationOptions options)
|
||||
@@ -186,3 +139,10 @@ public static class BackOfficeAuthBuilderExtensions
|
||||
AddPolicy(AuthorizationPolicies.SectionAccessContentOrMedia, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Content, Constants.Applications.Media);
|
||||
}
|
||||
}
|
||||
|
||||
internal class BackofficePipelineFilter : UmbracoPipelineFilter
|
||||
{
|
||||
public BackofficePipelineFilter(string name)
|
||||
: base(name)
|
||||
=> PrePipeline = builder => builder.UseMiddleware<BackOfficeAuthorizationInitializationMiddleware>();
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ using Umbraco.Cms.Api.Management.Serialization;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models.Configuration;
|
||||
using Umbraco.Cms.Web.Common.ApplicationBuilder;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management;
|
||||
|
||||
@@ -21,21 +22,20 @@ public class ManagementApiComposer : IComposer
|
||||
|
||||
IServiceCollection services = builder.Services;
|
||||
|
||||
builder
|
||||
.AddJson()
|
||||
.AddNewInstaller()
|
||||
.AddUpgrader()
|
||||
.AddSearchManagement()
|
||||
.AddTrees()
|
||||
.AddAuditLogs()
|
||||
.AddDocuments()
|
||||
.AddDocumentTypes()
|
||||
.AddMedia()
|
||||
.AddMediaTypes()
|
||||
.AddLanguages()
|
||||
.AddDictionary()
|
||||
.AddHealthChecks()
|
||||
.AddModelsBuilder()
|
||||
ModelsBuilderBuilderExtensions.AddModelsBuilder(builder
|
||||
.AddJson()
|
||||
.AddNewInstaller()
|
||||
.AddUpgrader()
|
||||
.AddSearchManagement()
|
||||
.AddTrees()
|
||||
.AddAuditLogs()
|
||||
.AddDocuments()
|
||||
.AddDocumentTypes()
|
||||
.AddMedia()
|
||||
.AddMediaTypes()
|
||||
.AddLanguages()
|
||||
.AddDictionary()
|
||||
.AddHealthChecks())
|
||||
.AddRedirectUrl()
|
||||
.AddTags()
|
||||
.AddTrackedReferences()
|
||||
@@ -80,6 +80,8 @@ public class ManagementApiComposer : IComposer
|
||||
builder.AddUmbracoOptions<NewBackOfficeSettings>();
|
||||
// FIXME: remove this when NewBackOfficeSettings is moved to core
|
||||
services.AddSingleton<IValidateOptions<NewBackOfficeSettings>, NewBackOfficeSettingsValidator>();
|
||||
|
||||
BackOfficeAuthBuilderOpenIddictExtensions.AddUmbracoEFCoreDbContext(builder);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Extensions;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Routing;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Infrastructure.Security;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Middleware;
|
||||
@@ -13,11 +15,16 @@ public class BackOfficeAuthorizationInitializationMiddleware : IMiddleware
|
||||
|
||||
private readonly UmbracoRequestPaths _umbracoRequestPaths;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly IRuntimeState _runtimeState;
|
||||
|
||||
public BackOfficeAuthorizationInitializationMiddleware(UmbracoRequestPaths umbracoRequestPaths, IServiceProvider serviceProvider)
|
||||
public BackOfficeAuthorizationInitializationMiddleware(
|
||||
UmbracoRequestPaths umbracoRequestPaths,
|
||||
IServiceProvider serviceProvider,
|
||||
IRuntimeState runtimeState)
|
||||
{
|
||||
_umbracoRequestPaths = umbracoRequestPaths;
|
||||
_serviceProvider = serviceProvider;
|
||||
_runtimeState = runtimeState;
|
||||
}
|
||||
|
||||
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
|
||||
@@ -33,6 +40,11 @@ public class BackOfficeAuthorizationInitializationMiddleware : IMiddleware
|
||||
return;
|
||||
}
|
||||
|
||||
if (_runtimeState.Level < RuntimeLevel.Run)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_umbracoRequestPaths.IsBackOfficeRequest(context.Request.Path) == false)
|
||||
{
|
||||
return;
|
||||
@@ -50,13 +62,3 @@ public class BackOfficeAuthorizationInitializationMiddleware : IMiddleware
|
||||
_firstBackOfficeRequestLocker.Release();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: remove this (used for testing BackOfficeAuthorizationInitializationMiddleware until it can be added to the existing UseBackOffice extension)
|
||||
// public static class UmbracoApplicationBuilderExtensions
|
||||
// {
|
||||
// public static IUmbracoApplicationBuilderContext UseNewBackOffice(this IUmbracoApplicationBuilderContext builder)
|
||||
// {
|
||||
// builder.AppBuilder.UseMiddleware<BackOfficeAuthorizationInitializationMiddleware>();
|
||||
// return builder;
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -4,6 +4,7 @@ using Microsoft.Extensions.Options;
|
||||
using OpenIddict.Abstractions;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models.Configuration;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Infrastructure.Security;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Security;
|
||||
@@ -12,22 +13,30 @@ public class BackOfficeApplicationManager : IBackOfficeApplicationManager
|
||||
{
|
||||
private readonly IOpenIddictApplicationManager _applicationManager;
|
||||
private readonly IWebHostEnvironment _webHostEnvironment;
|
||||
private readonly IRuntimeState _runtimeState;
|
||||
private readonly Uri? _backOfficeHost;
|
||||
private readonly string? _authorizeCallbackPathName;
|
||||
|
||||
public BackOfficeApplicationManager(
|
||||
IOpenIddictApplicationManager applicationManager,
|
||||
IWebHostEnvironment webHostEnvironment,
|
||||
IOptions<NewBackOfficeSettings> securitySettings)
|
||||
IOptions<NewBackOfficeSettings> securitySettings,
|
||||
IRuntimeState runtimeState)
|
||||
{
|
||||
_applicationManager = applicationManager;
|
||||
_webHostEnvironment = webHostEnvironment;
|
||||
_runtimeState = runtimeState;
|
||||
_backOfficeHost = securitySettings.Value.BackOfficeHost;
|
||||
_authorizeCallbackPathName = securitySettings.Value.AuthorizeCallbackPathName;
|
||||
}
|
||||
|
||||
public async Task EnsureBackOfficeApplicationAsync(Uri backOfficeUrl, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_runtimeState.Level < RuntimeLevel.Run)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (backOfficeUrl.IsAbsoluteUri is false)
|
||||
{
|
||||
throw new ArgumentException($"Expected an absolute URL, got: {backOfficeUrl}", nameof(backOfficeUrl));
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="JsonPatch.Net" Version="2.0.4" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="7.0.2" />
|
||||
<PackageReference Include="OpenIddict.EntityFrameworkCore" Version="4.5.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using OpenIddict.Abstractions;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
|
||||
namespace Umbraco.Cms.Infrastructure.HostedServices;
|
||||
|
||||
@@ -14,17 +16,24 @@ public class OpenIddictCleanup : RecurringHostedServiceBase
|
||||
|
||||
private readonly ILogger<OpenIddictCleanup> _logger;
|
||||
private readonly IServiceProvider _provider;
|
||||
private readonly IRuntimeState _runtimeState;
|
||||
|
||||
public OpenIddictCleanup(
|
||||
ILogger<OpenIddictCleanup> logger, IServiceProvider provider)
|
||||
ILogger<OpenIddictCleanup> logger, IServiceProvider provider, IRuntimeState runtimeState)
|
||||
: base(logger, TimeSpan.FromHours(1), TimeSpan.FromMinutes(5))
|
||||
{
|
||||
_logger = logger;
|
||||
_provider = provider;
|
||||
_runtimeState = runtimeState;
|
||||
}
|
||||
|
||||
public override async Task PerformExecuteAsync(object? state)
|
||||
{
|
||||
if (_runtimeState.Level < RuntimeLevel.Run)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// hosted services are registered as singletons, but this particular one consumes scoped services... so
|
||||
// we have to fetch the service dependencies manually using a new scope per invocation.
|
||||
IServiceScope scope = _provider.CreateScope();
|
||||
|
||||
Submodule src/Umbraco.Web.UI.New.Client updated: b422069597...90f9c709cb
@@ -7,7 +7,9 @@ using Umbraco.Cms.Api.Management;
|
||||
using Umbraco.Cms.Api.Management.Controllers.Install;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Hosting;
|
||||
using Umbraco.Cms.Persistence.EFCore.Composition;
|
||||
using Umbraco.Cms.Tests.Integration.TestServerTest;
|
||||
using Umbraco.Cms.Web.Common.ApplicationBuilder;
|
||||
|
||||
namespace Umbraco.Cms.Tests.Integration.NewBackoffice;
|
||||
|
||||
@@ -27,6 +29,17 @@ internal sealed class OpenAPIContractTest : UmbracoTestServerTestBase
|
||||
});
|
||||
|
||||
new ManagementApiComposer().Compose(builder);
|
||||
new UmbracoEFCoreComposer().Compose(builder);
|
||||
|
||||
// Currently we cannot do this in tests, as EF Core is not initialized
|
||||
builder.Services.PostConfigure<UmbracoPipelineOptions>(options =>
|
||||
{
|
||||
var backofficePipelineFilter = options.PipelineFilters.FirstOrDefault(x => x.Name.Equals("Backoffice"));
|
||||
if (backofficePipelineFilter != null)
|
||||
{
|
||||
options.PipelineFilters.Remove(backofficePipelineFilter);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
Reference in New Issue
Block a user