12

In the controller code below, only users who are in the "Administrator" role can access the GetData() action method, because of the controller-level AuthorizeAttribute. But I also want users who only are in "Manager" role to have access to the GetData() action method.

[Authorize(Roles = "Administrator")]
Public class AdminController : Controller
{
    [Authorize(Roles = "Administrator, Manager")]
    public IActionResult GetData()
    {
    }
}

Is there an option like OverrideAuthorization attribute available in .NET Core framework to achieve this requirement?

aochagavia
  • 5,887
  • 5
  • 34
  • 53
Pravin
  • 839
  • 7
  • 12

5 Answers5

6

Was able to find a solution after long time of analysis on the Authorization assemblies.

In the startup.cs file, add the Authorization as follows:

services.AddAuthorization(options =>
        {
            var roles = new List<string>{ Role.Administrator, Role.Manager};

            var requirement =
                new List<IAuthorizationRequirement> {new AdminManagerAuthorizationOverrideOthers(roles) };
            var sharedAuthentication =
                new AuthorizationPolicy(requirement,
                    new List<string>());
            options.AddPolicy(name: "AdminManager", policy: sharedAuthentication);
            options.AddPolicy(name: "Administrator", configurePolicy: policy => policy.RequireAssertion(e =>
            {
                if (e.Resource is AuthorizationFilterContext afc)
                {
                    var noPolicy = afc.Filters.OfType<AuthorizeFilter>().Any(p =>
                        p.Policy.Requirements.Count == 1 &&
                        p.Policy.Requirements.Single() is AdminManagerAuthorizationOverrideOthers);
                    if (noPolicy)
                        return true;
                }
                return e.User.IsInRole(Role.Administrator);
            }));

        });

Create a class in any namespace that Inherits "RolesAuthorizationRequirement" from "Microsoft.AspNetCore.Authorization.Infrastructure" namespace as follows:

public class AdminManagerAuthorizationOverrideOthers : RolesAuthorizationRequirement
{
    public AdminManagerAuthorizationOverrideOthers(IEnumerable<string> allowedRoles) : base(allowedRoles)
    {
    }
}

Then, decorate the controller and action method as follows:

[Authorize(Policy = "Administrator")]
Public class AdminController : Controller
{
    public IActionResult GetData()
    {
    }

    [Authorize(Policy = "AdminManager")]
    public IActionResult AdministratorOnly()
    {
    }
}
Pravin
  • 839
  • 7
  • 12
3

Ideally, you want to narrow down the restriction to Action Method, because in Controller Initialization step, it checks Controller's Authorize filter first before Action filters.

[Authorize(Roles = "Administrator, Manager")]
Public class AdminController : Controller
{
    public IActionResult GetData()
    {
    }

    [Authorize(Roles = "Administrator")]
    public IActionResult AdministratorOnly()
    {
    }
}
Win
  • 61,100
  • 13
  • 102
  • 181
  • 1
    Yes. But there should be some way to override the controller-level authorization in .NET core and that's the answer I'm looking for. In .NET 4.6, there is OverrideAuthorization attribute to do this. But .NET Core does not have this and I'm checking if there is a work-around. – Pravin Dec 01 '17 at 02:35
  • In order to do it in ASP.NET Core, check my answer. – Francisco Goldenstein Jul 11 '18 at 18:30
2

In ASP.NET Core 2.1 you can do it. Check this: https://learn.microsoft.com/en-us/aspnet/core/security/authorization/roles?view=aspnetcore-2.1

You can also lock down a controller but allow anonymous, unauthenticated access to individual actions.

[Authorize(Roles = "Admin,Employee")] // admin or employee
public class XController : Controller 
{
    [Authorize(Roles = "Admin")] // only admin
    public ActionResult ActionX() { ... }

    [AllowAnonymous] // anyone
    public ActionResult ActionX() { ... }
}
Francisco Goldenstein
  • 13,299
  • 7
  • 58
  • 74
  • 2
    Thank you. Just what I needed! – Daniel Congrove Jul 16 '18 at 17:34
  • 3
    This answer does not cover a possible replacement of the OverrideAuthorization attribute, as was mentioned in the question. It just covers disabling authorisation entirely for a specific method. – S. Baggy Aug 10 '20 at 06:51
1

All above is right, i just want to give a full example easy for all My case is Asp.Net Core 3.1

