2009-07-18 9 views
27

Ho un controller e voglio due ruoli per essere in grado di accedervi. 1-admin o 2 moderatoreasp.net mvc decorare [Autorizza()] con più enumerazioni

So che si può fare [Autorizza (roles = "admin, moderatori")], ma ho i miei ruoli in un enum. Con l'enum posso solo autorizzare UN ruolo. Non riesco a capire come autorizzare due.

Ho provato qualcosa di simile [Autorizza (Ruoli = MyEnum.Admin, MyEnum.Moderator)], ma che sogliono compilazione.

Qualcuno una volta ha suggerito questa:

[Authorize(Roles=MyEnum.Admin)] 
[Authorize(MyEnum.Moderator)] 
public ActionResult myAction() 
{ 
} 

ma non funziona come un OR. Credo che in questo caso l'utente deve essere parte di entrambi i ruoli. Sto trascurando qualche sintassi? O è questo un caso in cui devo rilasciare la mia autorizzazione personalizzata?

+5

Interessante. Come sei riuscito a far funzionare tutto questo anche con un solo ruolo in un enum? MyEnum.Admin restituisce una stringa? Sto cercando di fare la stessa cosa di te, e ho riscontrato un paio di problemi: - Non riesco a impostare un enum di tipo stringa. - Non riesco a chiamare ToString() sull'enumerazione, ad es. [Authorize (Roles = MyEnum.Admin.ToString())] Entrambi gli esempi precedenti mi danno errori del compilatore. Se puoi consigliare come funziona, sarebbe apprezzato. Grazie. –

+0

@JohnnyO - Ho lo stesso problema, sei riuscito a scoprire cosa stiamo facendo male? @codette - potresti darci un consiglio qui? – UpTheCreek

+0

Mi dispiace ma non sono mai andato con nessuna di queste soluzioni. Così ho cambiato il mio codice in modo che solo un ruolo debba essere controllato. Più è alto il loro ruolo e più possono fare. Ad esempio, un utente "normale" può fare alcune cose. Un "moderatore" può fare tutto ciò che un utente "normale" può e di più. Un "admin" può fare tutto ciò che un utente "normale" e "moderatore" possono e di più. – codette

risposta

29

provare a utilizzare il bit o operatore come questo:

[Authorize(Roles= MyEnum.Admin | MyEnum.Moderator)] 
public ActionResult myAction() 
{ 
} 

Se questo non funziona, si può solo rotolare il proprio. Al momento ho appena fatto questo sul mio progetto. Ecco quello che ho fatto:

public class AuthWhereRole : AuthorizeAttribute 
{ 
    /// <summary> 
    /// Add the allowed roles to this property. 
    /// </summary> 
    public UserRole Is; 

    /// <summary> 
    /// Checks to see if the user is authenticated and has the 
    /// correct role to access a particular view. 
    /// </summary> 
    /// <param name="httpContext"></param> 
    /// <returns></returns> 
    protected override bool AuthorizeCore(HttpContextBase httpContext) 
    { 
     if (httpContext == null) 
      throw new ArgumentNullException("httpContext"); 

     // Make sure the user is authenticated. 
     if (!httpContext.User.Identity.IsAuthenticated) 
      return false; 

     UserRole role = someUser.Role; // Load the user's role here 

     // Perform a bitwise operation to see if the user's role 
     // is in the passed in role values. 
     if (Is != 0 && ((Is & role) != role)) 
      return false; 

     return true; 
    } 
} 

// Example Use 
[AuthWhereRole(Is=MyEnum.Admin|MyEnum.Newbie)] 
public ActionResult Test() {} 

Inoltre, assicurarsi di aggiungere un flag di attributo per il tuo enum e assicurarsi che sono tutte valutate da 1 in su. Come questo:

[Flags] 
public enum Roles 
{ 
    Admin = 1, 
    Moderator = 1 << 1, 
    Newbie = 1 << 2 
    etc... 
} 

Lo spostamento bit sinistra riporta i valori 1, 2, 4, 8, 16 e così via.

Beh, spero che questo aiuta un po '.

+0

Perfetto! Proprio quello di cui avevo bisogno! Grazie. – Kamyar

+2

Cosa succede se l'utente potrebbe appartenere a più di un ruolo? – ssmith

+0

Quale valore devo avere nel mio database in modo da selezionare Admin, Moderatore o Principiante? (db.users.role = 1; 2; 4; 8 ...? – Misi

-1

Oppure si potrebbe concatenare come:

[Autorizza (Roles = Common.Lookup.Item.SecurityRole.Administrator + "" + Common.Lookup.Item.SecurityRole.Intake)]

0

Per aggiungere al codice di CalebHC e rispondi alla domanda di ssmith sulla gestione degli utenti che hanno più ruoli ...

Il nostro principio di sicurezza personalizzato restituisce una matrice di stringhe che rappresenta tutti i gruppi/ruoli in cui si trova un utente. Quindi, prima dobbiamo convertire tutte le stringhe nella matrice che corrisponde agli elementi nell'enumerazione. Infine, cerchiamo qualsiasi corrispondenza, se è così, l'utente è autorizzato.

Si noti che stiamo reindirizzando un utente non autorizzato a una vista personalizzata "NotAuthorized".

Tutta la classe si presenta così:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)] 
public class CustomAuthorizeAttribute : AuthorizeAttribute 
{ 
    /// <summary> 
    /// Add the allowed roles to this property. 
    /// </summary> 
    public Roles Is { get; set; } 

