Web Api Custom Authorize Attribute Properties - asp.net-mvc

I'm trying to extend he default Web Api authorize attribute to allow authenticated users to have access to a set of actions even though they are not registered in the application (e.g., they don't have a role).
public class AuthorizeVerifiedUsersAttribute : AuthorizeAttribute
{
/// <summary>
/// Gets or sets the authorized roles.
/// </summary>
public new string Roles { get { return base.Roles; } set { base.Roles = value; } }
/// <summary>
/// Gets or sets the authorized users.
/// </summary>
public new string Users { get { return base.Users; } set { base.Users = value; } }
private bool _bypassValidation;
/// <summary>
/// Gets of sets a controller or an action as an authorization exception
/// </summary>
public virtual bool BypassValidation
{
get
{
Debug.WriteLine("get:" + TypeId.GetHashCode() + " " + _bypassValidation);
return _bypassValidation;
}
set
{
Debug.WriteLine("set:" + TypeId.GetHashCode() + " " + value);
_bypassValidation = value;
}
}
protected override bool IsAuthorized(System.Web.Http.Controllers.HttpActionContext actionContext)
{
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
if (BypassValidation)
{
return true;
}
else
{
//return false if user is unverified
}
}
return base.IsAuthorized(actionContext);
}
}
And it is being used like this:
[AuthorizeVerifiedUsers]
public class UserProfileController : ApiController
{
[AuthorizeVerifiedUsers(BypassValidation = true)]
public bool Verify(string verificationCode)
{}
}
So far this action is the only that is using the BypassValidation = true.
The issue arises because the BypassValidation property is false for the action even though the Debug window - used in the BypassValidation property - shows the following:
set:26833123 True
set:39602703 True
get:43424763 False
get:43424763 False
get:43424763 False //call that should have "True"...
I noticed two things:
The TypeId (The unique identifier for the attribute) is different between the calls that have BypassValidation = true and the ones that have BypassValidation = false.
The id '43424763' doesn't have a corresponding set
Any ideas?
Thanks in advance,
Joao

