-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
OpenId Connect authentication for new management API (#13318)
* First attempt at OpenIddict * Making headway and more TODOs * Redo current policies for multiple schemas + clean up auth controller * Fix bad merge * Clean up some more test code * Fix spacing * Include AddAuthentication() in OpenIddict addition * A little more clean-up * Move application creation to its own implementation + prepare for middleware to handle valid callback URL * Enable refresh token flow * Fix bad merge from v11/dev * Support auth for Swagger and Postman in non-production environments + use default login screen for back-office logins * Add workaround to client side login handling so the OAuth return URL is not corrupted before redirection * Add temporary configuration handling for new backoffice * Restructure the code somewhat, move singular responsibility from management API project * Add recurring task for cleaning up old tokens in the DB * Fix bad merge + make auth controller align with the new management API structure * Explicitly handle the new management API path as a backoffice path (NOTE: this is potentially behaviorally breaking!) * Redo handle the new management API requests as backoffice requests, this time in a non-breaking way * Add/update TODOs * Revert duplication of current auth policies for OpenIddict (as it breaks everything for V11 without the new management APIs) and introduce a dedicated PoC policy setup for OpenIddict. * Fix failing unit tests * Update src/Umbraco.Cms.ManagementApi/Security/BackOfficeApplicationManager.cs Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> * Update src/Umbraco.Cms.ManagementApi/Security/BackOfficeApplicationManager.cs Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> * Update src/Umbraco.Cms.ManagementApi/Security/BackOfficeApplicationManager.cs Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> * Update src/Umbraco.Core/Routing/UmbracoRequestPaths.cs Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com>
- Loading branch information
Showing
25 changed files
with
763 additions
and
17 deletions.
There are no files selected for viewing
77 changes: 77 additions & 0 deletions
77
src/Umbraco.Cms.ManagementApi/Controllers/Security/BackOfficeController.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
using System.Security.Claims; | ||
using Microsoft.AspNetCore; | ||
using Microsoft.AspNetCore.Authentication; | ||
using Microsoft.AspNetCore.Http; | ||
using Microsoft.AspNetCore.Mvc; | ||
using NSwag.Annotations; | ||
using OpenIddict.Abstractions; | ||
using OpenIddict.Server.AspNetCore; | ||
using Umbraco.Cms.Core; | ||
using Umbraco.Cms.Core.Security; | ||
using Umbraco.Cms.Web.BackOffice.Security; | ||
using Umbraco.Extensions; | ||
using Umbraco.New.Cms.Web.Common.Routing; | ||
|
||
namespace Umbraco.Cms.ManagementApi.Controllers.Security; | ||
|
||
[ApiController] | ||
[VersionedApiBackOfficeRoute(Paths.BackOfficeApiEndpointTemplate)] | ||
[OpenApiTag("Security")] | ||
public class BackOfficeController : ManagementApiControllerBase | ||
{ | ||
private readonly IHttpContextAccessor _httpContextAccessor; | ||
private readonly IBackOfficeSignInManager _backOfficeSignInManager; | ||
private readonly IBackOfficeUserManager _backOfficeUserManager; | ||
|
||
public BackOfficeController(IHttpContextAccessor httpContextAccessor, IBackOfficeSignInManager backOfficeSignInManager, IBackOfficeUserManager backOfficeUserManager) | ||
{ | ||
_httpContextAccessor = httpContextAccessor; | ||
_backOfficeSignInManager = backOfficeSignInManager; | ||
_backOfficeUserManager = backOfficeUserManager; | ||
} | ||
|
||
[HttpGet("authorize")] | ||
[HttpPost("authorize")] | ||
[MapToApiVersion("1.0")] | ||
public async Task<IActionResult> Authorize() | ||
{ | ||
HttpContext context = _httpContextAccessor.GetRequiredHttpContext(); | ||
OpenIddictRequest? request = context.GetOpenIddictServerRequest(); | ||
if (request == null) | ||
{ | ||
return BadRequest("Unable to obtain OpenID data from the current request"); | ||
} | ||
|
||
// retrieve the user principal stored in the authentication cookie. | ||
AuthenticateResult cookieAuthResult = await HttpContext.AuthenticateAsync(Constants.Security.BackOfficeAuthenticationType); | ||
if (cookieAuthResult.Succeeded && cookieAuthResult.Principal?.Identity?.Name != null) | ||
{ | ||
BackOfficeIdentityUser? backOfficeUser = await _backOfficeUserManager.FindByNameAsync(cookieAuthResult.Principal.Identity.Name); | ||
if (backOfficeUser != null) | ||
{ | ||
ClaimsPrincipal backOfficePrincipal = await _backOfficeSignInManager.CreateUserPrincipalAsync(backOfficeUser); | ||
backOfficePrincipal.SetClaim(OpenIddictConstants.Claims.Subject, backOfficeUser.Key.ToString()); | ||
|
||
// TODO: it is not optimal to append all claims to the token. | ||
// the token size grows with each claim, although it is still smaller than the old cookie. | ||
// see if we can find a better way so we do not risk leaking sensitive data in bearer tokens. | ||
// maybe work with scopes instead? | ||
Claim[] backOfficeClaims = backOfficePrincipal.Claims.ToArray(); | ||
foreach (Claim backOfficeClaim in backOfficeClaims) | ||
{ | ||
backOfficeClaim.SetDestinations(OpenIddictConstants.Destinations.AccessToken); | ||
} | ||
|
||
if (request.GetScopes().Contains(OpenIddictConstants.Scopes.OfflineAccess)) | ||
{ | ||
// "offline_access" scope is required to use refresh tokens | ||
backOfficePrincipal.SetScopes(OpenIddictConstants.Scopes.OfflineAccess); | ||
} | ||
|
||
return new SignInResult(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, backOfficePrincipal); | ||
} | ||
} | ||
|
||
return new ChallengeResult(new[] { Constants.Security.BackOfficeAuthenticationType }); | ||
} | ||
} |
12 changes: 12 additions & 0 deletions
12
src/Umbraco.Cms.ManagementApi/Controllers/Security/Paths.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
namespace Umbraco.Cms.ManagementApi.Controllers.Security; | ||
|
||
public static class Paths | ||
{ | ||
public const string BackOfficeApiEndpointTemplate = "security/back-office"; | ||
|
||
public static string BackOfficeApiAuthorizationEndpoint = BackOfficeApiEndpointPath($"{BackOfficeApiEndpointTemplate}/authorize"); | ||
|
||
public static string BackOfficeApiTokenEndpoint = BackOfficeApiEndpointPath($"{BackOfficeApiEndpointTemplate}/token"); | ||
|
||
private static string BackOfficeApiEndpointPath(string relativePath) => $"/umbraco/management/api/v1.0/{relativePath}"; | ||
} |
146 changes: 146 additions & 0 deletions
146
src/Umbraco.Cms.ManagementApi/DependencyInjection/BackOfficeAuthBuilderExtensions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
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.ManagementApi.Middleware; | ||
using Umbraco.Cms.ManagementApi.Security; | ||
using Umbraco.Cms.Web.Common.Authorization; | ||
using Umbraco.New.Cms.Infrastructure.HostedServices; | ||
using Umbraco.New.Cms.Infrastructure.Security; | ||
|
||
namespace Umbraco.Cms.ManagementApi.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(); | ||
}) | ||
|
||
// 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(); | ||
}); | ||
|
||
builder.Services.AddTransient<IBackOfficeApplicationManager, BackOfficeApplicationManager>(); | ||
builder.Services.AddSingleton<IClientSecretManager, ClientSecretManager>(); | ||
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.