ASP.NET MVC 4 Custom Role Authorization show / hide Edit / Remove links in views

I want to show / hide Edit / Remove links (including menu items) depending on user authorization. I have applied AuthorizeAttribute and custom logic to validate roles in AuthorizeCore override. I would like to use this logic by checking if the user has permission to view edit / delete links inside the LinkExtensions method. This is my setup:

public class AuthorizeActivity : AuthorizeAttribute
{
    public override void OnAuthorization(AuthorizationContext filterContext)
    {
        base.OnAuthorization(filterContext);
    }

    protected override bool AuthorizeCore(System.Web.HttpContextBase httpContext)
    {
        bool isAuthorized = base.AuthorizeCore(httpContext);
        string actionType = httpContext.Request.HttpMethod;

        string controller = httpContext.Request.RequestContext.RouteData.Values["controller"].ToString();
        string action = httpContext.Request.RequestContext.RouteData.Values["action"].ToString();

        //ADMINS
        if (controller == "Admin")
        {
            if (httpContext.User.IsInRole(Constants.Admin))
                return true;
        }
        else
        {
            //DATA READERS ONLY
            if ((action == "Details") || (action == "Index"))
            {
                if (httpContext.User.IsInRole(Constants.DataReader))
                    return true;
            }
            //DATA WRITERS & IT
            else
            {
              ...
            }
        }
        return false;
    }

      

Also I used Vivien Chevallier logic to create the allowed link action extension described here: http://vivien-chevallier.com/Articles/create-an-authorized-action-link-extension-for-aspnet-mvc-3 Now, at my opinion i can use:

<li>@Html.ActionLinkAuthorized("Admin", "Index", "Admin",false) </li>

      

And the link will either be displayed or not depend on the user's rights. In my controller, the action is decorated with:

    [AuthorizeActivity]
    public ActionResult Index()
    {
        return View(view);
    }

      

Authorized link won't work unless I also specify "Roles" in the attribute, which I think is redundant, for example:

[AuthorizeActivity(Roles = Constants.roleSalesContractAdmin)]
public ActionResult Index()
{
    return View(view);
}

      

I can't seem to find a way to reuse logic in the AuthorizeAttribute. Ideally, this would be called in ActionLinkAuthorized like Vivien:

public static MvcHtmlString ActionLinkAuthorized(this HtmlHelper htmlHelper, string linkText, string actionName, string controllerName, RouteValueDictionary routeValues, IDictionary<string, object> htmlAttributes, bool showActionLinkAsDisabled)
    {
        if (htmlHelper.ActionAuthorized(actionName, controllerName)) //The call to verify here -- or inside ActionAuthorized
        {
            return htmlHelper.ActionLink(linkText, actionName, controllerName, routeValues, htmlAttributes);
        }
        else
        {
            if (showActionLinkAsDisabled)
            {
                TagBuilder tagBuilder = new TagBuilder("span");
                tagBuilder.InnerHtml = linkText;
                return MvcHtmlString.Create(tagBuilder.ToString());
            }
            else
            {
                return MvcHtmlString.Empty;
            }
        }
    }

      

This is the ActionAuthorized method. OnAuthorization call does not go to configured

public static bool ActionAuthorized(this HtmlHelper htmlHelper, string actionName, string controllerName)
    {
        ControllerBase controllerBase = string.IsNullOrEmpty(controllerName) ? htmlHelper.ViewContext.Controller : htmlHelper.GetControllerByName(controllerName);
        ControllerContext controllerContext = new ControllerContext(htmlHelper.ViewContext.RequestContext, controllerBase);
        ControllerDescriptor controllerDescriptor = new ReflectedControllerDescriptor(controllerContext.Controller.GetType());
        ActionDescriptor actionDescriptor = controllerDescriptor.FindAction(controllerContext, actionName);

        if (actionDescriptor == null)
            return false;
        FilterInfo filters = new FilterInfo(FilterProviders.Providers.GetFilters(controllerContext, actionDescriptor));

        AuthorizationContext authorizationContext = new AuthorizationContext(controllerContext, actionDescriptor);
        foreach (IAuthorizationFilter authorizationFilter in filters.AuthorizationFilters)
        {
            authorizationFilter.OnAuthorization(authorizationContext); //This call
            if (authorizationContext.Result != null)
                return false;
        }
        return true;
    }

      

+3


source to share


3 answers


In your opinion, you can write:

@if (User.IsInRole("role"))
{
    <li>@Html.ActionLink("Words", "View", "Controller")</li>
    <li>@Html.ActionLink("Words", "View", "Controller")</li>
}

      



... and assuming they are logged in, this will conditionally hide the links

+6


source


When you decorate an action or controller with an authorization attribute, the action is only executed when the user is authorized. This means that if the user is not logged in, the view (which will contain all of your authorized link extensions) will not be displayed at all.

Because of this, you need to separate your authorization logic from your attribute and logic for html extensions.

I also noticed that in your attribute's authorization core, you do the following:

if ((action == "Details") || (action == "Index"))
            {
                if (httpContext.User.IsInRole(Constants.DataReader))
                    return true;
            }

      

This is a really, really bad idea ! You shouldn't include action names in your core authorization logic! All you have to do is decorate the "Details" and "Index" methods with a default authorization attribute that has the appropriate roles:



[Authorize(Roles=Constants.DataReader)]
public ActionResult Index()
{
}

      

Now about the role of dependent helpers:

you can do something like this:

public static MvcHtmlString ActionLinkAuthorized(this HtmlHelper htmlHelper, string roles, other arguments)
{
   //assuming that roles are passed as coma separated strings
   var rolesList = roles.Split(",",roles);
   bool shouldShow = false;
   foreach(var role in rolesList )
   {
       if (HttpContext.User.IsInRole(role))
       {
           shouldShow = true;
           break;
       }               
   }
   if(shouldShow)
   {
       //return your extension representation 
   }
   else
   {
       //fallback 
   }
}

      

+1


source


I had a similar problem I solved it like this:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
    public class MyAuthorizedAttribute : AuthorizeAttribute
    {
        public bool CheckPermissions(HttpContextBase httpContext, string controller, string action)
        {
            bool authorized;

            //Validate User permissions of the way you think is best

            return authorized;
        }

