Pergunta

Com Domain Driven Design, em mente, como você poderia implementar Autorização user em um repositório? Especificamente, como você restringir os dados que você pode ver pelo usuário fornecido login?

Vamos dizer que temos um e-commerce shopping que armazena produtos, em que apenas alguns produtos são mantidos por qualquer gerente da loja. Neste caso, nem todos os produtos deve ser visto por um determinado login.

Perguntas:

  1. Você selecionar todos os produtos no Repo, em seguida, usar um filtro para restringir os produtos que são devolvidos? Like GetProducts("keyword: boat").restrictBy("myusername")?
  2. Você leu o login do ControllerContext dentro dos resultados de repositório e filtro passivamente?
  3. Como você armazenar a relação entre uma função de usuário e que as entidades que poderia acessar? Você simplesmente armazenar a chave de entidade e o papel em uma tabela de muitos-para-muitos, ter um registro para cada produto que cada gerente de loja pode acessar?

Exemplos de código ou links para exemplos de código seria fantástico. Obrigado.

Foi útil?

Solução

A aderência que eu tenho tido é usar atributos sobre a ação do controlador que analisa a relação entre o usuário atual ea entidade que está sendo solicitado, então, ou permite ou não a ação com base nos resultados do olhar para cima. Eu tenho um par de atributos diferentes dependendo se ele passa por uma junção de tabela ou tem uma relação direta. Ele usa reflexão contra, no meu caso o contexto de dados, mas em seu repositório (s) de obter e verificar se os valores correspondem. Vou incluir o código abaixo (que eu tenho feito alguns esforços para genericize por isso não pode compilar). Nota você pode estender isso para incluir alguma noção de autorização, bem como (na tabela de junção).

Código para o atributo relação direta. Ele verifica se o usuário atual é o proprietário do registro (o atributo especificado "id" nos parâmetros de roteamento corresponde ao ID do usuário atual na tabela de usuário).

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

    }

}

Este é o código para o atributo de associação, ou seja, existe uma associação em uma tabela de junção entre o id no parâmetro de roteamento e ID do usuário da tabela de usuário. Observe que há uma dependência no código System.Linq.Dynamic do VS2008 amostras .

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

Outras dicas

Eu perguntei a um muito semelhante pergunta na S # arp Architecture newsgroup para que Tom CABANSKI sugeriu um quadro AOP, como PostSharp . Isto parece uma solução viável para as minhas necessidades, então estou pensando em tomar um olhar mais profundo sobre ele. Infelizmente, isso não é uma resposta completa à sua pergunta, como eu não tenho exemplos de código para compartilhar.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top