    /// <summary> 
    /// Checks to see if the user is authenticated and has the 
    /// correct role to access a particular view. 
    /// </summary> 
    /// <param name="httpContext"></param> 
    /// <returns></returns> 
    protected override bool AuthorizeCore(HttpContextBase httpContext) 
    { 
     if (httpContext == null) 
      throw new ArgumentNullException("httpContext"); 

     if (!httpContext.User.Identity.IsAuthenticated) 
      return false; 

     var iCustomPrincipal = (ICustomPrincipal) httpContext.User; 

     var roles = iCustomPrincipal.CustomIdentity 
         .GetGroups() 
         .Select(s => Enum.Parse(typeof (Roles), s)) 
         .ToArray(); 

     if (Is != 0 && !roles.Cast<Roles>().Any(role => ((Is & role) == role))) 
     { 
      return false; 
     } 

     return true; 
    } 

    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext) 
    { 
     if (filterContext == null) 
      throw new ArgumentNullException("filterContext"); 

     filterContext.Result = new ViewResult { ViewName = "NotAuthorized" }; 
    } 
} 
31

Ecco una soluzione semplice ed elegante che permette di utilizzare semplicemente la seguente sintassi:

[AuthorizeRoles(MyEnum.Admin, MyEnum.Moderator)] 

Quando si crea il proprio attributo, utilizza la parola chiave params nel tuo costruttore:

public class AuthorizeRoles : AuthorizeAttribute 
{ 
    public AuthorizeRoles(params MyEnum[] roles) 
    { 
     ... 
    } 
    protected override bool AuthorizeCore(HttpContextBase httpContext) 
    { 
     ... 
    } 
} 

Questo vi permetterà di utilizzare l'attributo come segue:

[AuthorizeRoles(MyEnum.Admin, MyEnum.Moderator)] 
public ActionResult myAction() 
{ 
} 
+6

Credo che questa dovrebbe essere una soluzione accettata, poiché funziona, e non si limitano a 32 ruoli al massimo nella risposta accettata. Grazie per questo. – Zoka

+0

Solo un suggerimento su quello che sta succedendo qui (bene - mi ha ingannato per 5 minuti!). Stai creando l'oggetto AuthorizeRoles e poi chiamerà AuthorizeCore per scoprire se concedere o meno l'accesso. Qualcosa del genere potrebbe aiutarti. http://stackoverflow.com/questions/1148312/ asp-net-mvc-decor-authorize-with-multiple-enums –

+0

@zoka Accetto Questa soluzione è migliore e più flessibile della mia :) :) – CalebHC

2

Prova

public class CustomAuthorize : AuthorizeAttribute 
{ 
    public enum Role 
    { 
     DomainName_My_Group_Name, 
     DomainName_My_Other_Group_Name 
    } 

    public CustomAuthorize(params Role[] DomainRoles) 
    { 
     foreach (var domainRole in DomainRoles) 
     { 
      var domain = domainRole.ToString().Split('_')[0] + "_"; 
      var role = domainRole.ToString().Replace(domain, "").Replace("_", " "); 
      domain=domain.Replace("_", "\\"); 
      Roles += ", " + domain + role; 
     } 
     Roles = Roles.Substring(2); 
    }  
} 

public class HomeController : Controller 
{ 
    [CustomAuthorize(Role.DomainName_My_Group_Name, Role.DomainName_My_Other_Group_Name)] 
    public ActionResult Index() 
    { 
     return View(); 
    } 
} 
1

Ecco la mia versione, basata su @CalebHC e risposte di @Lee Harold.

Ho seguito lo stile di utilizzo dei parametri con nome nell'attributo e ho sovrascritto le proprietà di base Roles.

@ risposta di CalebHC utilizza una nuova Is proprietà che credo sia inutile, perché AuthorizeCore() viene sostituita (che nella classe di base utilizza i ruoli) quindi ha senso usare la nostra Roles pure. Usando il nostro Roles si arriva a scrivere Roles = Roles.Admin sul controller, che segue lo stile di altri attributi .Net.

Ho usato due costruttori per CustomAuthorizeAttribute per mostrare i nomi dei gruppi directory reale attivi vengono passati Nella produzione io uso il costruttore parametrizzato per evitare le stringhe di magia nella classe:. I nomi dei gruppi sono tirati da web.config durante Application_Start() e superato in sulla creazione utilizzando uno strumento DI.

Avrete bisogno di un NotAuthorized.cshtml o simile nella vostra cartella Views\Shared o gli utenti non autorizzati visualizzeranno una schermata di errore.