The way Web API works is that the authorize attribute is called for the parent scope, in this case the controller, and the override (authorize attribute on the action) needs to be done manually (Please correct me if I'm wrong).
Therefore a solution could look like the following:
public class AuthorizeVerifiedUsersAttribute : AuthorizeAttribute
{
(...)
protected override bool IsAuthorized(HttpActionContext actionContext)
{
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
//retrieve controller action's authorization attributes
var authorizeAttributes = actionContext.ActionDescriptor.GetCustomAttributes<AuthorizeVerifiedUsersAttribute>();
//check controller and action BypassValidation value
if (BypassValidation ||
actionAttributes.Count > 0 && actionAttributes.Any(x => x.BypassValidation))
{
return true;
}
else
{
//return false if user is unverified
}
return base.IsAuthorized(actionContext);
}
}

A bit too late, but for other users with similar problems: in Web API 2 you can override all previous authorization attributes (global authorization filters, controller authorization attributes, etc.) using "OverrideAuthorization" and afterwards just use the Authorize attribute, without specifying the role. The default behavior of the Authorize attribute is just to check if the user is authenticated.
In this case:
[YourCustomAuthorize]
public class UserProfileController : ApiController
{
[OverrideAuthorization]
[Authorize]
public bool Verify(string verificationCode)
{
// TODO
}
}

Related

Mvc Application, windows authentication, authorization, visibility

I have a windows authentication MVC app that needs the username to do a lookup to determine if links are visible and set authorization. Note: I do visibility/Authorization with roles as well.
I need the username so I am currently doing it in OnAuthentification (not sure if this is the right place). I am splicing the username down to put it on the main page and say welcome, User. (presentation purposes)
[Authorize]
public abstract class ApplicationController : Controller
{
public static bool IsApprover;
protected override void OnAuthentication(AuthenticationContext filterContext)
{
base.OnAuthentication(filterContext);
if (filterContext.RequestContext.HttpContext.User.Identity.IsAuthenticated == true)
{
string userName = User.Identity.Name.Remove(0, 16).Replace('.', ' ').ToLower();
HttpContext.Application["UserName"] = TitleCase(userName, "Nothing");
//Initialize Values
HttpContext.Application["IsApprover"] = false; //used for link visibility
IsApprover = false; //used for Authorization
//do db lookup and set IsApprover values
}
}
}
So, I set the values above. I am not including the entity framework code just to be brief. The above works fine and every controller inherits from ApplicationController.
I also have
public class CustomAuthorizationValue : AuthorizeAttribute
{
private bool localIsAllowed;
public CustomAuthorizationValue(bool isAllowed)
{
localIsAllowed = isAllowed;
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext.Request.IsLocal)
{
var authorized = base.AuthorizeCore(httpContext);
if (!authorized)
{
// The user is not authorized => no need to go any further
return false;
}
return localIsAllowed;
}
return false;
}
}
For Authorization I use:
[CustomAuthorizationValue(IsApprover)]
public ActionResult Approve()
{
//code
}
For Visibility in Razor I use
#if((bool)#HttpContext.Current.Application["IsApprover"] == true)
{
<li>Approve (#HttpContext.Current.Application["ApproveCount"])</li>
}
This works fine but I have 2 different variables to use,
one for visibility (HttpContext.Current.Application["IsApprover"])
and
one for Authorization (IsApprover).
Is there a more elegant solution?
Is there another place to put the code rather than override void OnAuthentication?
Is there a way I can just set 1 variable for visibility and Authorization rather than having 2?
Is this the best practice or am I way off?
The above works fine and every controller inherits from
ApplicationController.
Hmmmm. You are storing user specific information information in the wrong scope:
HttpContext.Application["UserName"] = TitleCase(userName, "Nothing");
HttpContext.Application["IsApprover"] = false;
In ASP.NET, the Application scope is shared among ALL users of your website. So you have a concurrency issue here.
You should use the HTTP Context scope instead:
HttpContext.Items["UserName"] = TitleCase(userName, "Nothing");
HttpContext.Items["IsApprover"] = false;
Is there a more elegant solution?
You could use a view model:
public class MyViewModel
{
public string UserName { get; set; }
public bool IsApprover { get; set; }
}
and then have a couple of extension methods to work more easily:
public static class HttpContextExtensions
{
private const string MyViewModelKey = "__MyViewModel__";
public static MyViewModel GetMyViewModel(this HttpContextBase context)
{
return (MyViewModel)context.Items[MyViewModelKey];
}
public static void SetMyViewModel(this HttpContextBase context, MyViewModel model)
{
context.Items[MyViewModelKey] = model;
}
}
and then use those extension methods:
if (filterContext.RequestContext.HttpContext.User.Identity.IsAuthenticated)
{
string userName = User.Identity.Name.Remove(0, 16).Replace('.', ' ').ToLower();
bool isApprover = ... do db lookup and set IsApprover value
var model = new MyViewModel
{
UserName = TitleCase(userName, "Nothing"),
IsApprover = isApprover,
}
this.HttpContext.SetMyViewModel(model);
}
and in your view:
#if(HttpContext.GetMyViewModel().IsApprover)
{
<li>
<a href="#Url.Action("Approve", "Approve")">
Approve (#HttpContext.Current.Application["ApproveCount"])
</a>
</li>
}
NOTE: In this anchor text once again you seem to be using the Application scope to store user specific information such as ApproveCount which we discussed earlier.
Is this the best practice or am I way off?
Well, I would probably use claims based authentication and store this information (IsApprover, ...) as claims in the current user.

MVCSiteMap Node Requring Multiple Roles

I've set up my menu using MVCSiteMap and I have this node:
<mvcSiteMapNode title="Courses Form" controller="Booking" action="Course" roles="CORLIC, VIEWCOBO"/>
I'm trying to enforce that this node must have roles "CORLIC" AND "VIEWCOBO" for it to be visible but of course this means that it will be displayed if the user has either of the above.
Is this possible?
Thanks.
The roles attribute is for interoperability with ASP.NET and should not be used in an MVC-only application.
For MVC, if you are already defining the AuthorizeAttribute on your controller actions, MvcSiteMapProvider will automatically pick them up and hide the matching nodes accordingly if security trimming is enabled.
[Authorize]
public ActionResult Course()
{
return View();
}
[Authorize]
[HttpPost]
public ActionResult Course(CourseModel model)
{
if (ModelState.IsValid)
{
// Implementation omitted
}
// If we got this far, something failed, redisplay form
return View(model);
}
The default AuthorizeAttribute accepts roles, but it works in the same way as the roles attribute - that is, any role that the user is in will cause it to succeed.
However, you could inherit AuthorizeAttribute yourself and override the IsAuthorized method to change the logic as needed.
public class SpecialAuthorizeAttribute : AuthorizeAttribute
{
private string _requiredRoles;
private string[] _requiredRolesSplit = new string[0];
/// <summary>
/// Gets or sets the required roles. The user must be a member of all roles for it to succeed.
/// </summary>
/// <value>
/// The roles string.
/// </value>
/// <remarks>Multiple role names can be specified using the comma character as a separator.</remarks>
public string RequiredRoles
{
get { return _requiredRoles ?? String.Empty; }
set
{
_requiredRoles = value;
_requiredRolesSplit = SplitString(value);
}
}
/// <summary>
/// Determines whether access for this particular request is authorized. This method uses the user <see cref="IPrincipal"/>
/// returned via <see cref="HttpRequestContext.Principal"/>. Authorization is denied if the user is not authenticated,
/// the user is not in the authorized group of <see cref="Users"/> (if defined), or if the user is not in any of the authorized
/// <see cref="Roles"/> (if defined).
/// </summary>
/// <param name="actionContext">The context.</param>
/// <returns><c>true</c> if access is authorized; otherwise <c>false</c>.</returns>
protected override bool IsAuthorized(HttpActionContext actionContext)
{
if (actionContext == null)
{
throw new ArgumentNullException("actionContext");
}
IPrincipal user = actionContext.ControllerContext.RequestContext.Principal;
if (user == null || user.Identity == null || !user.Identity.IsAuthenticated)
{
return false;
}
// Ensure all of the roles in RequiredRoles are present.
if (_requiredRolesSplit.Length > 0 && !_requiredRolesSplit.All(user.IsInRole))
{
return false;
}
// Call the base class to check the users and roles there.
return base.IsAuthorized(actionContext);
}
/// <summary>
/// Splits the string on commas and removes any leading/trailing whitespace from each result item.
/// </summary>
/// <param name="original">The input string.</param>
/// <returns>An array of strings parsed from the input <paramref name="original"/> string.</returns>
internal static string[] SplitString(string original)
{
if (String.IsNullOrEmpty(original))
{
return new string[0];
}
var split = from piece in original.Split(',')
let trimmed = piece.Trim()
where !String.IsNullOrEmpty(trimmed)
select trimmed;
return split.ToArray();
}
}
Then you can specify which roles are required by using the new property.
[SpecialAuthorize(RequiredRoles = "CORLIC, VIEWCOBO")]
public ActionResult Course()
{
return View();
}
[SpecialAuthorize(RequiredRoles = "CORLIC, VIEWCOBO")]
[HttpPost]
public ActionResult Course(CourseModel model)
{
if (ModelState.IsValid)
{
// Implementation omitted
}
// If we got this far, something failed, redisplay form
return View(model);
}
Another possible option is to use FluentSecurity as shown here. For FluentSecurity v2.0 to work with MvcSiteMapProvider, you need to copy the HandleSecurityAttribute code into your project and change it to inherit from AuthorizeAttribute instead of Attribute, then use it as specified in the FluentSecurity documentation.

Making AuthorizeAttribute deny users by default if Roles is empty

I'm rather surprised at the default behaviour of AuthorizeAttribute; if you don't supply it any Roles property, it just appears to allow any authorized user to access the controller/action. I want whitelist behaviour instead; if Roles is null or empty, deny all users access. How can I make this behaviour occur?
public class AuthorizeExAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (string.IsNullOrWhiteSpace(Roles))
return false;
return base.AuthorizeCore(httpContext);
}
}
Now use [AuthorizeEx] on your controllers/actions
Here's what I came up with eventually, as a filter I add to the global filter collection for an MVC application:
/// <summary>
/// This filter should be applied to an MVC application as a global filter in RegisterGlobalFilters, not applied to individual actions/controllers.
/// It will cause access to every action to be DENIED by default.
/// If an AllowAnonymousAttribute is applied, all authorization checking is skipped (this takes precedence over AuthorizeSafeAttribute).
/// If an AuthorizeSafeAttribute is applied, only the roles specified in AuthorizeSafeAttribute's Roles property will be allowed access.
/// </summary>
public sealed class AuthorizeSafeFilter : AuthorizeAttribute {
public override void OnAuthorization(AuthorizationContext filterContext) {
if (!string.IsNullOrEmpty(this.Roles) || !string.IsNullOrEmpty(this.Users)) {
throw new Exception("This class is intended to be applied to an MVC application as a global filter in RegisterGlobalFilters, not applied to individual actions/controllers. Use the AuthorizeSafeAttribute with individual actions/controllers.");
}
// Disable caching for this request
filterContext.HttpContext.Response.Cache.SetNoServerCaching();
filterContext.HttpContext.Response.Cache.SetNoStore();
// If AllowAnonymousAttribute applied, skip authorization
if (
filterContext.ActionDescriptor.IsDefined(typeof(AllowAnonymousAttribute), true) ||
filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(AllowAnonymousAttribute), true)
) {
return;
}
// Backup original roles
string rolesBackup = this.Roles;
// Look for AuthorizeSafeAttribute roles
bool foundRoles = false;
string foundRolesString = null;
object[] actionCustomAttributes = filterContext.ActionDescriptor.GetCustomAttributes(false);
object[] controllerCustomAttributes = filterContext.ActionDescriptor.ControllerDescriptor.GetCustomAttributes(false);
if (actionCustomAttributes.Any(attr => attr is AuthorizeSafeAttribute)) {
AuthorizeSafeAttribute foundAttr = (AuthorizeSafeAttribute)(actionCustomAttributes.First(attr => attr is AuthorizeSafeAttribute));
foundRoles = true;
foundRolesString = foundAttr.Roles;
}
else if (controllerCustomAttributes.Any(attr => attr is AuthorizeSafeAttribute)) {
AuthorizeSafeAttribute foundAttr = (AuthorizeSafeAttribute)(controllerCustomAttributes.First(attr => attr is AuthorizeSafeAttribute));
foundRoles = true;
foundRolesString = foundAttr.Roles;
}
if (foundRoles && !string.IsNullOrWhiteSpace(foundRolesString)) {
// Found valid roles string; use it as our own Roles property and auth normally
this.Roles = foundRolesString;
base.OnAuthorization(filterContext);
}
else {
// Didn't find valid roles string; DENY all access by default
filterContext.Result = new HttpUnauthorizedResult();
}
// Restore original roles
this.Roles = rolesBackup;
}
}
I also define this attribute:
/// <summary>
/// Represents an attribute that is used to restrict access by callers to an action method, in conjunction
/// with a global AuthorizeSafeFilter, DENYING all access by default.
/// </summary>
public class AuthorizeSafeAttribute : Attribute {
public string Roles { get; set; }
}
I apply AllowAnonymousAttribute to my login actions/controllers and AuthorizeSafeAttribute to other ones, but if I forget to apply these, access is denied by default. I wish ASP.NET MVC were as secure as this by default. :-)

