2008-09-23 48 views
4

我想爲MVC的htmlHelper創建一個擴展方法。 其目的是基於控制器/操作上設置的AuthorizeAttribute來啓用或禁用ActionLink。 借用MVCSitemap
Maarten Balliauw創建的代碼,我想在決定如何呈現actionlink之前驗證用戶對控制器/操作的權限。 當我嘗試獲取MvcHandler時,我得到一個空值。 有沒有更好的方法來控制器/操作的屬性?Stuck創建一個「安全修剪」html.ActionLink擴展方法

下面是擴展方法的代碼:

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; 
    } 

} 

回答

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。

0

你的ViewPage有觀點上下文的引用,所以你可以把它放在那,而不是擴展方法。

然後,你可以說,如果Request.IsAuthenticated或Request.User.IsInRole(...)

使用會像<%= this.SecurityLink(text, demandRole, controller, action, values) %>

+0

的新版本,我試圖讓從AuthorizeAttribute角色來比較它們的用戶角色。我不確定這是怎麼做到的。 – 2008-09-23 15:02:57

+0

問題是,一旦在AuthorizeAttribute中指定了角色,就不必再將它們添加到每個單獨的鏈接中。 – 2008-09-23 15:12:21

0

我真的很喜歡@ Robert的帖子中的代碼,但是有一些錯誤,我想緩存角色和用戶的聚會,因爲反射會花費一點時間。

錯誤修正:如果同時存在Controller屬性和Action屬性,那麼當角色被連接時,不會在控制器的角色和操作的角色之間插入額外的逗號,從而無法正確分析角色。

[Authorize(Roles = "SuperAdmin,Executives")] 
public class SomeController() { 
    [Authorize(Roles = "Accounting")]  
    public ActionResult Stuff() { 
    } 
} 

那麼角色字符串最終被SuperAdmin,ExecutivesAccounting,我的版本保證了管理人員和會計是分開的。

我的新代碼還會忽略HttpPost操作上的Auth,因爲這可能會導致事情發生,儘管不太可能。

最後,它返回MvcHtmlString,而不是string爲MVC

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; 
    } 


}