ASP.NET MVC с LINQ to SQL:Как реализовать авторизацию для объектов в репозитории?

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

Вопрос

Учитывая доменно-ориентированный дизайн, как бы вы реализовали авторизацию пользователей в репозитории?В частности, как бы вы ограничили данные, которые вы можете видеть при входе в систему, предоставленном пользователем?

Допустим, у нас есть торговый центр электронной коммерции, в котором хранятся товары, но менеджер магазина обслуживает только некоторые товары.В этом случае не все продукты должны быть видны при любом логине.

Вопросы:

  1. Вы бы выбрали все продукты в репозитории, а затем использовали фильтр, чтобы ограничить возвращаемые продукты? Like GetProducts("keyword: boat").restrictBy("myusername")?
  2. Вы бы прочитали логин из контекста контроллера в репозитории и пассивно отфильтровали результаты?
  3. Как бы вы сохранили связь между ролью пользователя и объектами, к которым она может иметь доступ?Вы бы просто сохранили ключ сущности и роль в таблице «многие-ко-многим», имея по одной записи для каждого продукта, к которой мог бы получить доступ каждый менеджер магазина?

Примеры кода или ссылки на примеры кода были бы фантастическими.Спасибо.

Это было полезно?

Решение

Мой подход заключается в использовании атрибутов действия контроллера, которое проверяет связь между текущим пользователем и запрашиваемой сущностью, а затем либо разрешает, либо запрещает действие на основе результатов поиска.У меня есть несколько разных атрибутов в зависимости от того, проходит ли он через таблицу соединений или имеет прямую связь.Он использует отражение, в моем случае, контекста данных, но в вашем репозитории, чтобы получить и проверить соответствие значений.Я приведу ниже код (который я приложил некоторые усилия, чтобы обобщить, чтобы он не мог скомпилироваться).Обратите внимание, что вы можете расширить это, включив в него также некоторое понятие разрешения (в таблице соединений).

Код атрибута прямой связи.Он проверяет, что текущий пользователь является владельцем записи (указанный атрибут «id» в параметрах маршрутизации соответствует идентификатору текущего пользователя в таблице пользователей).

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

    }

}

Это код атрибута ассоциации, т. е. в таблице соединения существует связь между идентификатором в параметре маршрутизации и идентификатором пользователя из пользовательской таблицы.Обратите внимание, что существует зависимость от кода System.Linq.Dynamic из Образцы 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;
    }
}

Другие советы

Я спросил очень аналогичный вопрос на Группа новостей S#arp Architecture для которого Том Кабански предложил структуру АОП, например ПостШарп.Это похоже на работоспособное решение для моих нужд, поэтому я планирую рассмотреть его более подробно.К сожалению, это не полный ответ на ваш вопрос, поскольку у меня нет примеров кода, которыми я мог бы поделиться.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top