C# 如何使用实体框架设计可配置的字段级权限
假设我们有一个关于某些车型的信息表,例如: 如果我还需要用户可配置的规则,那么如何最好地实现读写操作的字段级访问权限?我正在使用MSSQL Server 2016和EF 6 根据该表,我们可能有以下用例,这些用例描述了特定角色或组可见的字段: 1) 公共数据的默认权限组 2) 基于实体的权限组 3) 自定义基于字段的权限组 要求是,隐藏数据必须区别于空值,规则/权限必须是用户可配置的。我还需要对列表进行分页,这需要对可见数据进行正确排序。为此,我需要一种处理数据类型的方法。例如,构造年是不可为空的DateTime,但当字段不可见时,需要将其设置为DateTime.MinValue之类的默认值。在处理位(布尔)值时,这变得更具挑战性:-)C# 如何使用实体框架设计可配置的字段级权限,c#,sql-server,entity-framework,permissions,C#,Sql Server,Entity Framework,Permissions,假设我们有一个关于某些车型的信息表,例如: 如果我还需要用户可配置的规则,那么如何最好地实现读写操作的字段级访问权限?我正在使用MSSQL Server 2016和EF 6 根据该表,我们可能有以下用例,这些用例描述了特定角色或组可见的字段: 1) 公共数据的默认权限组 2) 基于实体的权限组 3) 自定义基于字段的权限组 要求是,隐藏数据必须区别于空值,规则/权限必须是用户可配置的。我还需要对列表进行分页,这需要对可见数据进行正确排序。为此,我需要一种处理数据类型的方法。例如,构造年是
我目前正在考虑一种方法,要么使用表值函数(在我的场景中似乎更难动态实现),要么使用一个单独的缓存层来保存整个数据,我需要与数据库保持同步。实现目标的一个简单方法是创建一个设置表,其中,按组指定每个字段的可见性 首先,您需要制作一个组(针对品牌)表,如下所示:
public class Group
{
public int Id { get; set; }
public string Name { get; set; }
}
public IEnumerable<TableVm> GetTableByGroupWithPag(int groupId, ApplicationDbContext context,int pageNumber, int rowsPerPage)
{
var table = context.Tables.Skip((pageNumber-1)*rowsPerPage).Take(rowsPerPage).ToList();
var visibility = context.TableVisibilitySettings.FirstOrDefault(x => x.GroupId == groupId);
return table.Select(x => new TableVm
{
Id = x.Id,
Group = x.Grup.Name,
Color = x.Color,
ConstructionYear = visibility.ContructionYear == true ? x.ConstructionYear : null,
Power = visibility.Power == true ? x.Power : null,
IsConvertible = visibility.IsConvertible == true ? x.IsConvertible : null
}).ToList();
}
public class ApplicationUser
{
...
public int GroupId {get;set;}
public virtual Group Group
}
然后需要一个用于可见性设置的表:
public class TableVisibilitySettings
{
public int Id { get; set; }
public int GroupId { get; set; }
public virtual Group Group { get; set; }
public bool ContructionYear { get; set; }
public bool Power { get; set; }
public bool IsConvertible { get; set; }
}
然后需要表格和视图模型:
public class Table
{
public int Id { get; set; }
public int GroupId { get; set; }
public virtual Group Grup { get; set; }
public string Color { get; set; }
public int? ConstructionYear { get; set; }
public string Power { get; set; }
public bool? IsConvertible { get; set; }
public IEnumerable<TableVm> GetTableByGroupType(int groupId, ApplicationDbContext context)
{
var table = context.Tables.ToList();
var visibility = context.TableVisibilitySettings.FirstOrDefault(x => x.GroupId == groupId);
return table.Select(x => new TableVm
{
Id = x.Id,
Brand= x.Grup.Name,
Color = x.Color,
ConstructionYear = visibility.ContructionYear == true ? x.ConstructionYear : null,
Power = visibility.Power == true ? x.Power : null,
IsConvertible = visibility.IsConvertible == true ? x.IsConvertible : null
}).ToList();
}
}
首先,您需要从表中获取要显示的行,而不仅仅是应用可见性设置
编辑:
根据您的应用程序设计和技能,有几种方法可以将组链接到用户。
最简单的方法是设置应用程序用户
和组
之间的一对一
或多对多
关系,如下所示:
public class Group
{
public int Id { get; set; }
public string Name { get; set; }
}
public IEnumerable<TableVm> GetTableByGroupWithPag(int groupId, ApplicationDbContext context,int pageNumber, int rowsPerPage)
{
var table = context.Tables.Skip((pageNumber-1)*rowsPerPage).Take(rowsPerPage).ToList();
var visibility = context.TableVisibilitySettings.FirstOrDefault(x => x.GroupId == groupId);
return table.Select(x => new TableVm
{
Id = x.Id,
Group = x.Grup.Name,
Color = x.Color,
ConstructionYear = visibility.ContructionYear == true ? x.ConstructionYear : null,
Power = visibility.Power == true ? x.Power : null,
IsConvertible = visibility.IsConvertible == true ? x.IsConvertible : null
}).ToList();
}
public class ApplicationUser
{
...
public int GroupId {get;set;}
public virtual Group Group
}
在Group类中,您需要添加:
public virtual ICollection<ApplicationUser> Users {get;set;}
公共虚拟ICollection用户{get;set;}
另一种方法是为每个品牌创建角色,并根据您希望每个用户读/写的品牌为其提供一个或多个角色
另一种方法是使用声明,您只需向每个用户添加一个表示groupId、groupName或品牌的声明
希望这将帮助您选择一种将用户链接到组的方式 另一个选项是使用Castle.DynamicProxy()创建代理:
类程序
{
静态void Main(字符串[]参数)
{
ProxyGenerator=新的ProxyGenerator();
var person=新人{Id=1,Name=“Bob”,年龄=40};
var proxy=generator.CreateClassProxyWithTarget(person,new-EFInterceptor(new-SecurityInfo());
WriteLine(“Id:{0},Name:{1},Age:{2}”,person.Id,person.Name,person.Age);
WriteLine(“Id:{0},Name:{1},Age:{2}”,proxy.Id,proxy.Name,proxy.Age);
}
}
公共阶层人士
{
公共虚拟整数Id{get;set;}
公共虚拟字符串名称{get;set;}
公共虚拟整数{get;set;}
}
公共接口ISecurityInfo
{
bool是允许的(字符串propName);
}
公共类安全信息:ISecurityInfo
{
允许使用公共bool(字符串propName)
{
return propName!=姓名(Person.Age);
}
}
类interceptor:Castle.DynamicProxy.IInterceptor
{
私有只读ISecurityInfo securityInfo;
公共eInterceptor(ISecurityInfo)
{
this.securityInfo=info;
}
公共无效拦截(IInvocation调用)
{
if(invocation.Method.Name.StartsWith(“get_”))
{
var propName=invocation.Method.Name.Replace(“get_u3;”,即“”);
HandleAccess(调用,propName);
}
if(invocation.Method.Name.StartsWith(“set_”))
{
var propName=invocation.Method.Name.Replace(“set_”);
HandleAccess(调用,propName);
}
}
私有void HandleAccess(IInvocation调用,字符串propName)
{
如果(!securityInfo.IsAllowed(propName))
{
invocation.ReturnValue=GetDefault(invocation.Method.ReturnType);
}否则
{
invocation.procedure();
}
}
公共静态对象GetDefault(类型)
{
if(type.IsValueType)
{
返回Activator.CreateInstance(类型);
}
返回null;
}
}
如果您关心此解决方案的安全性,我认为您不能缓存,除了有问题的用户。这就是说,我将通过返回由位可见
字段组成的数据来实现这一点,并将不可见的字段设置为空。如果您将数据拉入临时表,找出它们应该能够看到的内容(根据需要更新visible
字段),然后null
取出值,那么您就能够向UI清楚地指示某些内容是null
,因为它是以这种方式存储的(visible
为1,但字段中有null
),或者因为用户不能看到它(visible
为0,数据为null
)
是的,这有点枯燥。这也意味着你没有向用户返回他们不应该看到的数据的风险-如果不应该显示数据,你的UI开发人员/维护人员将无法显示数据。如果你在做UI工作,你会更安全,因为你不会忘记你的意图mewhere down the road and expose the data.因为您需要像这样配置权限(请参阅我的评论),所以问题与EF无关-这与您有关
var permissionConfigEntry.Permission = CarFieldPermission.ViewConstructionYear
| CarFieldPermission.ViewPower
;
public IEnumerable<Car> LoadCars(User user, int pageIndex, int pageSize)
{
var result = db.Cars
.Skip((pageIndex - 1) * pageSize)
.Take(pageSize)
.ToArray()
;
var carsInInterest = result.Select(c => c.Id).ToArray();
var allThePermissions = db.PermissionConfiguration
.Where(pc => pc.User.Equals(user))
.Where(pc => carsInInterest.Contains(pc.CarId))
.ToArray()
;
foreach (var carX in result)
{
var current = allThePermissions.FirstOrDefault(pc => pc.User.Equals(user) && pc.Car.Equals(carX));
if (current != null)
{
if (!current.Permissions.HasFlag(CarFieldPermission.ViewConstructionYear))
{
carX.ConstructionYear = null;
}
}
}
return result;
}