Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

V14: Fix FIXMEs #15869

Merged
merged 8 commits into from Mar 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -2,6 +2,7 @@
using Asp.Versioning;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
Expand All @@ -15,6 +16,7 @@
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Web.Common.Authorization;
using Umbraco.Cms.Web.Common.Security;
using Umbraco.Extensions;
using IdentitySignInResult = Microsoft.AspNetCore.Identity.SignInResult;
Expand Down Expand Up @@ -49,9 +51,9 @@ public class BackOfficeController : SecurityControllerBase

// FIXME: this is a temporary solution to get the new backoffice auth rolling.
// once the old backoffice auth is no longer necessary, clean this up and merge with 2FA handling etc.
// [AllowAnonymous] // This is handled implicitly by the NewDenyLocalLoginIfConfigured policy on the <see cref="SecurityControllerBase" />. Keep it here for now and check FIXME in <see cref="DenyLocalLoginHandler" />.
[HttpPost("login")]
[MapToApiVersion("1.0")]
[Authorize(Policy = AuthorizationPolicies.DenyLocalLoginIfConfigured)]
public async Task<IActionResult> Login(LoginRequestModel model)
{
var validated = await _backOfficeUserManager.ValidateCredentialsAsync(model.Username, model.Password);
Expand Down Expand Up @@ -87,6 +89,7 @@ public async Task<IActionResult> Login(LoginRequestModel model)
return Ok();
}

[AllowAnonymous]
[HttpPost("verify-2fa")]
[MapToApiVersion("1.0")]
public async Task<IActionResult> Verify2FACode(Verify2FACodeModel model)
Expand Down Expand Up @@ -137,7 +140,7 @@ public class LoginRequestModel
public required string Password { get; init; }
}

// [AllowAnonymous] // This is handled implicitly by the NewDenyLocalLoginIfConfigured policy on the <see cref="SecurityControllerBase" />. Keep it here for now and check FIXME in <see cref="DenyLocalLoginHandler" />.
[AllowAnonymous]
[HttpGet("authorize")]
[MapToApiVersion("1.0")]
public async Task<IActionResult> Authorize()
Expand All @@ -160,6 +163,7 @@ public async Task<IActionResult> Authorize()
: await AuthorizeExternal(request);
}

[AllowAnonymous]
[HttpGet("signout")]
[MapToApiVersion("1.0")]
public async Task<IActionResult> Signout()
Expand Down
@@ -1,15 +1,12 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Api.Management.ViewModels.Security;
using Umbraco.Cms.Web.Common.Authorization;

namespace Umbraco.Cms.Api.Management.Controllers.Security;