How to redirect [Authorize] to loginUrl only when Roles are not used?

I'd like [Authorize] to redirect to loginUrl unless I'm also using a role, such as [Authorize (Roles="Admin")]. In that case, I want to simply display a page saying the user isn't authorized.
What should I do?
Here is the code from my modified implementation of AuthorizeAttribute; I named it SecurityAttribute. The only thing that I have changed is the OnAuthorization method, and I added an additional string property for the Url to redirect to an Unauthorized page:
// Set default Unauthorized Page Url here
private string _notifyUrl = "/Error/Unauthorized";
public string NotifyUrl {
get { return _notifyUrl; } set { _notifyUrl = value; }
}
public override void OnAuthorization(AuthorizationContext filterContext) {
if (filterContext == null) {
throw new ArgumentNullException("filterContext");
}
if (AuthorizeCore(filterContext.HttpContext)) {
HttpCachePolicyBase cachePolicy =
filterContext.HttpContext.Response.Cache;
cachePolicy.SetProxyMaxAge(new TimeSpan(0));
cachePolicy.AddValidationCallback(CacheValidateHandler, null);
}
/// This code added to support custom Unauthorized pages.
else if (filterContext.HttpContext.User.Identity.IsAuthenticated)
{
if (NotifyUrl != null)
filterContext.Result = new RedirectResult(NotifyUrl);
else
// Redirect to Login page.
HandleUnauthorizedRequest(filterContext);
}
/// End of additional code
else
{
// Redirect to Login page.
HandleUnauthorizedRequest(filterContext);
}
}
You call it the same way as the original AuthorizeAttribute, except that there is an additional property to override the Unauthorized Page Url:
// Use custom Unauthorized page:
[Security (Roles="Admin, User", NotifyUrl="/UnauthorizedPage")]
// Use default Unauthorized page:
[Security (Roles="Admin, User")]
Extend the AuthorizeAttribute class and override HandleUnauthorizedRequest
public class RoleAuthorizeAttribute : AuthorizeAttribute
{
private string redirectUrl = "";
public RoleAuthorizeAttribute() : base()
{
}
public RoleAuthorizeAttribute(string redirectUrl) : base()
{
this.redirectUrl = redirectUrl;
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (filterContext.HttpContext.Request.IsAuthenticated)
{
string authUrl = this.redirectUrl; //passed from attribute
//if null, get it from config
if (String.IsNullOrEmpty(authUrl))
authUrl = System.Web.Configuration.WebConfigurationManager.AppSettings["RolesAuthRedirectUrl"];
if (!String.IsNullOrEmpty(authUrl))
filterContext.HttpContext.Response.Redirect(authUrl);
}
//else do normal process
base.HandleUnauthorizedRequest(filterContext);
}
}
Usage
[RoleAuthorize(Roles = "Admin, Editor")]
public class AccountController : Controller
{
}
And make sure you add your AppSettings entry in the config
<appSettings>
<add key="RolesAuthRedirectUrl" value="http://mysite/myauthorizedpage" />
</appSettings>
The easiest way I've found is to extend and customize the AuthorizeAttribute so that it does something different (i.e., not set an HttpUnauthorizedResult) when the Role check fails. I've written an article about this on my blog that you might find useful. The article describes much what you are wanting, though it goes further and allows the user who "owns" the data to also have access to the action. I think it should be fairly easy to modify for your purposes -- you'd just need to remove the "or owner" part.

