基于controller/action Authorize属性的MVC UI Pruning

发布于 2024-12-14 05:02:34 字数 837 浏览 5 评论 0原文

我有一个安全应用程序,每个操作都有一个 Authorize 属性。

[Authorize(Roles = "Role1,Role2")]
public ActionResult MyAction(int id)
{
    return View();
}

在我的用户界面中,我有这些控制器/操作的链接。我想为接受控制器和操作名称的链接创建一个自定义 HtmlHelper:

@Html.SecuredLink("Click Me", "MyAction", "MyController");

这将根据用户是否有权执行给定操作来确定是否渲染自身的天气:

public static MvcHtmlString SecuredLink(this HtmlHelper helper, string text, string action, string controller)
{        
    var userId = Membership.GetUserId();

    var userHasRightsToThisAction = IsActionAccessibleToUser(helper.ViewContext.RequestContext.HttpContext, controller, action); // <- How would this work?

    if (userHasRightsToThisAction )
    {
       // Render Link
       // ...
    }
}

我一直无法找到一种方法来轻松地从代码中测试操作的授权状态。

I have an secure application with an Authorize attribute on each action.

[Authorize(Roles = "Role1,Role2")]
public ActionResult MyAction(int id)
{
    return View();
}

In my UI, I have links to these controller/actions. I would like to create a custom HtmlHelper for the links that accepts controller and action names:

@Html.SecuredLink("Click Me", "MyAction", "MyController");

And this would determine weather to render itself or not based on if the user has permission to the given action:

public static MvcHtmlString SecuredLink(this HtmlHelper helper, string text, string action, string controller)
{        
    var userId = Membership.GetUserId();

    var userHasRightsToThisAction = IsActionAccessibleToUser(helper.ViewContext.RequestContext.HttpContext, controller, action); // <- How would this work?

    if (userHasRightsToThisAction )
    {
       // Render Link
       // ...
    }
}

I have been unable to find a way to easily test the action from code for authorization status.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

烟凡古楼 2024-12-21 05:02:34

好的找到了解决方案。在深入研究了我知道会进行安全调整的 MvcSiteMap 后,我发现了这篇关于它的文章:

http://blog.maartenballiauw.be/post/2008/08/29/Building-an-ASPNET-MVC-sitemap-provider-with-security-trimming.aspx

我使用了稍微修改一下这段代码,以创建给我所需结果的方法:

    /// <summary>
    /// Determine if a controller/action is accessible for a user
    /// </summary>
    /// <param name="context">Current HttpContext</param>
    /// <param name="controllerName">Target controller</param>
    /// <param name="actionName">Target action</param>
    /// <returns>True/false if the action is accessible</returns>
    public static bool IsActionAccessibleToUser(HttpContextBase context, string controllerName, string actionName)
    {
        // Find current handler
        MvcHandler handler = context.Handler as MvcHandler;

        if (handler != null)
        {
            // try to figure out the controller class
            IController controller = null;
            try
            {
                controller = ControllerBuilder.Current.GetControllerFactory().CreateController(handler.RequestContext, controllerName);                    
            }
            catch (System.Web.HttpException e)
            {
                throw new Exception("The controller '" + controllerName + "Controller' was not found.", e);
            }

            // Find all AuthorizeAttributes on the controller class and action method
            object[] controllerAttributes = controller.GetType().GetCustomAttributes(typeof(AuthorizeAttribute), true);
            object[] actionAttributes = controller.GetType().GetMethod(actionName).GetCustomAttributes(typeof(AuthorizeAttribute), true);

            // No attributes, then the action is open to all
            if (controllerAttributes.Length == 0 && actionAttributes.Length == 0) return true;

            // Find out current principal
            IPrincipal principal = handler.RequestContext.HttpContext.User;

            // Do we pass the roles for the controller?
            string roles = "";
            if (controllerAttributes.Length > 0)
            {
                AuthorizeAttribute attribute = controllerAttributes[0] as AuthorizeAttribute;
                roles = attribute.Roles;

                if (!PassRoleValidation(principal, roles)) return false;
            }

            // Do we pass the roles for the action?
            if (actionAttributes.Length > 0)
            {
                AuthorizeAttribute attribute = actionAttributes[0] as AuthorizeAttribute;
                roles = attribute.Roles;

                if (!PassRoleValidation(principal, roles)) return false;
            }

            return true;
        }

        return false;
    }

    private static bool PassRoleValidation(IPrincipal principal, string roles)
    {
        // no roles, then all we need to be is authenticated
        if (string.IsNullOrEmpty(roles) && principal.Identity.IsAuthenticated) return true;

        string[] roleArray = roles.Split(',');

        // if role contains "*", it's open to all
        if (roleArray.Any(role => role == "*")) return true;

        // Determine if the current user is allowed to access the current node
        if (roleArray.Any(principal.IsInRole)) return true;

        return false;
    }

Ok found a solution. After digging around the MvcSiteMap which I know does security trimming, I found this article about it:

