169 lines
7.7 KiB
C#
169 lines
7.7 KiB
C#
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.Hosting;
|
|
using OpenIddict.Validation.AspNetCore;
|
|
using Umbraco.Cms.Core;
|
|
using Umbraco.Cms.Core.DependencyInjection;
|
|
using Umbraco.Cms.Api.Management.Middleware;
|
|
using Umbraco.Cms.Api.Management.Security;
|
|
using Umbraco.Cms.Web.Common.Authorization;
|
|
using Umbraco.New.Cms.Infrastructure.HostedServices;
|
|
using Umbraco.New.Cms.Infrastructure.Security;
|
|
|
|
namespace Umbraco.Cms.Api.Management.DependencyInjection;
|
|
|
|
public static class BackOfficeAuthBuilderExtensions
|
|
{
|
|
public static IUmbracoBuilder AddBackOfficeAuthentication(this IUmbracoBuilder builder)
|
|
{
|
|
builder
|
|
.AddDbContext()
|
|
.AddOpenIddict();
|
|
|
|
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 =>
|
|
{
|
|
// Enable the authorization and token endpoints.
|
|
options
|
|
.SetAuthorizationEndpointUris(Controllers.Security.Paths.BackOfficeApiAuthorizationEndpoint)
|
|
.SetTokenEndpointUris(Controllers.Security.Paths.BackOfficeApiTokenEndpoint);
|
|
|
|
// Enable authorization code flow with PKCE
|
|
options
|
|
.AllowAuthorizationCodeFlow()
|
|
.RequireProofKeyForCodeExchange()
|
|
.AllowRefreshTokenFlow();
|
|
|
|
// Register the encryption and signing credentials.
|
|
// - see https://documentation.openiddict.com/configuration/encryption-and-signing-credentials.html
|
|
options
|
|
// TODO: use actual certificates here, see docs above
|
|
.AddDevelopmentEncryptionCertificate()
|
|
.AddDevelopmentSigningCertificate()
|
|
.DisableAccessTokenEncryption();
|
|
|
|
// Register the ASP.NET Core host and configure for custom authentication endpoint.
|
|
options
|
|
.UseAspNetCore()
|
|
.EnableAuthorizationEndpointPassthrough();
|
|
|
|
// Enable reference tokens
|
|
// - see https://documentation.openiddict.com/configuration/token-storage.html
|
|
options
|
|
.UseReferenceAccessTokens()
|
|
.UseReferenceRefreshTokens();
|
|
|
|
// Use ASP.NET Core Data Protection for tokens instead of JWT.
|
|
// This is more secure, and has the added benefit of having a high throughput
|
|
// but means that all servers (such as in a load balanced setup)
|
|
// needs to use the same application name and key ring,
|
|
// however this is already recommended for load balancing, so should be fine.
|
|
// See https://documentation.openiddict.com/configuration/token-formats.html#switching-to-data-protection-tokens
|
|
// and https://learn.microsoft.com/en-us/aspnet/core/security/data-protection/configuration/overview?view=aspnetcore-7.0
|
|
// for more information
|
|
options.UseDataProtection();
|
|
})
|
|
|
|
// Register the OpenIddict validation components.
|
|
.AddValidation(options =>
|
|
{
|
|
// Import the configuration from the local OpenIddict server instance.
|
|
options.UseLocalServer();
|
|
|
|
// Register the ASP.NET Core host.
|
|
options.UseAspNetCore();
|
|
|
|
// Enable token entry validation
|
|
// - see https://documentation.openiddict.com/configuration/token-storage.html#enabling-token-entry-validation-at-the-api-level
|
|
options.EnableTokenEntryValidation();
|
|
|
|
// Use ASP.NET Core Data Protection for tokens instead of JWT. (see note in AddServer)
|
|
options.UseDataProtection();
|
|
});
|
|
|
|
builder.Services.AddTransient<IBackOfficeApplicationManager, BackOfficeApplicationManager>();
|
|
builder.Services.AddSingleton<BackOfficeAuthorizationInitializationMiddleware>();
|
|
|
|
builder.Services.AddHostedService<OpenIddictCleanup>();
|
|
builder.Services.AddHostedService<DatabaseManager>();
|
|
|
|
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:44331/"), 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)
|
|
{
|
|
void AddPolicy(string policyName, string claimType, params string[] allowedClaimValues)
|
|
{
|
|
options.AddPolicy($"New{policyName}", policy =>
|
|
{
|
|
policy.AuthenticationSchemes.Add(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme);
|
|
policy.RequireClaim(claimType, allowedClaimValues);
|
|
});
|
|
}
|
|
|
|
// NOTE: these are ONLY sample policies that allow us to test the new management APIs
|
|
AddPolicy(AuthorizationPolicies.SectionAccessContent, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Content);
|
|
AddPolicy(AuthorizationPolicies.SectionAccessForContentTree, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Content);
|
|
AddPolicy(AuthorizationPolicies.SectionAccessForMediaTree, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Media);
|
|
AddPolicy(AuthorizationPolicies.SectionAccessMedia, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Media);
|
|
AddPolicy(AuthorizationPolicies.SectionAccessContentOrMedia, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Content, Constants.Applications.Media);
|
|
}
|
|
}
|