How to override the ActionLink behavior
Asked Answered
T

1

5

Ok, I want to add some security to my site via the ActionLink method. If the user has enough rights to access the action/controller then the ActionLink should render the link. If not, It should return an empty string. Now, the ActionLink is a static method and that makes it all more difficult. Is there any way to achieve what im trying to do?

Tough answered 29/3, 2011 at 18:13 Comment(3)
That isn't very good security. If the user can figure out what the link is, they can just enter it directly. Hiding the link can be an extra thing to do, but your real security should take place on the server.Osculate
Yeah, I did that, it's awesome when it's all wired up. I'll dig up some code...Hannan
@Matt, not true if done properly. You can create ActionLinks that query the Authorize attribute from the class/ method before rendering the link at all. So you can control the visibility of the link and access to the method with nothing more than the Authorize attribute.Hannan
H
8

new AuthorizeActionLink extension method. Overload as needed.

public static MvcHtmlString AuthorizeActionLink(this HtmlHelper helper, string linkText, string actionName, string controllerName, object routeValues, object htmlAttributes)
{
    if (HasActionPermission(helper, actionName, controllerName))
        return helper.ActionLink(linkText, actionName, controllerName, routeValues, htmlAttributes);

    return MvcHtmlString.Empty;
}

public static MvcHtmlString AuthorizeActionLink(this HtmlHelper helper, string linkText, string actionName, string controllerName, RouteValueDictionary routeValues, IDictionary<string, object> htmlAttributes)
{
    if (HasActionPermission(helper, actionName, controllerName))
        return helper.ActionLink(linkText, actionName, controllerName, routeValues, htmlAttributes);

    return MvcHtmlString.Empty;
}

methods that do the dirty work in figuring out if the user is Authorized

static bool HasActionPermission(this HtmlHelper htmlHelper, string actionName, string controllerName)
{
    ControllerBase controllerToLinkTo = string.IsNullOrEmpty(controllerName)
        ? htmlHelper.ViewContext.Controller
        : GetControllerByName(htmlHelper, controllerName);

    ControllerContext controllerContext = new ControllerContext(htmlHelper.ViewContext.RequestContext, controllerToLinkTo);
    ReflectedControllerDescriptor controllerDescriptor = new ReflectedControllerDescriptor(controllerToLinkTo.GetType());
    ActionDescriptor actionDescriptor = controllerDescriptor.FindAction(controllerContext, actionName);

    return ActionIsAuthorized(controllerContext, actionDescriptor);
}

static bool ActionIsAuthorized(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
{
    if (actionDescriptor == null)
        return false;

    AuthorizationContext authContext = new AuthorizationContext(controllerContext, actionDescriptor);
    foreach (IAuthorizationFilter authFilter in actionDescriptor.GetFilters().AuthorizationFilters)
    {
        authFilter.OnAuthorization(authContext);

        if (authContext.Result != null)
            return false;
    }

    return true;
}

static ControllerBase GetControllerByName(HtmlHelper helper, string controllerName)
{
    IControllerFactory factory = ControllerBuilder.Current.GetControllerFactory();

    IController controller = factory.CreateController(helper.ViewContext.RequestContext, controllerName);

    if (controller == null)
    {
        throw new InvalidOperationException(
            string.Format(
                CultureInfo.CurrentUICulture,
                "Controller factory {0} controller {1} returned null",
                factory.GetType(),
                controllerName));
    }

    return (ControllerBase)controller;
}
Hannan answered 29/3, 2011 at 18:21 Comment(6)
so there is no way to do it with the same Html.ActionLink() method?Tough
no, but I think this is a great reusable way to do this type of conditional link display across your whole applicationHannan
@Hannan if we have edit link on users with 15 users listed on the page this whole code would execute 15 times. isn't it better to just calulate haspermission value in controller and push it on the view through some viewmodelRichmound
@Muhammad There's no reason this couldn't be extended by adding the result of HasActionPermission in HttpContext.Items for any given action/controller combination. That's actually a good idea.Hannan
@Hannan can you give me a starting point, i mean how to approach itRichmound
in the HasActionPermission do a check against the HttpContext.Items colletion to see if the action and controller exist. If so, return that, if not, continue and check, then save to the context.Hannan

© 2022 - 2024 — McMap. All rights reserved.