OData Delta补丁安全

时间:2015-01-31 06:13:07

标签: c# odata asp.net-web-api2

我在Web API 2中使用Delta的用户类有一个工作PATCH。通过使用.patch方法,我可以轻松地只检测发送的更改然后相应地更新,而不是必须接收整个用户!

问题是我想保护几个字段,因此它们永远不会更新。

我在SO上看到了一个例子,但它并没有利用Delta,而是似乎稍微过时并且实际上手工编写了所有补丁代码。难道没有办法轻易告诉OData的补丁跳过你指定的属性(也许我需要覆盖补丁并告诉它以避免一些属性)?

我怎么会开始这样做(或者我应该搜索/研究什么才能开始)?动作过滤器/验证在这里有作用吗?我是否考虑过模型绑定?是否覆盖补丁?

谢谢!

3 个答案:

答案 0 :(得分:3)

如果有人尝试更新受保护的字段,您可以执行以下操作,具体取决于您要执行的操作:

  1. 仅更新可修改的字段。为此,您可以构建新的Delta,只包含以下字段:

    Delta<User> filteredDelta = new Delta<User>();
    if (originalDelta.GetChangedPropertyNames().Contains("FirstName"))
    {
        filteredDelta.TrySetPropertyValue("FirstName", originalDelta.GetEntity().FirstName);
    }
    if (originalDelta.GetChangedPropertyNames().Contains("LastName"))
    {
        filteredDelta.TrySetPropertyValue("LastName", originalDelta.GetEntity().LastName);
    }    
    filteredDelta.Patch(selectedUser);
    
  2. PATCH请求失败(我会说这是处理此类请求的首选和最不令人惊讶的方式)。这会更简单:

    if (originalDelta.GetChangedPropertyNames().Contains("ModifiedDate"))
    {
        return InternalServerError(new ArgumentException("Attribue is read-only", "ModifiedDate"));
    }
    

答案 1 :(得分:1)

