Domanda

Con in mente Domain Driven Design, come implementeresti l'autorizzazione dell'utente in un repository? In particolare, come limiteresti quali dati puoi visualizzare dall'utente fornito login?

Diciamo che abbiamo un centro commerciale e-commerce che memorizza prodotti, in cui solo alcuni prodotti sono gestiti da un determinato responsabile del negozio. In questo caso, non tutti i prodotti dovrebbero essere visti da un dato login.

Domande:

  1. Vuoi selezionare tutti i prodotti nel Repo, quindi utilizzare un filtro per limitare quali prodotti vengono restituiti? Come GetProducts (" parola chiave: barca "). LimitBy (" nome utente ")?
  2. Leggeresti l'accesso dal controllercontesto all'interno del repository e filtreresti i risultati passivamente?
  3. Come memorizzeresti la relazione tra un ruolo utente e le entità a cui potrebbe accedere? Memorizzeresti semplicemente la chiave dell'entità e il ruolo in una tabella molti-a-molti, con un record per ogni prodotto a cui ogni gestore del negozio potrebbe accedere?

Esempi di codice o collegamenti ad esempi di codice sarebbero fantastici. Grazie.

È stato utile?

Soluzione

Il vantaggio che ho preso è quello di utilizzare gli attributi dell'azione del controller che esamina la relazione tra l'utente corrente e l'entità richiesta, quindi consente o non consente l'azione in base ai risultati della ricerca. Ho un paio di attributi diversi a seconda che passi attraverso una tabella di join o abbia una relazione diretta. Usa la riflessione contro, nel mio caso, il contesto dei dati, ma nel tuo repository (i) per ottenere e verificare che i valori corrispondano. Includerò il codice di seguito (che ho fatto alcuni sforzi per generarlo, quindi potrebbe non essere compilato). Nota che puoi estenderlo per includere anche alcune nozioni di autorizzazione (nella tabella dei join).

Codice per l'attributo di relazione diretta. Verifica che l'utente corrente sia il proprietario del record (l'attributo "id" specificato nei parametri di routing corrisponde all'id dell'utente corrente nella tabella utente).

[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();
        }

    }

}

Questo è il codice per l'attributo dell'associazione, ovvero esiste un'associazione in una tabella di join tra l'id nel parametro di routing e l'id dell'utente dalla tabella dell'utente. Si noti che esiste una dipendenza dal codice System.Linq.Dynamic dal VS2008 Samples .

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

Altri suggerimenti

Ho posto una domanda simile sul Newsgroup S # arp Architecture per il quale Tom Cabanski ha suggerito un framework AOP, come PostSharp . Sembra una soluzione praticabile per le mie esigenze, quindi sto pensando di dare un'occhiata più approfondita. Sfortunatamente, questa non è una risposta completa alla tua domanda, poiché non ho esempi di codice da condividere.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top