ASP.NET MVC avec LINQ to SQL: comment implémenter l'autorisation pour les entités dans le référentiel?

StackOverflow https://stackoverflow.com/questions/1429661

Question

Avec la conception axée sur le domaine à l'esprit, comment implémenteriez-vous l'autorisation d'utilisateur dans un référentiel? Plus précisément, comment limiteriez-vous les données que vous pouvez voir grâce au nom d'utilisateur fourni?

Disons que nous avons un centre d’e-commerce qui stocke des produits, où seuls certains produits sont gérés par un gérant de magasin donné. Dans ce cas, tous les produits ne doivent pas être vus par un identifiant de connexion donné.

Questions:

  1. Souhaitez-vous sélectionner tous les produits dans le dépôt, puis utiliser un filtre pour limiter les produits retournés? Like GetProducts (" mot-clé: bateau "). restrictBy (" nom_utilisateur ")?
  2. Voulez-vous lire les informations de connexion à partir du contexte du contrôleur dans le référentiel et filtrer les résultats de manière passive?
  3. Comment stockeriez-vous la relation entre un rôle d'utilisateur et les entités auxquelles il pourrait accéder? Voulez-vous simplement stocker la clé d'entité et le rôle dans une table plusieurs-à-plusieurs, avec un enregistrement pour chaque produit auquel chaque responsable de magasin peut accéder?

Des exemples de code ou des liens vers des exemples de code seraient fantastiques. Merci.

Était-ce utile?

La solution

La solution que j'ai choisie consiste à utiliser des attributs sur l'action du contrôleur qui examinent la relation entre l'utilisateur actuel et l'entité demandée, puis autorise ou non l'action sur la base des résultats de la recherche. J'ai quelques attributs différents selon que cela passe par une table de jointure ou par une relation directe. Il utilise la réflexion contre, dans mon cas, le contexte de données, mais chez vous, le ou les référentiels, pour obtenir et vérifier que les valeurs correspondent. Je vais inclure le code ci-dessous (que j'ai fait quelques efforts pour générer de manière générique afin de ne pas le compiler). Notez que vous pouvez également inclure une notion d’autorisation (dans la table de jointure).

Code de l'attribut de relation directe. Il vérifie que l'utilisateur actuel est le propriétaire de l'enregistrement (l'attribut "id" spécifié dans les paramètres de routage correspond à l'id de l'utilisateur actuel dans la table des utilisateurs).

[AttributeUsage( AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false )]
public class RoleOrOwnerAuthorizationAttribute : AuthorizationAttribute
{
    private IDataContextFactory ContextFactory { get; set; }

    private string routeParameter = "id";
    /// <summary>
    /// The name of the routing parameter to use to identify the owner of the data (participant id) in question.  Default is "id".
    /// </summary>
    public string RouteParameter
    {
        get { return this.routeParameter; }
        set { this.routeParameter = value; }
    }

    public RoleOrOwnerAuthorizationAttribute()
        : this( null )
    {
    }

    // this is for unit testing support
    public RoleOrOwnerAuthorizationAttribute( IDataContextFactory factory )
    {
        this.ContextFactory = factory ?? DefaultFactory();
    }

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

        if (AuthorizeCore( filterContext.HttpContext ))
        {
            SetCachePolicy( filterContext );
        }
        else if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
        {
            // auth failed, redirect to login page
            filterContext.Result = new HttpUnauthorizedResult();
        }
        else if (filterContext.HttpContext.User.IsInRole( "SuperUser" ) || IsOwner( filterContext ))
        {
            SetCachePolicy( filterContext );
        }
        else
        {
            ViewDataDictionary viewData = new ViewDataDictionary();
            viewData.Add( "Message", "You do not have sufficient privileges for this operation." );
            filterContext.Result = new ViewResult { MasterName = this.MasterName, ViewName = this.ViewName, ViewData = viewData };
        }

    }

    private bool IsOwner( AuthorizationContext filterContext )
    {
        using (var dc = this.ContextFactory.GetDataContextWrapper())
        {
            int id = -1;
            if (filterContext.RouteData.Values.ContainsKey( this.RouteParameter ))
            {
                id = Convert.ToInt32( filterContext.RouteData.Values[this.RouteParameter] );
            }

            string userName = filterContext.HttpContext.User.Identity.Name;

            return dc.Table<UserTable>().Where( p => p.UserName == userName && p.ParticipantID == id ).Any();
        }

    }

}

C’est le code de l’attribut association, c’est-à-dire qu’il existe une association dans une table de jointure entre l’id du paramètre de routage et l’identifiant de l’utilisateur de la table utilisateur. Notez qu'il existe une dépendance au code System.Linq.Dynamic à partir des exemples VS2008 .