[ApiVersion("1.0")]
[Authorize(Policy = AuthorizationPolicies.DenyLocalLoginIfConfigured)]
// FIXME: Add requiring password reset token policy when its implemented
public class ConfigurationSecurityController : SecurityControllerBase
{
Expand Down
Expand Up @@ -7,11 +7,13 @@
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
using Umbraco.Cms.Web.Common.Authorization;

namespace Umbraco.Cms.Api.Management.Controllers.Security;

[ApiVersion("1.0")]
// FIXME: Add requiring password reset token policy when its implemented
[Authorize(Policy = AuthorizationPolicies.DenyLocalLoginIfConfigured)]
public class ResetPasswordController : SecurityControllerBase
{
private readonly IUserService _userService;
Expand All @@ -21,7 +23,6 @@ public class ResetPasswordController : SecurityControllerBase

[HttpPost("forgot-password")]
[MapToApiVersion("1.0")]
[AllowAnonymous] // This is handled implicitly by the NewDenyLocalLoginIfConfigured policy on the <see cref="SecurityControllerBase" />. Keep it here for now and check FIXME in <see cref="DenyLocalLoginHandler" />.
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[UserPasswordEnsureMinimumResponseTime]
Expand Down
@@ -1,4 +1,5 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Common.Builders;
Expand All @@ -8,10 +9,12 @@
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
using Umbraco.Cms.Web.Common.Authorization;

namespace Umbraco.Cms.Api.Management.Controllers.Security;

[ApiVersion("1.0")]
[Authorize(Policy = AuthorizationPolicies.DenyLocalLoginIfConfigured)]
public class ResetPasswordTokenController : SecurityControllerBase
{
private readonly IUserService _userService;
Expand All @@ -20,7 +23,6 @@ public class ResetPasswordTokenController : SecurityControllerBase

[HttpPost("forgot-password/reset")]
[MapToApiVersion("1.0")]
// [AllowAnonymous] // This is handled implicitly by the NewDenyLocalLoginIfConfigured policy on the <see cref="SecurityControllerBase" />. Keep it here for now and check FIXME in <see cref="DenyLocalLoginHandler" />.
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(typeof(ProblemDetailsBuilder), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ProblemDetailsBuilder), StatusCodes.Status404NotFound)]
Expand Down
@@ -1,16 +1,13 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Routing;
using Umbraco.Cms.Core.Models.Membership;
using Umbraco.Cms.Core.Services.OperationStatus;
using Umbraco.Cms.Web.Common.Authorization;

namespace Umbraco.Cms.Api.Management.Controllers.Security;

[VersionedApiBackOfficeRoute("security")]
[ApiExplorerSettings(GroupName = "Security")]
[Authorize(Policy = AuthorizationPolicies.DenyLocalLoginIfConfigured)]
public abstract class SecurityControllerBase : ManagementApiControllerBase
{
protected IActionResult UserOperationStatusResult(UserOperationStatus status, ErrorMessageResult? errorMessageResult = null) =>
Expand Down
@@ -1,4 +1,5 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Common.Builders;
Expand All @@ -17,9 +18,9 @@ public class VerifyResetPasswordTokenController : SecurityControllerBase

public VerifyResetPasswordTokenController(IUserService userService) => _userService = userService;

[AllowAnonymous]
[HttpPost("forgot-password/verify")]
[MapToApiVersion("1.0")]
// [AllowAnonymous] // This is handled implicitly by the NewDenyLocalLoginIfConfigured policy on the <see cref="SecurityControllerBase" />. Keep it here for now and check FIXME in <see cref="DenyLocalLoginHandler" />.
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(typeof(ProblemDetailsBuilder), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ProblemDetailsBuilder), StatusCodes.Status404NotFound)]
Expand Down
Expand Up @@ -19,7 +19,6 @@ public class CreateInitialPasswordUserController : UserControllerBase

public CreateInitialPasswordUserController(IUserService userService) => _userService = userService;

// [AllowAnonymous] // This is handled implicitly by the NewDenyLocalLoginIfConfigured policy on the <see cref="SecurityControllerBase" />. Keep it here for now and check FIXME in <see cref="DenyLocalLoginHandler" />.
[HttpPost("invite/create-password")]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status200OK)]
Expand Down
Expand Up @@ -6,19 +6,17 @@
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
using Umbraco.Cms.Web.Common.Authorization;

namespace Umbraco.Cms.Api.Management.Controllers.User;