Startup.js (ConfigureServices):

            services.AddIdentity<ApplicationUser, IdentityRole>(config =>
            {
                config.User.RequireUniqueEmail = false;    // óíèêàëüíûé email
                config.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 -._@+"; 
                config.SignIn.RequireConfirmedEmail = false;
            })
                .AddEntityFrameworkStores<ApplicationDbContext>()
                .AddUserManager<UserManager<ApplicationUser>>()
                .AddRoleManager<RoleManager<IdentityRole>>()
                .AddDefaultTokenProviders();

            services.AddAuthorization(options =>
            {
                options.AddPolicy("User", policy => {
                    policy.RequireClaim("User");
                });
                options.AddPolicy("Admin", policy => {
                    policy.RequireRole("Admin");
                });
            });

services.AddScoped<IAuthorizationHandler, RolesAuthorizationHandler>();

Startup.js (Configure):

    app.UseAuthentication();
    app.UseAuthorization();

Controller:

[Authorize(Policy = "Admin")]
public class RoleController : Controller

Handler-Example:

 public class RolesAuthorizationHandler : AuthorizationHandler<RolesAuthorizationRequirement>, IAuthorizationHandler
    {

        private readonly RoleManager<IdentityRole> _roleManager;
        private readonly UserManager<ApplicationUser> _userManager;
        public RolesAuthorizationHandler(RoleManager<IdentityRole> roleManager, UserManager<ApplicationUser> userManager)
        {
            _roleManager = roleManager;
            _userManager = userManager;
        }

        protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
                                                       RolesAuthorizationRequirement requirement)
        {
            if (context.User == null || !context.User.Identity.IsAuthenticated)
            {
                context.Fail();
                return Task.CompletedTask;
            }

            var validRole = false;
            if (requirement.AllowedRoles == null ||
                requirement.AllowedRoles.Any() == false)
            {
                validRole = true;
            }
            else
            {
                var claims = context.User.Claims;
                //var userName = claims.FirstOrDefault(c => c.Type == "UserName").Value;
                var allowedRoles = requirement.AllowedRoles;

                var loggedInUserTask = _userManager.GetUserAsync(context.User);
                loggedInUserTask.Wait();
                var user = loggedInUserTask.Result;
                var roles = _userManager.GetRolesAsync(user);
                roles.Wait();
                var roleList = roles.Result;

                validRole = roleList.Where(p => allowedRoles.Contains(p.ToString())).Any();
            }

            if (validRole)
            {
                context.Succeed(requirement);
            }
            else
            {
                context.Fail();
            }
            return Task.CompletedTask;
        }
    }

I While updating a project that used to exist, I moved the old user table to the user table in the new identity database. Later, I defined roles at the table level for them, and with the RoleManager I wrote in this way, I left his next administration to the step. Quite successful. In my case, many people probably updated their old projects. However, I did not have such a post and wanted to share it. The following section is for them:

public class RoleAssignViewModel
    {
        public string RoleId { get; set; }
        public string RoleName { get; set; }
        public bool HasAssign { get; set; }
    }

    public class RoleViewModel
    {
        [Required(ErrorMessage = "Fill the role.")]
        [Display(Name = "Role Name")]
        public string Name { get; set; }
    }

    [Authorize(Policy = "Admin")]
    public class RoleController : Controller
    {
        private readonly RoleManager<IdentityRole> _roleManager;
        private readonly UserManager<ApplicationUser> _userManager;
        public RoleController(RoleManager<IdentityRole> roleManager, UserManager<ApplicationUser> userManager)
        {
            _roleManager = roleManager;
            _userManager = userManager;
        }
        public async Task<IActionResult> RoleAssign(string id)
        {
            ApplicationUser user = await _userManager.FindByIdAsync(id);
            List<IdentityRole> allRoles = _roleManager.Roles.ToList();
            List<string> userRoles = await _userManager.GetRolesAsync(user) as List<string>;
            List<RoleAssignViewModel> assignRoles = new List<RoleAssignViewModel>();
            allRoles.ForEach(role => assignRoles.Add(new RoleAssignViewModel
            {
                HasAssign = userRoles.Contains(role.Name),
                RoleId = role.Id,
                RoleName = role.Name
            }));

            return View(assignRoles);
        }
        [HttpPost]
        public async Task<ActionResult> RoleAssign(List<RoleAssignViewModel> modelList, string id)
        {
            ApplicationUser user = await _userManager.FindByIdAsync(id);
            foreach (RoleAssignViewModel role in modelList)
            {
                if (role.HasAssign)
                    await _userManager.AddToRoleAsync(user, role.RoleName);
                else
                    await _userManager.RemoveFromRoleAsync(user, role.RoleName);
            }
            return RedirectToAction("Index", "User");
        }
        public IActionResult RoleList()
        {
            return View(_roleManager.Roles.ToList());
        }
        public async Task<IActionResult> DeleteRole(string id)
        {
            IdentityRole role = await _roleManager.FindByIdAsync(id);
            IdentityResult result = await _roleManager.DeleteAsync(role);
            if (result.Succeeded)
            {
                //Başarılı...
            }
            return RedirectToAction("Index");
        }
        public async Task<IActionResult> CreateRole(string id)
        {
            if (id != null)
            {
                IdentityRole role = await _roleManager.FindByIdAsync(id);

                return View(new RoleViewModel
                {
                    Name = role.Name
                });
            }
            return View();
        }
        [HttpPost]
        public async Task<IActionResult> CreateRole(RoleViewModel model, string id)
        {
            IdentityResult result = null;
            if (id != null)
            {
                IdentityRole role = await _roleManager.FindByIdAsync(id);
                role.Name = model.Name;
                result = await _roleManager.UpdateAsync(role);
            }
            else
                result = await _roleManager.CreateAsync(new IdentityRole { Name = model.Name });

            if (result.Succeeded)
            {
                //Başarılı...
            }
            return View();
        }

        //[Authorize]
        public IActionResult UserRoleList()
        {
            return View(_userManager.Users);
        }

    }
Hamit YILDIRIM
  • 4,224
  • 1
  • 32
  • 35
0

Found something here I am using: https://github.com/dotnet/aspnetcore/issues/8149#issuecomment-471927034

/// <summary>
/// https://github.com/dotnet/aspnetcore/issues/8149#issuecomment-471927034
/// </summary>
public class OverrideFilter : ActionFilterAttribute
{
    public Type Type { get; set; }
}

public class OverrideFilterProvider : IFilterProvider
{
    public int Order => 1;

    public void OnProvidersExecuted(FilterProviderContext context) { }

    public void OnProvidersExecuting(FilterProviderContext context)
    {
        if (context.ActionContext.ActionDescriptor.FilterDescriptors != null)
        {
            //Check whether the method has any OverrideFilter
            var overrideFilters = context.Results.Where(filterItem => filterItem.Filter is OverrideFilter).ToList();
            foreach (var overrideFilter in overrideFilters)
            {
                //Remove the filters of the corresponding type, but with smaller scope
                context.Results.RemoveAll(filterItem =>
                    filterItem.Descriptor.Filter.GetType() == ((OverrideFilter)overrideFilter.Filter).Type &&
                    filterItem.Descriptor.Scope < overrideFilter.Descriptor.Scope);
            }
        }
    }
}

public class OverrideAuthorization : OverrideFilter
{
    public OverrideAuthorization()
    {
        Type = typeof(AuthorizeFilter);
    }
}

/// <summary>
/// https://stackoverflow.com/questions/16606281/linq-to-remove-certain-elements-from-a-ilistt-based-on-a-ilistint
/// </summary>
public static class IListExt
{
    public static int RemoveAll<T>(this IList<T> list, Predicate<T> match)
    {
        int count = 0;

        for (int i = list.Count - 1; i >= 0; i--)
        {
            if (match(list[i]))
            {
                ++count;
                list.RemoveAt(i);
            }
        }

        return count;
    }
}

Finally we inject it as follows (I am not sure this is the right wat to inject it, but it works);

services.TryAddEnumerable(ServiceDescriptor.Singleton<IFilterProvider, OverrideFilterProvider>());

Use like

[Authorize(Policy = "ControllerPolicy")
public class MyController : Controller
{
    [OverrideAuthorization]
    [Authorize(Policy = "ActionPolicy")]
    public IActionResult MyAction()
    {
        //Only ActionPolicy will be applied, while ControllerPolicy will be ignored
    }
}
osexpert
  • 485
  • 1
  • 6
  • 18