[AttributeUsage( AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false )]
public class RoleOrOwnerAssociatedAuthorizationAttribute : MasterEventAuthorizationAttribute
{
    private IDataContextFactory ContextFactory { get; set; }

    public RoleOrOwnerAssociatedAuthorizationAttribute()
        : this( null )
    {
    }

    // this supports unit testing
    public RoleOrOwnerAssociatedAuthorizationAttribute( IDataContextFactory factory )
    {
        this.ContextFactory = factory ?? new DefaultDataContextFactory();
    }

    /// <summary>
    /// The table in which to find the current user by name.
    /// </summary>
    public string UserTable { get; set; }
    /// <summary>
    /// The name of the property in the UserTable that holds the user's name to match against
    /// the current context's user name.
    /// </summary>
    public string UserNameProperty { get; set; }
    /// <summary>
    /// The property to select from the UserTable to match against the UserEntityProperty on the JoinTable
    /// to determine membership.
    /// </summary>
    public string UserSelectionProperty { get; set; }
    /// <summary>
    /// The join table that links users and the entity table.  An entry in this table indicates
    /// an association between the user and the entity.
    /// </summary>
    public string JoinTable { get; set; }
    /// <summary>
    /// The property on the JoinTable used to hold the entity's key.
    /// </summary>
    public string EntityProperty { get; set; }
    /// <summary>
    /// The property on the JoinTable used to hold the user's key.
    /// </summary>
    public string UserEntityProperty { get; set; }
    /// <summary>
    /// The name of the route data parameter which holds the group's key being requested.
    /// </summary>
    public string RouteParameter { get; set; }

    public override void OnAuthorization( AuthorizationContext filterContext )
    {
        using (var dc = this.ContextFactory.GetDataContextWrapper())
        {
            if (filterContext == null)
            {
                throw new ArgumentNullException( "filterContext" );
            }

            if (AuthorizeCore( filterContext.HttpContext ))
            {
                SetCachePolicy( filterContext );
            }
            else if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
            {
                // auth failed, redirect to login page
                filterContext.Result = new HttpUnauthorizedResult();
            }
            else if (filterContext.HttpContext.User.IsInRole( "SuperUser" )
                     || IsRelated( filterContext, this.GetTable( dc, this.JoinTable ), this.GetTable( dc, this.UserTable ) ))
            {
                SetCachePolicy( filterContext );
            }
            else
            {
                ViewDataDictionary viewData = new ViewDataDictionary();
                viewData.Add( "Message", "You do not have sufficient privileges for this operation." );
                filterContext.Result = new ViewResult { MasterName = this.MasterName, ViewName = this.ViewName, ViewData = viewData };
            }
        }
    }

    protected bool IsRelated( AuthorizationContext filterContext, IQueryable joinTable, IQueryable userTable )
    {
        bool result = false;
        try
        {
            int entityIdentifier = Convert.ToInt32( filterContext.RouteData.Values[this.RouteParameter] );
            int userIdentifier = this.GetUserIdentifer( filterContext, userTable );

            result = joinTable.Where( this.EntityProperty + "=@0 and " + this.UserEntityProperty + "=@1",
                                      entityIdentifier,
                                      userIdentifier )
                              .Count() > 0;
        }
        catch (NullReferenceException) { }
        catch (ArgumentNullException) { }
        return result;
    }

    private int GetUserIdentifer( AuthorizationContext filterContext, IQueryable userTable )
    {
        string userName = filterContext.HttpContext.User.Identity.Name;

        var query = userTable.Where( this.UserNameProperty + "=@0", userName )
                             .Select( this.UserSelectionProperty );

        int userIdentifer = -1;
        foreach (var value in query)
        {
            userIdentifer = Convert.ToInt32( value );
            break;
        }
        return userIdentifer;
    }

    private IQueryable GetTable( IDataContextWrapper dc, string name )
    {
        IQueryable result = null;
        if (!string.IsNullOrEmpty( name ))
        {
            DataContext context = dc.GetContext<DefaultDataContext>();
            PropertyInfo info = context.GetType().GetProperty( name );
            if (info != null)
            {
                result = info.GetValue( context, null ) as IQueryable;
            }
        }
        return result;
    }
}

Autres conseils

J'ai posé une très question similaire sur le groupe de discussion S # arp Architecture pour lequel Tom Cabanski a suggéré un cadre AOP, tel que PostSharp . Cela ressemble à une solution viable à mes besoins, alors je prévois de l'examiner plus en profondeur. Malheureusement, ce n'est pas une réponse complète à votre question, car je n'ai pas d'exemple de code à partager.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top