[ApiVersion("1.0")]
[Authorize(Policy = AuthorizationPolicies.DenyLocalLoginIfConfigured)]
public class VerifyInviteUserController : UserControllerBase
{
private readonly IUserService _userService;

public VerifyInviteUserController(IUserService userService) => _userService = userService;

// [AllowAnonymous] // This is handled implicitly by the NewDenyLocalLoginIfConfigured policy. Keep it here for now and check FIXME in <see cref="DenyLocalLoginHandler" />.
[AllowAnonymous]
[HttpPost("invite/verify")]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status200OK)]
Expand Down
Expand Up @@ -9,32 +9,25 @@ namespace Umbraco.Cms.Api.Management.Security.Authorization.DenyLocalLogin;
/// </summary>
public class DenyLocalLoginHandler : MustSatisfyRequirementAuthorizationHandler<DenyLocalLoginRequirement>
{
// private readonly IBackOfficeExternalLoginProviders _externalLogins;
//
// /// <summary>
// /// Initializes a new instance of the <see cref="DenyLocalLoginHandler" /> class.
// /// </summary>
// /// <param name="externalLogins">Provides access to <see cref="BackOfficeExternalLoginProvider" /> instances.</param>
// public DenyLocalLoginHandler(IBackOfficeExternalLoginProviders externalLogins)
// => _externalLogins = externalLogins;
//
// /// <inheritdoc />
// protected override Task<bool> IsAuthorized(AuthorizationHandlerContext context, DenyLocalLoginRequirement requirement)
// => Task.FromResult(!_externalLogins.HasDenyLocalLogin());
private readonly IBackOfficeExternalLoginProviders _externalLogins;

/// <summary>
/// Initializes a new instance of the <see cref="DenyLocalLoginHandler" /> class.
/// </summary>
/// <param name="externalLogins">Provides access to <see cref="BackOfficeExternalLoginProvider" /> instances.</param>
public DenyLocalLoginHandler(IBackOfficeExternalLoginProviders externalLogins)
=> _externalLogins = externalLogins;

// FIXME: Replace the value of isDenied with above implementation, once we have IBackOfficeExternalLoginProviders and related classes
// moved from Umbraco.Web.Backoffice
// FIXME: Remove [AllowAnonymous] from implementers of <see cref="SecurityControllerBase" /> and in <see cref="VerifyInviteUserController" /> when we have the proper implementation
protected override Task<bool> IsAuthorized(AuthorizationHandlerContext context, DenyLocalLoginRequirement requirement)
{
// Some logic here - for now we will always authorize successfully
var isDenied = false;
var isDenied = _externalLogins.HasDenyLocalLogin();

if (isDenied is false)
{
// Now allow anonymous (RequireAuthenticatedUser() adds this requirement) - necessary for some of the endpoints (BackOfficeController.Login())
var denyAnonymousUserRequirements = context.PendingRequirements.OfType<DenyAnonymousAuthorizationRequirement>();
foreach (var denyAnonymousUserRequirement in denyAnonymousUserRequirements)
// AuthorizationPolicies.BackOfficeAccess policy adds this requirement by policy.RequireAuthenticatedUser()
// Since we want to "allow anonymous" for some endpoints (i.e. BackOfficeController.Login()), it is necessary to succeed this requirement
IEnumerable<DenyAnonymousAuthorizationRequirement> denyAnonymousUserRequirements = context.PendingRequirements.OfType<DenyAnonymousAuthorizationRequirement>();
foreach (DenyAnonymousAuthorizationRequirement denyAnonymousUserRequirement in denyAnonymousUserRequirements)
{
context.Succeed(denyAnonymousUserRequirement);
}
Expand Down
3 changes: 1 addition & 2 deletions src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs
Expand Up @@ -382,12 +382,11 @@ private void AddCoreServices()
// Authorizers
Services.AddSingleton<IAuthorizationHelper, AuthorizationHelper>();
Services.AddSingleton<IContentPermissionAuthorizer, ContentPermissionAuthorizer>();
Services.AddSingleton<IDictionaryPermissionAuthorizer, DictionaryPermissionAuthorizer>();
Services.AddSingleton<IFeatureAuthorizer, FeatureAuthorizer>();
Services.AddSingleton<IMediaPermissionAuthorizer, MediaPermissionAuthorizer>();
Services.AddSingleton<IUserGroupPermissionAuthorizer, UserGroupPermissionAuthorizer>();
Services.AddSingleton<IUserPermissionAuthorizer, UserPermissionAuthorizer>();
Services.AddSingleton<IDictionaryPermissionAuthorizer, DictionaryPermissionAuthorizer>();

}
}
}