        public override void OnAuthorization(AuthorizationContext filterContext)
        {
            var action = filterContext.ActionDescriptor.ActionName;
            var controller = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName;
            if (filterContext == null)
            {
                throw new ArgumentNullException(nameof(filterContext));
            }

            if (OutputCacheAttribute.IsChildActionCacheActive(filterContext))
            {
                // If a child action cache block is active, we need to fail immediately, even if authorization
                // would have succeeded. The reason is that there no way to hook a callback to rerun
                // authorization before the fragment is served from the cache, so we can't guarantee that this
                // filter will be re-run on subsequent requests.
                throw new InvalidOperationException("AuthorizeAttribute Cannot Use Within Child Action Cache");
            }

            var skipAuthorization = filterContext.ActionDescriptor.IsDefined(typeof (AllowAnonymousAttribute), true)
                                    ||
                                    filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(
                                        typeof (AllowAnonymousAttribute), true);

            if (skipAuthorization)
            {
                return;
            }

            if (AuthorizeCore(filterContext.HttpContext) && CheckPermissions(filterContext.HttpContext, controller, action))
            {
                // ** IMPORTANT **
                // Since we're performing authorization at the action level, the authorization code runs
                // after the output caching module. In the worst case this could allow an authorized user
                // to cause the page to be cached, then an unauthorized user would later be served the
                // cached page. We work around this by telling proxies not to cache the sensitive page,
                // then we hook our custom authorization code into the caching mechanism so that we have
                // the final say on whether a page should be served from the cache.

                var cachePolicy = filterContext.HttpContext.Response.Cache;
                cachePolicy.SetProxyMaxAge(new TimeSpan(0));
                cachePolicy.AddValidationCallback(CacheValidateHandler, null /* data */);
            }
            else
            {
                HandleUnauthorizedRequest(filterContext);
            }
        }

        private void CacheValidateHandler(HttpContext context, object data, ref HttpValidationStatus validationStatus)
        {
            validationStatus = OnCacheAuthorization(new HttpContextWrapper(context));
        }

        protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
        {
            if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
            {
                base.HandleUnauthorizedRequest(filterContext);
            }
            else
            {
                filterContext.Result =
                    new RedirectToRouteResult(
                        new RouteValueDictionary(new {controller = "Error", action = "Unauthorized"}));
            }
        }
    }

      

So Vivienne Chevalier's logic worked fine

0


source







All Articles