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?
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()
{
}
}
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()
{
}
}
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() { ... }
}
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);
}
}
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
}
}
Related
I'm using ASP.NET Core identity
and I want to observe DIP and I create a interface
public interface IUserManagerService : IDisposable
{
Task<IdentityResult> CreateAsync(User user, string password);
}
and implementation class is
public class UserManagerService : UserManager<User>, IUserManagerService
{
public UserManagerService()
: base()
{
}
}
now I don't know what parameters should pass to base class
how can I fix that ?
From your description, you want to create a service with the UserManager, then use it to manage users. If that is the case, you could refer the following sample code:
public interface IUserManagerService:IDisposable
{
Task<IdentityResult> CreateAsync(User user, string password);
}
public class UserManagerService : IUserManagerService
{
//request the UserManger service and call the relate methods.
private readonly UserManager<IdentityUser> usermanager;
public UserManagerService(UserManager<IdentityUser> userManager)
{
usermanager = userManager;
}
public async Task<IdentityResult> CreateAsync(User user, string password)
{
var identityuser = new IdentityUser { UserName = user.UserName, Email = user.Email };
var result = await usermanager.CreateAsync(identityuser, password);
return result;
}
public void Dispose()
{
Console.WriteLine("- IUserManagerService was disposed!");
}
}
The User model:
public class User
{
public string UserName { get; set; }
public string Email { get; set; }
}
Then, registers the IUserManagerService service with the concrete type UserManagerService:
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDatabaseDeveloperPageExceptionFilter();
services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddControllersWithViews();
services.AddScoped<IUserManagerService, UserManagerService>();
Then, in the MVC controller, request the IUserManagerService service, and call its method to create a user:
public class HomeController : Controller
{
private readonly IUserManagerService userManagerService;
public HomeController(IUserManagerService userManager)
{
userManagerService = userManager;
}
public IActionResult Index()
{
var newuser = new User() { UserName = "Tom", Email = "tom121#hotmail.com" };
var password = "Password123!";
var result = userManagerService.CreateAsync(newuser, password);
if (result.Result.Succeeded)
{
//do some thing
}
return View();
}
The result as below:
More detail information, refer Dependency injection in ASP.NET Core
I'm trying to add a user in my database but when I create it it is not added and it doesn't give me an error message. When I add a breakpoint to the CeateUser method the code is running but when I add a breakpoint on the result condition, the code is never reached.
Here's my PageModel
public class AddUserModel : PageModel
{
[BindProperty]
public NewAccountInput AccountInput { get; set; }
private UserManager<AdminUser> UserManager { get; set; }
public AddUserModel(UserManager<AdminUser> userManager)
{
UserManager = userManager;
}
public void OnPost()
{
if(AccountInput.ComfirmationPassword != AccountInput.Password)
{
return;
}
_ = CreateUser();
}
public async Task<bool> CreateUser()
{
var result = await UserManager.CreateAsync(new AdminUser()
{
Email = AccountInput.Email,
FirstName = AccountInput.UserName,
LastName = AccountInput.UserName,
UserName = AccountInput.Email
}, AccountInput.Password);
if (result.Succeeded)
{
return true;
}
else
{
return false;
}
}
}
The solution is because the method OnPost must be of type Task.
public async Task OnPost()
{
if(AccountInput.ComfirmationPassword != AccountInput.Password)
{
return;
}
await CreateUser();
}
is it possible to get data from an attribute in middleware before the page is loaded?
Meaning if I attach an attribute to a controller, can I access the data in middleware?
My for now empty attribute:
public sealed class Secure : Attribute
{
public Secure()
{
}
public Secure(params string[] roles)
{
}
}
I think you'd better custom ActionFilterAttribute to get the data before action excutes:
public class SecureAttribute : ActionFilterAttribute
{
private readonly UserManager<ApplicationUser> _userManager;
public SecureAttribute(UserManager<ApplicationUser> userManager)
{
_userManager = userManager;
}
public override void OnActionExecuting(ActionExecutingContext context)
{
//get data from query string
if (context.ActionArguments.TryGetValue("returnUrl", out object value))
{
//returnUrl is the query string key name
var query = value.ToString();
}
//get data from form
if (context.ActionArguments.TryGetValue("test", out object model))
{
var data = model;
}
//get data from log in User
var USER = context.HttpContext.User;
if(USER.Identity.IsAuthenticated)
{
var user = _userManager.FindByNameAsync(USER.Identity.Name).Result;
var roles = _userManager.GetRolesAsync(user).Result;
}
base.OnActionExecuting(context);
}
}
Controller:
[ServiceFilter(typeof(SecureAttribute))]
public async Task<IActionResult> Index(string returnUrl)
{...}
[HttpPost]
[ServiceFilter(typeof(SecureAttribute))]
public IActionResult Index(Test test)
{
return View(test);
}
Startup.cs:
services.AddScoped<SecureAttribute>();
If you use Identity to get the role,be sure register service like below:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("YourConnectionString")));
services.AddIdentity<IdentityUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddScoped<SecureAttribute>();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
Result:
Found a nice example of how to access attributes in middleware.
Source: https://michaelscodingspot.com/attributes-and-middleware-in-asp-net-core/
Create attribute:
public class TelemetryAttribute : Attribute
{
public TelemetryEvent Event { get; set; }
public TelemetryAttribute(TelemetryEvent ev)
{
Event = ev;
}
}
public enum TelemetryEvent { SignUp, SignIn}
Use the attribute:
[TelemetryAttribute(TelemetryEvent.SignUp)]
public async Task<IActionResult> SignUp([FromBody]SignUpViewModel vm)
{
// ...
}
Use attribute params in middleware:
public class TelemetryMiddleware
{
private RequestDelegate _next;
public TelemetryMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
await _next(context);
var endpoint = context.Features.Get<IEndpointFeature>()?.Endpoint;
var attribute = endpoint?.Metadata.GetMetadata<TelemetryAttribute>();
if (attribute != null && attribute.Event == TelemetryEvent.SignUp)
{
// your code here
}
}
}
I ended up implementing an ActionFilterAttribute.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public sealed class SecureAttribute : ActionFilterAttribute
{
private readonly string[] _roles;
public SecureAttribute()
{
_roles = new string[0];
}
public SecureAttribute(params string[] roles)
{
_roles = roles;
}
public override void OnActionExecuting(ActionExecutingContext context)
{
ISecureRepo repo = (ISecureRepo)context.HttpContext.RequestServices.GetService(typeof(ISecureRepo));
IUserProcessResult user = repo.GetCurrentUserAsync().Result;
UnauthorizedObjectResult unauth = new UnauthorizedObjectResult($"{StatusCodes.Status401Unauthorized} Unauthorized");
if (!user.Null())
{
bool access = true;
foreach (string role in this.Roles)
{
if (user.User.Features.Where(ft => ft.Feature.Name == role).Count() == 0)
{
access = false;
}
}
if (!access)
{
context.Result = unauth;
}
}
else
{
context.Result = unauth;
}
}
public IEnumerable<string> Roles => _roles;
}
I've added a few specific Properties to the ApplicationUser of the standart project.
some of the added Properties are of custom classes.
Since i use EntityFramework, it creates a dbtable for users and one for each custom class.
i added the Properties to my ManageController and Views and adding these Properties to the specific dbtable works, but i cant access them. in the dbo.AspNetUsers there is a column added, that is called after the attribute + ID (In my example "NameID").
Now if i am loading the user in my ManageController, every normal Attribute is loaded, but the custom ones are null.
My Question is, how can i load the custom objects (that are really stored in the other table).
ApplicationUser.cs:
namespace refProject.Models
{
public class ApplicationUser : IdentityUser
{
public Name Name { get; set; }
}
}
ManageController.cs
//other usings
using refProject.Models;
using refProject.Models.ManageViewModels;
namespace refProject.Controllers
{
[Authorize]
public class ManageController : Controller
{
private readonly UserManager<ApplicationUser> _userManager;
//other managers
public ManageController(
UserManager<ApplicationUser> userManager,
//other managers
)
{
_userManager = userManager;
//other managers
}
//
// GET: /Manage/Index
[HttpGet]
public async Task<IActionResult> Index(ManageMessageId? message = null)
{
ViewData["StatusMessage"] =
message == ManageMessageId.ChangeNameSuccess ? "Your name has been changed."
: message == ManageMessageId.SetNameSuccess ? "Your name has been set."
: "";
var user = await GetCurrentUserAsync();
if (user == null)
{
return View("Error");
}
var model = new IndexViewModel
{
//other Properties
//
//
// THIS ONE IS NULL
//
//
Name = user.Name
//other Properties
};
return View(model);
}
// GET: /Manage/ChangeName
[HttpGet]
public IActionResult ChangeName()
{
return View();
}
//
// POST: /Manage/ChangeName
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> ChangeName(ChangeNameViewModel model)
{
if(!ModelState.IsValid)
{
return View(model);
}
var user = await GetCurrentUserAsync();
if(user != null)
{
Name NewName = new Name();
NewName.FirstName = model.NewFirstName;
NewName.LastName = model.NewLastName;
user.Name = NewName;
IdentityResult result = await _userManager.UpdateAsync(user);
if (result.Succeeded)
{
return RedirectToAction(nameof(Index), new { Message = ManageMessageId.ChangeNameSuccess });
}
AddErrors(result);
return View(model);
}
return RedirectToAction(nameof(Index), new { Message = ManageMessageId.Error });
}
//
// GET: /Manage/SetName
[HttpGet]
public IActionResult SetName()
{
return View();
}
//
// POST: /Manage/SetName
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> SetName(SetNameViewModel model)
{
if(!ModelState.IsValid)
{
return View(model);
}
var user = await GetCurrentUserAsync();
if(user != null)
{
Name NewName = new Name();
NewName.FirstName = model.NewFirstName;
NewName.LastName = model.NewLastName;
user.Name = NewName;
IdentityResult result = await _userManager.UpdateAsync(user);
if(result.Succeeded)
{
return RedirectToAction(nameof(Index), new { Message = ManageMessageId.SetNameSuccess });
}
AddErrors(result);
return View(model);
}
return RedirectToAction(nameof(Index), new { Message = ManageMessageId.Error });
}
#region Helpers
private void AddErrors(IdentityResult result)
{
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
}
public enum ManageMessageId
{
Error,
ChangeNameSuccess,
SetNameSuccess,
}
private Task<ApplicationUser> GetCurrentUserAsync()
{
return _userManager.GetUserAsync(HttpContext.User);
}
#endregion
}
}
Name.cs
namespace refProject.Models
{
public class Name
{
public int ID { get; set; }
public string fTitle { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string lTitle { get; set; }
public override string ToString()
{
return fTitle + " " + FirstName + " " + LastName + " " + lTitle;
}
}
}
That is a known issue. It is not considered a bug, but rather a design decision.
The recommended way is to access the user through DbContext rather than from the UserManager implementation.
"Just to add a bit more detail: as a performance optimization ASP.NET Core Identity currently only loads the entities related to a user or a role as needed to satisfy API calls. I.e. it won't load related entities (not even the built-in ones) eagerly on a method call like such as FindByName() because the find methods are only required to return the root.
At this point issuing queries against the DbContext is the recommended
way to load related data. If you want to abstract this from the
application code you can extend both the Identity store and manager
classes to add methods to retrieve and return your custom related
data."
Comment link
You could change your GetCurrentUserAsync method as follows:
private ApplicationUser GetCurrentUserAsync()
{
return _userManager.Users.Include(x => x.Name).FirstOrDefault(x => x.Id == _userManager.GetUserId(User));
}
Question Summary: In ASP.NET MVC, is there a clean way to prevent a specific user or role from accessing an action?
Obviously, the following would allow roles Admin and Editor to access the entire controller.
[Authorize(Roles = "Admin, Editor")]
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult About()
{
return View();
}
}
If I only wanted the Admin role to have access to the About action, I could do the following:
[Authorize(Roles = "Admin, Editor")]
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
[Authorize(Roles = "Admin")] // this will take precedence over the controller's authorization
public ActionResult About()
{
return View();
}
}
Is there a way to accomplish this without listing every single role that needs access, and only specifying the roles that should be prevented from having access?
Create your own blacklist class just like this one:
public class Blacklist : AuthorizeAttribute {
private List<string> RolesList;
public string Roles {
get {
string roles = "";
if (RolesList!= null && RolesList.Count > 0) {
int counter = 0;
foreach (string role in RolesList) {
counter++;
if (counter == RolesList.Count)
roles = role;
else
roles += role + ",";
}
}
return roles;
}
set {
RolesList = new List<string>();
string[] roles = value.Split(',');
foreach (string role in roles) {
RolesList.Add(role);
}
}
}
//constructor
public Blacklist () {
RolesList = new List<string>();
}
protected override bool AuthorizeCore(HttpContextBase httpContext) {
bool result = true;
if (httpContext == null) {
throw new ArgumentNullException("httpContext");
}
foreach (string role in RolesList) {
if (httpContext.User.IsInRole(role)) {
result = false;
break;
}
}
return result;
}
}
Now you are going to block the roles you want:
[Authorize]
[Blacklist (Roles = "Admin", "Editor")]
public ActionResult Index() {
return View();
}
Here is the code for the class I used to solve this problem. It derives heavily from AuthorizeAttribute, and will allow any authenticated user through who does not match the specifications set by the parameters.
(Note that the important method is AuthorizeCore - everything else is essentially copied or inherited from AuthorizeAttribute)
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)]
public class BlackListAttribute : AuthorizeAttribute
{
private static readonly string[] _emptyArray = new string[0];
private string _roles;
private string _users;
private string[] _rolesSplit = _emptyArray;
private string[] _usersSplit = _emptyArray;
public new string Roles
{
get { return _roles ?? String.Empty; }
set
{
_roles = value;
_rolesSplit = SplitString(value);
}
}
public new string Users
{
get { return _users ?? String.Empty; }
set
{
_users = value;
_usersSplit = SplitString(value);
}
}
// This is the important part. Everything else is either inherited from AuthorizeAttribute or, in the case of private or internal members, copied from AuthorizeAttribute.
protected override bool AuthorizeCore(System.Web.HttpContextBase httpContext)
{
if (httpContext == null)
{
throw new ArgumentNullException("httpContext");
}
IPrincipal user = httpContext.User;
if (user == null || user.Identity == null || !user.Identity.IsAuthenticated)
{
return false;
}
if (_usersSplit.Length > 0 && _usersSplit.Contains(user.Identity.Name, StringComparer.OrdinalIgnoreCase))
{
return false;
}
if (_rolesSplit.Length > 0 && _rolesSplit.Any(user.IsInRole))
{
return false;
}
return true;
}
internal static string[] SplitString(string original)
{
if (String.IsNullOrEmpty(original))
{
return _emptyArray;
}
var split = from piece in original.Split(',')
let trimmed = piece.Trim()
where !String.IsNullOrEmpty(trimmed)
select trimmed;
return split.ToArray();
}
}
You can use it on controllers or actions like any other AuthorizeAttribute:
[Authorize(Roles = "Admin, Editor")]
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
[BlackList(Roles = "Editor")]
public ActionResult About()
{
return View();
}
}