Files
Umbraco-CMS/src/Umbraco.Cms.Api.Management/DependencyInjection/BackOfficeAuthBuilderExtensions.cs
Mole 73515560b7 New backoffice: Token storage (#13519)
* Enable reference tokens

* Enable token validation

* Add Data protection
2022-12-07 06:48:53 +01:00

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);
}
}