Questo è il codice per la classe base AuthorizationAttribute.cs.

Controller:

public ActionResult Index() 
{ 
    return this.View(); 
} 

[CustomAuthorize(Roles = Roles.Admin)] 
public ActionResult About() 
{ 
    return this.View(); 
} 

CustomAuthorizeAttribute:

// The left bit shifting gives the values 1, 2, 4, 8, 16 and so on. 
[Flags] 
public enum Roles 
{ 
    Admin = 1, 
    User = 1 << 1  
} 

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)] 
public class CustomAuthorizeAttribute : AuthorizeAttribute 
{ 
    private readonly string adminGroupName; 

    private readonly string userGroupName; 

    public CustomAuthorizeAttribute() : this("Domain Admins", "Domain Users") 
    {  
    } 

    private CustomAuthorizeAttribute(string adminGroupName, string userGroupName) 
    { 
    this.adminGroupName = adminGroupName; 
    this.userGroupName = userGroupName; 
    } 

    /// <summary> 
    /// Gets or sets the allowed roles. 
    /// </summary> 
    public new Roles Roles { get; set; } 

    /// <summary> 
    /// Checks to see if the user is authenticated and has the 
    /// correct role to access a particular view. 
    /// </summary> 
    /// <param name="httpContext">The HTTP context.</param> 
    /// <returns>[True] if the user is authenticated and has the correct role</returns> 
    /// <remarks> 
    /// This method must be thread-safe since it is called by the thread-safe OnCacheAuthorization() method. 
    /// </remarks> 
    protected override bool AuthorizeCore(HttpContextBase httpContext) 
    { 
    if (httpContext == null) 
    { 
     throw new ArgumentNullException("httpContext"); 
    } 

    if (!httpContext.User.Identity.IsAuthenticated) 
    { 
     return false; 
    } 

    var usersRoles = this.GetUsersRoles(httpContext.User); 

    return this.Roles == 0 || usersRoles.Any(role => (this.Roles & role) == role); 
    } 

    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext) 
    { 
    if (filterContext == null) 
    { 
     throw new ArgumentNullException("filterContext"); 
    } 

    filterContext.Result = new ViewResult { ViewName = "NotAuthorized" }; 
    } 

    private IEnumerable<Roles> GetUsersRoles(IPrincipal principal) 
    { 
    var roles = new List<Roles>(); 

    if (principal.IsInRole(this.adminGroupName)) 
    { 
     roles.Add(Roles.Admin); 
    } 

    if (principal.IsInRole(this.userGroupName)) 
    { 
     roles.Add(Roles.User); 
    } 

    return roles; 
    }  
} 
+0

Nel caso in cui qualcuno cerchi effettivamente di usare questo attributo con l'iniezione del costruttore, non lo farà lavoro perché gli attributi sono c gestito tramite la reflection API - vedi questo [post] (http://alexmg.com/post/2012/09/01/New-features-in-the-Autofac-MVC-4-and-Web-API- (Beta) -Integrations.aspx) per maggiori informazioni. Il codice pubblicato sopra era un primo taglio prima di farlo funzionare con DI. – meataxe

+0

Alla fine, ho deciso di non usare DI con questo perché la lib che sto usando (Autofac + Autofac.MVC4) supporta solo quello che sto cercando di ottenere in una versione beta, che non voglio usare. Ecco un collegamento su come fare questo genere di cose con [Autofac.MVC4 beta] (http://alexmg.com/post/2012/09/01/New-features-in-the-Autofac-MVC-4 -e-Web-API-% 28Beta% 29-Integrations.aspx). La mia versione finale ha eliminato gli argomenti del costruttore e ho convertito 'adminGroupName' e' userGroupName' in 'const'. – meataxe

3

ho unito alcune delle soluzioni qui per creare il mio preferito. Il mio attributo personalizzato cambia semplicemente i dati per essere nel modulo che SimpleMembership si aspetta e consente di gestire tutto il resto.

miei ruoli enum:

public enum MyRoles 
{ 
    Admin, 
    User, 
} 

Per creare ruoli: attributo

public static void CreateDefaultRoles() 
{ 
    foreach (var role in Enum.GetNames(typeof(MyRoles))) 
    { 
     if (!Roles.RoleExists(role)) 
     { 
      Roles.CreateRole(role); 
     } 
    } 
} 

personalizzato:

public class AuthorizeRolesAttribute : AuthorizeAttribute 
{ 
    public AuthorizeRolesAttribute(params MyRoles[] allowedRoles) 
    { 
     var allowedRolesAsStrings = allowedRoles.Select(x => Enum.GetName(typeof(MyRoles), x)); 
     Roles = string.Join(",", allowedRolesAsStrings); 
    } 
} 

Utilizzato in questo modo:

[AuthorizeRoles(MyRoles.Admin, MyRoles.User)] 
public ActionResult MyAction() 
{ 
    return View(); 
} 
Problemi correlati