Skip to content

ContextualAuthorize Attribute#43

Open
Franck-Boost wants to merge 9 commits intov15/devfrom
v14/feature/contextual-permission-authorize-attribute
Open

ContextualAuthorize Attribute#43
Franck-Boost wants to merge 9 commits intov15/devfrom
v14/feature/contextual-permission-authorize-attribute

Conversation

@Franck-Boost
Copy link

Description

With the addition of IGranularPermission we paved the way for a more flexible permission system that package developers could use to store their package permissions in if they want to instead of creating their own CRUD mechanics

This PR adds a generic policy with matching requirement and resource, plus an authorizer that can be replaced to use these IGranularPermission in a non granular way. The granularity (permission for a specific entity with a given key) is left out in this case as that logic is usually specific to the context of the permission.

An attribute is also added to quickly make use of this system. See LINK HERE for a tutorial.

Testing

Unit tests have been added to cover a variety of permutations regarding the resource and the decision logic.

To manually test, one could setup a controller similar to the one supplied below. Then add/remove/alter Granular permissions on a usergroup and try to call the controller method with a user belonging to said group.

Example controller

using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using Umbraco.Cms.Api.Common.Attributes;
using Umbraco.Cms.Api.Common.Filters;
using Umbraco.Cms.Api.Management.Controllers;
using Umbraco.Cms.Api.Management.OpenApi;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Core.Models.Membership.Permissions;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Web.Common.Authorization;
using Umbraco.Cms.Api.Management.Security.Authorization.Contextual;

namespace Umbraco.Cms.Web.UI;

[ApiController]
[ApiVersion("1.0")]
[MapToApi("my-api-v1")]
[Authorize(Policy = AuthorizationPolicies.BackOfficeAccess)]
[JsonOptionsName(Constants.JsonOptionsNames.BackOffice)]
[Route("api/v{version:apiVersion}/my")]
public class ContextualAuthorizedController : ManagementApiControllerBase
{
    private readonly IUserGroupService _userGroupService;
    private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;

    public ContextualAuthorizedController(IUserGroupService userGroupService, IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
    {
        _userGroupService = userGroupService;
        _backOfficeSecurityAccessor = backOfficeSecurityAccessor;
    }

    [ContextualAuthorize(PackageConstants.MyPermissionVerb, PackageConstants.MyPackageContextIdentifier)]
    [MapToApiVersion("1.0")]
    [HttpGet]
    public IActionResult ProtectedAction()
    {
        return Ok("success value");
    }

    [ContextualAuthorize(PackageConstants.MyPermissionVerb+"nonsense", PackageConstants.MyPackageContextIdentifier)]
    [MapToApiVersion("1.0")]
    [HttpGet("/other")]
    public IActionResult InvalidProtectedAction()
    {
        return Ok("success value");
    }

    [MapToApiVersion("1.0")]
    [HttpPost]
    public async Task<IActionResult> SetValidPermissionOnAdminUserGroup()
    {
        var admins = await _userGroupService.GetAsync("admin");
        admins!.GranularPermissions.Add(new UnknownTypeGranularPermission
        {
            Context = PackageConstants.MyPackageContextIdentifier, Permission = PackageConstants.MyPermissionVerb
        });
        await _userGroupService.UpdateAsync(admins, _backOfficeSecurityAccessor.BackOfficeSecurity!.CurrentUser!.Key);
        return Ok();
    }
}

public class PackageConstants
{
    public const string MyPackageContextIdentifier = "MyPackageContextIdentifier";
    public const string MyPermissionVerb = "MyPermissionVerb";
}

// Included as setup, but is already covered in the docs
public class MyBackOfficeSecurityRequirementsOperationFilter : BackOfficeSecurityRequirementsOperationFilterBase
{
    protected override string ApiName => "my-api-v1";
}

public class MyConfigureSwaggerGenOptions : IConfigureOptions<SwaggerGenOptions>
{
    public void Configure(SwaggerGenOptions options)
    {
        options.SwaggerDoc("my-api-v1", new OpenApiInfo { Title = "My API v1", Version = "1.0" });
        options.OperationFilter<MyBackOfficeSecurityRequirementsOperationFilter>();
    }
}

public class MySwaggerComposer : IComposer
{
    public void Compose(IUmbracoBuilder builder)
        => builder.Services.ConfigureOptions<MyConfigureSwaggerGenOptions>();
}

Todo post merge

  • Turn controller into a documentation PR (tutorial format)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants