hemp
hemp

Reputation: 5683

Can an action authorize everyone except a given user/role?

Setting an action to be allowed by only a specific user or role is easy using the [Authorize] attribute. E.g.

[Authorize(Roles = "Administrator")]
public ActionResult Index()
{
  ...

However, I ran into a problem when I wanted the inverse. Is there a way using MVC framework features to allow all authenticated users except those specified by name or role?

The desired usage would be something akin to:

[DoNotAuthorize(Roles = "RestrictedUser")]
public ActionResult Index()
{
  ...

Upvotes: 3

Views: 4469

Answers (5)

Shahid Hameed
Shahid Hameed

Reputation: 1

One way to authorize specific users and assign it to different controllers or actions: Make a simple static class and create const variables.

public static class RolesText
    {
        public const string MISDeveloper = "MIS Developer";
        public const string SuperAdmin = "Super Admin";
        public const string Admin = "Admin";
        public const string DistrictMnE = "District M&E";
        public const string PSUEngineer = "PSU Engineer";
        public const string Guest = "Guest";

        public const string All_Users = MISDeveloper + "," + SuperAdmin + "," + Admin + "," + DistrictMnE + "," + PSUEngineer + "," + Guest;
        public const string Without_PSUEng_DistMnE = MISDeveloper + "," + SuperAdmin + "," + Admin + "," + Guest;
        public const string Without_PSUEng_Guest = MISDeveloper + "," + SuperAdmin + "," + Admin + "," + DistrictMnE;
        public const string Without_DistMnE_Guest = MISDeveloper + "," + SuperAdmin + "," + Admin + "," + PSUEngineer;
    }

Then call these values like below.

[Authorize(Roles = RolesText.Without_PSUEng_Guest)]
    public class NeedBasedSMsController : Controller
    {
        private AUPDBEntities db = new AUPDBEntities();
    }

If want to assign different users then call it as below.

[Authorize(Roles = RolesText.Admin + "," + RolesText.Guest)]
public class MyController : Controller
{
    // YOUR CONTROLLER CLASS
}

Upvotes: 0

Edgar Costanzo
Edgar Costanzo

Reputation: 11

The following code simply negates the authorization granted to the specified user/role, effectively becoming an authorization to everyone but the specified user/role.

Use it just like you would use the AuthorizeAttribute class.

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public class DoNotAuthorizeAttribute : AuthorizeAttribute
{
    protected override bool AuthorizeCore(HttpContextBase httpContext) => !base.AuthorizeCore(httpContext);
}

Upvotes: 1

Filip
Filip

Reputation: 127

Old question, but for those who will need it - very simply you can handle it using Policy:

Just add the class:

public class CustomRoleRequirement : AuthorizationHandler<CustomRoleRequirement>, IAuthorizationRequirement
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, CustomRoleRequirement requirement)
    {
        var roles = new[] { "YourExcludedRole1", "YourExcludedRole2", "YourExcludedRole3"}; 
        var userIsInRole = roles.Any(role => context.User.IsInRole(role));
        if (userIsInRole)
        {
            context.Fail();
            return Task.FromResult(false);
        }

        context.Succeed(requirement);
        return Task.FromResult(true);
    }
}

Then add to startup.cs

public void ConfigureServices(IServiceCollection services)
{
     ...

     services.AddAuthorization(options => {
            options.AddPolicy("ExcludeRoles", policy => policy.AddRequirements(new CustomRoleRequirement()));
        });

     ...
}

And then you can simply use [Authorize(Policy = "ExcludeRoles")] and all the roles will be authorized excluded "YourExcludedRole1", "YourExcludedRole2", "YourExcludedRole3"

Upvotes: 1

codingbiz
codingbiz

Reputation: 26406

You could just list accepted roles and leave the exception out

[Authorize(Roles = "Administrator, Admin, SuperUser")]
public ActionResult Index()
{
}

Or create a special AuthorizeAttribute with that logic

Upvotes: 1

hemp
hemp

Reputation: 5683

One fairly simple solution is to derive from the AuthorizeAttribute class and override its AuthorizeCore method, swapping its true/false logic.

/// <summary>
/// Authorizes any authenticated user *except* those who match the provided Users or Roles.
/// </summary>
public class DoNotAuthorizeAttribute : AuthorizeAttribute
{
    /// <summary>
    /// This is effectively a copy of the MVC source for AuthorizeCore with true/false logic swapped.
    /// </summary>
    /// <param name="httpContext">The HTTP context, which encapsulates all HTTP-specific information about an individual HTTP request.</param>
    /// <returns>true if the user is authorized; otherwise, false.</returns>
    protected override bool AuthorizeCore(HttpContextBase httpContext)
    {
        if (httpContext == null)
        {
            throw new ArgumentNullException("httpContext");
        }
        IPrincipal user = httpContext.User;
        if (!user.Identity.IsAuthenticated)
        {
            return false;
        }

        string[] usersSplit = SplitString(Users);
        if ((usersSplit.Length > 0) && usersSplit.Contains<string>(user.Identity.Name, StringComparer.OrdinalIgnoreCase))
        {
            return false;
        }

        string[] rolesSplit = SplitString(Roles);
        if ((rolesSplit.Length > 0) && rolesSplit.Any<string>(new Func<string, bool>(user.IsInRole)))
        {
            return false;
        }

        return true;
    }

    /// <summary>
    /// This is a direct copy of the MVC source for the internal SplitString method.
    /// </summary>
    /// <param name="original">The original string to split.</param>
    /// <returns>An array of strings.</returns>
    internal static string[] SplitString(string original)
    {
        if (string.IsNullOrWhiteSpace(original))
        {
            return new string[0];
        }
        return (from piece in original.Split(new[] { ',' })
                let trimmed = piece.Trim()
                where !string.IsNullOrEmpty(trimmed)
                select trimmed).ToArray<string>();
    }
}

Upvotes: 6

Related Questions