asp.net mvc decorate [Authorize()] with multiple enums

I have a controller and I want two roles to be able to access it. 1-admin OR 2-moderator
I know you can do [Authorize(Roles="admin, moderators")] but I have my roles in an enum. With the enum I can only authorize ONE role. I can't figure out how to authorize two.
I have tried something like [Authorize(Roles=MyEnum.Admin, MyEnum.Moderator)] but that wont compile.
Someone once suggested this:
[Authorize(Roles=MyEnum.Admin)]
[Authorize(MyEnum.Moderator)]
public ActionResult myAction()
{
}
but it doesn't work as an OR. I think in this case the user has to be part of BOTH roles. Am I overlooking some syntax? Or is this a case where I have to roll my own custom authorization?
Here is a simple and elegant solution which allows you to simply use the following syntax:
[AuthorizeRoles(MyEnum.Admin, MyEnum.Moderator)]
When creating your own attribute, use the params keyword in your constructor:
public class AuthorizeRoles : AuthorizeAttribute
{
public AuthorizeRoles(params MyEnum[] roles)
{
...
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
...
}
}
This will allow you to use the attribute as follows:
[AuthorizeRoles(MyEnum.Admin, MyEnum.Moderator)]
public ActionResult myAction()
{
}
Try using the bit OR operator like this:
[Authorize(Roles= MyEnum.Admin | MyEnum.Moderator)]
public ActionResult myAction()
{
}
If that doesn't work, you could just roll your own. I currently just did this on my project. Here's what I did:
public class AuthWhereRole : AuthorizeAttribute
{
/// <summary>
/// Add the allowed roles to this property.
/// </summary>
public UserRole Is;
/// <summary>
/// Checks to see if the user is authenticated and has the
/// correct role to access a particular view.
/// </summary>
/// <param name="httpContext"></param>
/// <returns></returns>
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext == null)
throw new ArgumentNullException("httpContext");
// Make sure the user is authenticated.
if (!httpContext.User.Identity.IsAuthenticated)
return false;
UserRole role = someUser.Role; // Load the user's role here
// Perform a bitwise operation to see if the user's role
// is in the passed in role values.
if (Is != 0 && ((Is & role) != role))
return false;
return true;
}
}
// Example Use
[AuthWhereRole(Is=MyEnum.Admin|MyEnum.Newbie)]
public ActionResult Test() {}
Also, make sure to add a flags attribute to your enum and make sure they are all valued from 1 and up. Like this:
[Flags]
public enum Roles
{
Admin = 1,
Moderator = 1 << 1,
Newbie = 1 << 2
etc...
}
The left bit shifting gives the values 1, 2, 4, 8, 16 and so on.
Well, I hope this helps a little.
I combined a few of the solutions here to create my personal favorite. My custom attribute just changes the data to be in the form that SimpleMembership expects and lets it handle everything else.
My roles enum:
public enum MyRoles
{
Admin,
User,
}
To create roles:
public static void CreateDefaultRoles()
{
foreach (var role in Enum.GetNames(typeof(MyRoles)))
{
if (!Roles.RoleExists(role))
{
Roles.CreateRole(role);
}
}
}
Custom attribute:
public class AuthorizeRolesAttribute : AuthorizeAttribute
{
public AuthorizeRolesAttribute(params MyRoles[] allowedRoles)
{
var allowedRolesAsStrings = allowedRoles.Select(x => Enum.GetName(typeof(MyRoles), x));
Roles = string.Join(",", allowedRolesAsStrings);
}
}
Used like so:
[AuthorizeRoles(MyRoles.Admin, MyRoles.User)]
public ActionResult MyAction()
{
return View();
}
Try
public class CustomAuthorize : AuthorizeAttribute
{
public enum Role
{
DomainName_My_Group_Name,
DomainName_My_Other_Group_Name
}
public CustomAuthorize(params Role[] DomainRoles)
{
foreach (var domainRole in DomainRoles)
{
var domain = domainRole.ToString().Split('_')[0] + "_";
var role = domainRole.ToString().Replace(domain, "").Replace("_", " ");
domain=domain.Replace("_", "\\");
Roles += ", " + domain + role;
}
Roles = Roles.Substring(2);
}
}
public class HomeController : Controller
{
[CustomAuthorize(Role.DomainName_My_Group_Name, Role.DomainName_My_Other_Group_Name)]
public ActionResult Index()
{
return View();
}
}
Here's my version, based on #CalebHC and #Lee Harold's answers.
I've followed the style of using named parameters in the attribute and overridden the base classes Roles property.
#CalebHC's answer uses a new Is property which I think is unnecessary, because AuthorizeCore() is overridden (which in the base class uses Roles) so it makes sense to use our own Roles as well. By using our own Roles we get to write Roles = Roles.Admin on the controller, which follows the style of other .Net attributes.
I've used two constructors to CustomAuthorizeAttribute to show real active directory group names being passed in. In production I use the parameterised constructor to avoid magic strings in the class: group names are pulled from web.config during Application_Start() and passed in on creation using a DI tool.
You'll need a NotAuthorized.cshtml or similar in your Views\Shared folder or unauthorized users will get an error screen.
Here is the code for the base class AuthorizationAttribute.cs.
Controller:
public ActionResult Index()
{
return this.View();
}
[CustomAuthorize(Roles = Roles.Admin)]
public ActionResult About()
{
return this.View();
}
CustomAuthorizeAttribute:
// The left bit shifting gives the values 1, 2, 4, 8, 16 and so on.
[Flags]
public enum Roles
{
Admin = 1,
User = 1 << 1
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
private readonly string adminGroupName;
private readonly string userGroupName;
public CustomAuthorizeAttribute() : this("Domain Admins", "Domain Users")
{
}
private CustomAuthorizeAttribute(string adminGroupName, string userGroupName)
{
this.adminGroupName = adminGroupName;
this.userGroupName = userGroupName;
}
/// <summary>
/// Gets or sets the allowed roles.
/// </summary>
public new Roles Roles { get; set; }
/// <summary>
/// Checks to see if the user is authenticated and has the
/// correct role to access a particular view.
/// </summary>
/// <param name="httpContext">The HTTP context.</param>
/// <returns>[True] if the user is authenticated and has the correct role</returns>
/// <remarks>
/// This method must be thread-safe since it is called by the thread-safe OnCacheAuthorization() method.
/// </remarks>
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext == null)
{
throw new ArgumentNullException("httpContext");
}
if (!httpContext.User.Identity.IsAuthenticated)
{
return false;
}
var usersRoles = this.GetUsersRoles(httpContext.User);
return this.Roles == 0 || usersRoles.Any(role => (this.Roles & role) == role);
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
filterContext.Result = new ViewResult { ViewName = "NotAuthorized" };
}
private IEnumerable<Roles> GetUsersRoles(IPrincipal principal)
{
var roles = new List<Roles>();
if (principal.IsInRole(this.adminGroupName))
{
roles.Add(Roles.Admin);
}
if (principal.IsInRole(this.userGroupName))
{
roles.Add(Roles.User);
}
return roles;
}
}
To add to CalebHC's code and answer ssmith's question about handling users who have multiple roles...
Our custom security principal returns a string array representing all the groups/roles that a user is in. So first we have to convert all the strings in the array that match items in the enum. Finally, we look for any match - if so, then the user is authorized.
Note that we're also redirecting an unauthorized user to a custom "NotAuthorized" view.
The whole class looks like this:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
/// <summary>
/// Add the allowed roles to this property.
/// </summary>
public Roles Is { get; set; }
/// <summary>
/// Checks to see if the user is authenticated and has the
/// correct role to access a particular view.
/// </summary>
/// <param name="httpContext"></param>
/// <returns></returns>
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext == null)
throw new ArgumentNullException("httpContext");
if (!httpContext.User.Identity.IsAuthenticated)
return false;
var iCustomPrincipal = (ICustomPrincipal) httpContext.User;
var roles = iCustomPrincipal.CustomIdentity
.GetGroups()
.Select(s => Enum.Parse(typeof (Roles), s))
.ToArray();
if (Is != 0 && !roles.Cast<Roles>().Any(role => ((Is & role) == role)))
{
return false;
}
return true;
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (filterContext == null)
throw new ArgumentNullException("filterContext");
filterContext.Result = new ViewResult { ViewName = "NotAuthorized" };
}
}
Or you could concatenate like:
[Authorize(Roles = Common.Lookup.Item.SecurityRole.Administrator + "," + Common.Lookup.Item.SecurityRole.Intake)]

Resources