http://blog.maartenballiauw.be/post/2008/08/29/Building-an-ASPNET-MVC-sitemap-provider-with-security-trimming.aspx

I used a bit of this code, modified slightly, to create the method that gives me the desired result:

    /// <summary>
    /// Determine if a controller/action is accessible for a user
    /// </summary>
    /// <param name="context">Current HttpContext</param>
    /// <param name="controllerName">Target controller</param>
    /// <param name="actionName">Target action</param>
    /// <returns>True/false if the action is accessible</returns>
    public static bool IsActionAccessibleToUser(HttpContextBase context, string controllerName, string actionName)
    {
        // Find current handler
        MvcHandler handler = context.Handler as MvcHandler;

        if (handler != null)
        {
            // try to figure out the controller class
            IController controller = null;
            try
            {
                controller = ControllerBuilder.Current.GetControllerFactory().CreateController(handler.RequestContext, controllerName);                    
            }
            catch (System.Web.HttpException e)
            {
                throw new Exception("The controller '" + controllerName + "Controller' was not found.", e);
            }

            // Find all AuthorizeAttributes on the controller class and action method
            object[] controllerAttributes = controller.GetType().GetCustomAttributes(typeof(AuthorizeAttribute), true);
            object[] actionAttributes = controller.GetType().GetMethod(actionName).GetCustomAttributes(typeof(AuthorizeAttribute), true);

            // No attributes, then the action is open to all
            if (controllerAttributes.Length == 0 && actionAttributes.Length == 0) return true;

            // Find out current principal
            IPrincipal principal = handler.RequestContext.HttpContext.User;

            // Do we pass the roles for the controller?
            string roles = "";
            if (controllerAttributes.Length > 0)
            {
                AuthorizeAttribute attribute = controllerAttributes[0] as AuthorizeAttribute;
                roles = attribute.Roles;

                if (!PassRoleValidation(principal, roles)) return false;
            }

            // Do we pass the roles for the action?
            if (actionAttributes.Length > 0)
            {
                AuthorizeAttribute attribute = actionAttributes[0] as AuthorizeAttribute;
                roles = attribute.Roles;

                if (!PassRoleValidation(principal, roles)) return false;
            }

            return true;
        }

        return false;
    }

    private static bool PassRoleValidation(IPrincipal principal, string roles)
    {
        // no roles, then all we need to be is authenticated
        if (string.IsNullOrEmpty(roles) && principal.Identity.IsAuthenticated) return true;

        string[] roleArray = roles.Split(',');

        // if role contains "*", it's open to all
        if (roleArray.Any(role => role == "*")) return true;

        // Determine if the current user is allowed to access the current node
        if (roleArray.Any(principal.IsInRole)) return true;

        return false;
    }
最美不过初阳 2024-12-21 05:02:34

好的,快速而肮脏的解决方案:

准备一个用于构建 URL 服务器端的函数,

这样可能是最好的选择:

public static string GetUrl(string Action, string Controller, object RouteValues) {
    UrlHelper Url = new UrlHelper(HttpContext.Current.Request.RequestContext);
    return Url.Action(Action, Controller, RouteValues);
}

在你的帮助器中,获取用户身份验证信息并返回构建的 url 或 string.Empty。

public static string SecureLink(this HtmlHelper helper, string Action, string Controller, object RouteValues)
{
  YourUserObject LoggedUser = /* Whatever you need to obtain your UserId */
  if (LoggedUser.IsSuperUser) {
    return GetUrl(Action, Controller, RouteValues);
  }
  return string.empty;
}

如果您的结果是 HTML 编码的,只需使用 MvcHtmlString 代替字符串作为返回值。
否则请注意,您可能需要使用 @Html.Raw 来发出它。

PS:显然我没有在此示例代码中添加完整的 生成,由您决定需要哪些参数(将它们添加到 Helper 签名中),我通常复制其他 @Html 帮助程序的签名(例如名称、值和 HtmlAttributes 列表)。

Ok, quick and dirt solution:

prepare a function for building the Urls server side

something like this will probably be the best choice:

public static string GetUrl(string Action, string Controller, object RouteValues) {
    UrlHelper Url = new UrlHelper(HttpContext.Current.Request.RequestContext);
    return Url.Action(Action, Controller, RouteValues);
}

In your helper, obtain User Authentication infos and return the built url or string.Empty.

public static string SecureLink(this HtmlHelper helper, string Action, string Controller, object RouteValues)
{
  YourUserObject LoggedUser = /* Whatever you need to obtain your UserId */
  if (LoggedUser.IsSuperUser) {
    return GetUrl(Action, Controller, RouteValues);
  }
  return string.empty;
}

If your result is HTML Encoded just use MvcHtmlString in place of string as a return value.
Otherwise beware, you may need to use @Html.Raw for emitting it.

PS: obviously I've not added full <a href .../> generation in this sample code, is up to you decide which parameters are need (add them to the Helper signature), I usually copy the signatures of other @Html helpers (so Name, Value and a list of HtmlAttributes).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文