我正在尝试为MVC的htmlHelper创建一个扩展方法。 目的是基于控制器/操作上设置的AuthorizeAttribute启用或禁用ActionLink。 借用MVCSitemap Maarten Balliauw创建的代码,我想在决定如何呈现actionlink之前验证用户对控制器/操作的权限。 当我尝试获取MvcHandler时,我得到一个空值。 是否有更好的方法来控制器/操作的属性?
以下是扩展方法的代码:
public static class HtmlHelperExtensions
{
public static string SecurityTrimmedActionLink(this HtmlHelper htmlHelper, string linkText, string action, string controller)
{
//simplified for brevity
if (IsAccessibleToUser(action, controller))
{
return htmlHelper.ActionLink(linkText, action,controller);
}
else
{
return String.Format("<span>{0}</span>",linkText);
}
}
public static bool IsAccessibleToUser(string action, string controller)
{
HttpContext context = HttpContext.Current;
MvcHandler handler = context.Handler as MvcHandler;
IController verifyController =
ControllerBuilder
.Current
.GetControllerFactory()
.CreateController(handler.RequestContext, controller);
object[] controllerAttributes = verifyController.GetType().GetCustomAttributes(typeof(AuthorizeAttribute), true);
object[] actionAttributes = verifyController.GetType().GetMethod(action).GetCustomAttributes(typeof(AuthorizeAttribute), true);
if (controllerAttributes.Length == 0 && actionAttributes.Length == 0)
return true;
IPrincipal principal = handler.RequestContext.HttpContext.User;
string roles = "";
string users = "";
if (controllerAttributes.Length > 0)
{
AuthorizeAttribute attribute = controllerAttributes[0] as AuthorizeAttribute;
roles += attribute.Roles;
users += attribute.Users;
}
if (actionAttributes.Length > 0)
{
AuthorizeAttribute attribute = actionAttributes[0] as AuthorizeAttribute;
roles += attribute.Roles;
users += attribute.Users;
}
if (string.IsNullOrEmpty(roles) && string.IsNullOrEmpty(users) && principal.Identity.IsAuthenticated)
return true;
string[] roleArray = roles.Split(',');
string[] usersArray = users.Split(',');
foreach (string role in roleArray)
{
if (role != "*" && !principal.IsInRole(role)) return false;
}
foreach (string user in usersArray)
{
if (user != "*" && (principal.Identity.Name == "" || principal.Identity.Name != user)) return false;
}
return true;
}
}
答案 0 :(得分:4)
以下是工作代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Security.Principal;
using System.Web.Routing;
using System.Web.Mvc;
using System.Collections;
using System.Reflection;
namespace System.Web.Mvc.Html
{
public static class HtmlHelperExtensions
{
public static string SecurityTrimmedActionLink(
this HtmlHelper htmlHelper,
string linkText,
string action,
string controller)
{
return SecurityTrimmedActionLink(htmlHelper, linkText, action, controller, false);
}
public static string SecurityTrimmedActionLink(this HtmlHelper htmlHelper, string linkText, string action, string controller, bool showDisabled)
{
if (IsAccessibleToUser(action, controller))
{
return htmlHelper.ActionLink(linkText, action, controller);
}
else
{
return showDisabled ? String.Format("<span>{0}</span>", linkText) : "";
}
}
public static bool IsAccessibleToUser(string actionAuthorize, string controllerAuthorize)
{
Assembly assembly = Assembly.GetExecutingAssembly();
GetControllerType(controllerAuthorize);
Type controllerType = GetControllerType(controllerAuthorize);
var controller = (IController)Activator.CreateInstance(controllerType);
ArrayList controllerAttributes = new ArrayList(controller.GetType().GetCustomAttributes(typeof(AuthorizeAttribute), true));
ArrayList actionAttributes = new ArrayList();
MethodInfo[] methods = controller.GetType().GetMethods();
foreach (MethodInfo method in methods)
{
object[] attributes = method.GetCustomAttributes(typeof(ActionNameAttribute), true);
if ((attributes.Length == 0 && method.Name == actionAuthorize) || (attributes.Length > 0 && ((ActionNameAttribute)attributes[0]).Name == actionAuthorize))
{
actionAttributes.AddRange(method.GetCustomAttributes(typeof(AuthorizeAttribute), true));
}
}
if (controllerAttributes.Count == 0 && actionAttributes.Count == 0)
return true;
IPrincipal principal = HttpContext.Current.User;
string roles = "";
string users = "";
if (controllerAttributes.Count > 0)
{
AuthorizeAttribute attribute = controllerAttributes[0] as AuthorizeAttribute;
roles += attribute.Roles;
users += attribute.Users;
}
if (actionAttributes.Count > 0)
{
AuthorizeAttribute attribute = actionAttributes[0] as AuthorizeAttribute;
roles += attribute.Roles;
users += attribute.Users;
}
if (string.IsNullOrEmpty(roles) && string.IsNullOrEmpty(users) && principal.Identity.IsAuthenticated)
return true;
string[] roleArray = roles.Split(',');
string[] usersArray = users.Split(',');
foreach (string role in roleArray)
{
if (role == "*" || principal.IsInRole(role))
return true;
}
foreach (string user in usersArray)
{
if (user == "*" && (principal.Identity.Name == user))
return true;
}
return false;
}
public static Type GetControllerType(string controllerName)
{
Assembly assembly = Assembly.GetExecutingAssembly();
foreach (Type type in assembly.GetTypes())
{
if (type.BaseType.Name == "Controller" && (type.Name.ToUpper() == (controllerName.ToUpper() + "Controller".ToUpper())))
{
return type;
}
}
return null;
}
}
}
我不喜欢使用反射,但我无法访问ControllerTypeCache。
答案 1 :(得分:0)
您的ViewPage引用了视图上下文,因此您可以将其作为扩展方法。
然后你可以说,如果Request.IsAuthenticated或Request.User.IsInRole(...)
用法就像<%= this.SecurityLink(text, demandRole, controller, action, values) %>
答案 2 :(得分:0)
我真的很喜欢@ Robert的帖子中的代码,但是有一些错误,我想缓存角色和用户的聚集,因为反思可能需要花费一些时间。
修复了错误:如果同时存在Controller属性和Action属性,那么当角色连接在一起时,控制器角色和动作角色之间不会插入额外的逗号,而这些角色将无法正确分析。
[Authorize(Roles = "SuperAdmin,Executives")]
public class SomeController() {
[Authorize(Roles = "Accounting")]
public ActionResult Stuff() {
}
}
然后角色字符串最终为SuperAdmin,ExecutivesAccounting
,我的版本确保了高管和会计是分开的。
我的新代码也忽略了HttpPost操作上的Auth,因为这可能会让事情失败,尽管不太可能。
最后,对于较新版本的MVC,它会返回MvcHtmlString
而不是string
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Reflection;
using System.Collections;
using System.Web.Mvc;
using System.Web.Mvc.Html;
using System.Security.Principal;
public static class HtmlHelperExtensions
{
/// <summary>
/// only show links the user has access to
/// </summary>
/// <returns></returns>
public static MvcHtmlString SecurityLink(this HtmlHelper htmlHelper, string linkText, string action, string controller, bool showDisabled = false)
{
if (IsAccessibleToUser(action, controller))
{
return htmlHelper.ActionLink(linkText, action, controller);
}
else
{
return new MvcHtmlString(showDisabled ? String.Format("<span>{0}</span>", linkText) : "");
}
}
/// <summary>
/// reflection can be kinda slow, lets cache auth info
/// </summary>
private static Dictionary<string, Tuple<string[], string[]>> _controllerAndActionToRolesAndUsers = new Dictionary<string, Tuple<string[], string[]>>();
private static Tuple<string[], string[]> GetAuthRolesAndUsers(string actionName, string controllerName)
{
var controllerAndAction = controllerName + "~~" + actionName;
if (_controllerAndActionToRolesAndUsers.ContainsKey(controllerAndAction))
return _controllerAndActionToRolesAndUsers[controllerAndAction];
Type controllerType = GetControllerType(controllerName);
MethodInfo matchingMethodInfo = null;
foreach (MethodInfo method in controllerType.GetMethods())
{
if (method.GetCustomAttributes(typeof(HttpPostAttribute), true).Any())
continue;
if (method.GetCustomAttributes(typeof(HttpPutAttribute), true).Any())
continue;
if (method.GetCustomAttributes(typeof(HttpDeleteAttribute), true).Any())
continue;
var actionNameAttr = method.GetCustomAttributes(typeof(ActionNameAttribute), true).Cast<ActionNameAttribute>().FirstOrDefault();
if ((actionNameAttr == null && method.Name == actionName) || (actionNameAttr != null && actionNameAttr.Name == actionName))
{
matchingMethodInfo = method;
}
}
if (matchingMethodInfo == null)
return new Tuple<string[], string[]>(new string[0], new string[0]);
var authAttrs = new List<AuthorizeAttribute>();
authAttrs.AddRange(controllerType.GetCustomAttributes(typeof(AuthorizeAttribute), true).Cast<AuthorizeAttribute>());
var roles = new List<string>();
var users = new List<string>();
foreach(var authAttr in authAttrs)
{
roles.AddRange(authAttr.Roles.Split(','));
users.AddRange(authAttr.Roles.Split(','));
}
var rolesAndUsers = new Tuple<string[], string[]>(roles.ToArray(), users.ToArray());
try
{
_controllerAndActionToRolesAndUsers.Add(controllerAndAction, rolesAndUsers);
}
catch (System.ArgumentException ex)
{
//possible but unlikely that two threads hit this code at the exact same time and enter a race condition
//instead of using a mutex, we'll just swallow the exception when the method gets added to dictionary
//for the second time. mutex only allow single worker regardless of which action method they're getting
//auth for. doing it this way eliminates permanent bottleneck in favor of a once in a bluemoon time hit
}
return rolesAndUsers;
}
public static bool IsAccessibleToUser(string actionName, string controllerName)
{
var rolesAndUsers = GetAuthRolesAndUsers(actionName, controllerName);
var roles = rolesAndUsers.Item1;
var users = rolesAndUsers.Item2;
IPrincipal principal = HttpContext.Current.User;
if (!roles.Any() && !users.Any() && principal.Identity.IsAuthenticated)
return true;
foreach (string role in roles)
{
if (role == "*" || principal.IsInRole(role))
return true;
}
foreach (string user in users)
{
if (user == "*" && (principal.Identity.Name == user))
return true;
}
return false;
}
public static Type GetControllerType(string controllerName)
{
Assembly assembly = Assembly.GetExecutingAssembly();
foreach (Type type in assembly.GetTypes())
{
if (type.BaseType.Name == "Controller" && (type.Name.ToUpper() == (controllerName.ToUpper() + "Controller".ToUpper())))
{
return type;
}
}
return null;
}
}