根据您的使用情况,有几种可能性......

  1. 您希望在提供更改时排除这些更改
  2. 如果更新了不可编辑的字段,您希望抛出错误。
  3. 从属性开始,以标记适当的属性

    /// <summary>
    /// Marks a property as non-editable.
    /// </summary>
    [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
    public class NonEditableAttribute : Attribute
    {
    }
    

    然后我们可以针对Delta编写一些扩展来利用这个

    public static class PatchExtensions
    {
        /// <summary>
        /// Get the properties of a type that are non-editable.
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static IList<string> NonEditableProperties(this Type type)
        {
            return type.GetProperties().Where(x => Attribute.IsDefined(x, typeof(NonEditableAttribute))).Select(prop => prop.Name).ToList();
        }
    
        /// <summary>
        /// Get this list of non-editable changes in a <see cref="Delta{T}"/>.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="delta"></param>
        /// <returns></returns>
        public static IList<string> NonEditableChanges<T>(this Delta<T> delta)
            where T : class
        {
            var nec = new List<string>();
            var excluded = typeof(T).NonEditableProperties();
    
            nec.AddRange(delta.GetChangedPropertyNames().Where(x => excluded.Contains(x)));
    
            return nec;
        }
    
        /// <summary>
        /// Exclude changes from a <see cref="Delta{T}"/> based on a list of property names
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="delta"></param>
        /// <param name="excluded"></param>
        /// <returns></returns>
        public static Delta<T> Exclude<T>(this Delta<T> delta, IList<string> excluded)
            where T : class
        {
            var changed = new Delta<T>();
    
            foreach (var prop in delta.GetChangedPropertyNames().Where(x => !excluded.Contains(x)))
            {
                object value;
                if (delta.TryGetPropertyValue(prop, out value))
                {
                    changed.TrySetPropertyValue(prop, value);
                }
            }
    
            return changed;
        }
    
        /// <summary>
        /// Exclude changes from a <see cref="Delta{T}"/> where the properties are marked with <see cref="NonEditableAttribute"/>
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="delta"></param>
        /// <returns></returns>
        public static Delta<T> ExcludeNonEditable<T>(this Delta<T> delta)
            where T : class
        {
            var excluded = typeof(T).NonEditableProperties();
            return delta.Exclude(excluded);
        }
    }
    

    和域类

    public class Customer 
    {
        public int Id { get; set; }
    
        public string Name { get; set; }
    
        [NonEditable]
        public string SecurityId { get; set; }
    }
    

    最后,您的控制器可以在Patch方法

    中利用这一点
    public async Task<IHttpActionResult> Patch([FromODataUri] int key, Delta<Customer> delta)
    {
        var patch = delta.ExcludeNonEditable();
    
        // TODO: Your patching action here
    }
    

    public async Task<IHttpActionResult> Patch([FromODataUri] int key, Delta<Customer> delta)
    {
        var nonEditable = delta.NonEditableChanges();
        if (nonEditable.Count > 0)
        {
            throw new HttpException(409, "Cannot update as non-editable fields included");
        }
    
        // TODO: Your patching action here
    }
    

答案 2 :(得分:-1)

我有同样的需求,我最终为Delta编写了一个扩展方法,接受其他参数来限制更新哪些字段(类似于TryUpDateModel)

我知道必须有更好的方法来做到这一点,但现在这样做了。

我不得不重新创建一些Delta私有方法和类。大多数代码来自https://github.com/mono/aspnetwebstack/blob/master/src/System.Web.Http.OData/OData/Delta.cshttps://github.com/ASP-NET-MVC/aspnetwebstack/blob/master/OData/src/System.Web.Http.OData/OData/PropertyAccessor.cshttps://github.com/mono/aspnetwebstack/blob/master/src/System.Web.Http.OData/OData/CompiledPropertyAccessor.cs(或类似,这些不是我复制的确切网址)

using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Web;
using System.Reflection;
using System.Linq.Expressions;

namespace MyProject.ODataExtensions
{
public static class ODataExtensions
{

    public static void Patch<TEntityType>(this System.Web.OData.Delta<TEntityType> d, TEntityType original, String[] includedProperties, String[] excludedProperties) where TEntityType : class
    {
        Dictionary<string, PropertyAccessor<TEntityType>> _propertiesThatExist = InitializePropertiesThatExist<TEntityType>();

        var changedProperties = d.GetChangedPropertyNames();
        if (includedProperties != null) changedProperties = changedProperties.Intersect(includedProperties);
        if (excludedProperties != null) changedProperties = changedProperties.Except(excludedProperties);

        PropertyAccessor<TEntityType>[] array = (
                from s in changedProperties
                select _propertiesThatExist[s]).ToArray();

        var array2 = array;

        for (int i = 0; i < array2.Length; i++)
        {
            PropertyAccessor<TEntityType> propertyAccessor = array2[i];
            propertyAccessor.Copy(d.GetEntity(), original);
        }

    }

    private static Dictionary<string, PropertyAccessor<T>> InitializePropertiesThatExist<T>() where T : class
    {
        Type backingType = typeof(T);
        return backingType.GetProperties()
                            .Where(p => p.GetSetMethod() != null && p.GetGetMethod() != null)
                            .Select<PropertyInfo, PropertyAccessor<T>>(p => new CompiledPropertyAccessor<T>(p))
                            .ToDictionary(p => p.Property.Name);
    }

    internal abstract class PropertyAccessor<TEntityType> where TEntityType : class
    {
        protected PropertyAccessor(PropertyInfo property)
        {
            if (property == null)
            {
                throw new System.ArgumentException("Property cannot be null","property");
            }
            Property = property;
            if (Property.GetGetMethod() == null || Property.GetSetMethod() == null)
            {
                throw new System.ArgumentException("Property must have public setter and getter", "property");
            }
        }

        public PropertyInfo Property
        {
            get;
            private set;
        }

        public void Copy(TEntityType from, TEntityType to)
        {
            if (from == null)
            {
                throw new System.ArgumentException("Argument cannot be null", "from");
            }
            if (to == null)
            {
                throw new System.ArgumentException("Argument cannot be null", "to");
            }
            SetValue(to, GetValue(from));
        }

        public abstract object GetValue(TEntityType entity);

        public abstract void SetValue(TEntityType entity, object value);
    }

    internal class CompiledPropertyAccessor<TEntityType> : PropertyAccessor<TEntityType> where TEntityType : class
    {
        private Action<TEntityType, object> _setter;
        private Func<TEntityType, object> _getter;

        public CompiledPropertyAccessor(PropertyInfo property)
            : base(property)
        {
            _setter = MakeSetter(Property);
            _getter = MakeGetter(Property);
        }

        public override object GetValue(TEntityType entity)
        {
            if (entity == null)
            {
                throw new ArgumentNullException("entity");
            }
            return _getter(entity);
        }

        public override void SetValue(TEntityType entity, object value)
        {
            if (entity == null)
            {
                throw new ArgumentNullException("entity");
            }

            _setter(entity, value);
        }

        private static Action<TEntityType, object> MakeSetter(PropertyInfo property)
        {
            Type type = typeof(TEntityType);
            ParameterExpression entityParameter = Expression.Parameter(type);
            ParameterExpression objectParameter = Expression.Parameter(typeof(Object));
            MemberExpression toProperty = Expression.Property(Expression.TypeAs(entityParameter, property.DeclaringType), property);
            UnaryExpression fromValue = Expression.Convert(objectParameter, property.PropertyType);
            BinaryExpression assignment = Expression.Assign(toProperty, fromValue);
            Expression<Action<TEntityType, object>> lambda = Expression.Lambda<Action<TEntityType, object>>(assignment, entityParameter, objectParameter);
            return lambda.Compile();
        }

        private static Func<TEntityType, object> MakeGetter(PropertyInfo property)
        {
            Type type = typeof(TEntityType);
            ParameterExpression entityParameter = Expression.Parameter(type);
            MemberExpression fromProperty = Expression.Property(Expression.TypeAs(entityParameter, property.DeclaringType), property);
            UnaryExpression convert = Expression.Convert(fromProperty, typeof(Object));
            Expression<Func<TEntityType, object>> lambda = Expression.Lambda<Func<TEntityType, object>>(convert, entityParameter);
            return lambda.Compile();
        }
    }
}

}