最佳设计模式,用于控制每个用户每个对象的权限"与ServiceStack的基础?

时间:2014-03-13 10:36:00

标签: design-patterns permissions servicestack

我知道ServiceStack提供了一个RequiredRole属性来控制权限,但是,这并不完全适用于我的用例。我的网站有很多用户生成的内容。用户只能编辑他们具有显式权限的文档。每个对象或对象组控制权限。因此,如果用户是某个组的管理员,那么他们可以编辑该组管理的所有文档。

per object per user的基础上控制对请求的访问的最佳设计模式是什么?我希望尽可能使用DRY方法,因为它会影响我所有API端点的95%。

此外,这可以与FluentValidation集成并返回适当的HTTP响应吗?

非常感谢,

理查德。

1 个答案:

答案 0 :(得分:22)

我在ServiceStack应用程序中使用每个对象的权限。实际上,这是一个访问控制列表(ACL)。

我创建了一个Working Self Hosted Console Example,你可以在GitHub上分叉。

ACL模式:

我使用下图所示的数据库结构,我的数据库中的资源,如文档,文件,联系人等(我要保护的任何资源)都被赋予了{{1} } id。

Database

权限表包含适用于特定用户,特定组,特定对象和特定对象类型的规则,并且可以灵活地以组合方式接受它们,其中ObjectType值将被视为通配符。

保护服务和路线:

我发现处理它们的最简单方法是使用请求过滤器属性。通过我的解决方案,我只需在请求路由声明中添加几个属性:

null

我有一个过滤器属性调用[RequirePermission(ObjectType.Document)] [Route("/Documents/{Id}", "GET")] public class DocumentRequest : IReturn<string> { [ObjectId] public int Id { get; set; } } [Authenticate] public class DocumentService : Service { public string Get(DocumentRequest request) { // We have permission to access this document } } ,这将执行检查,以确定请求DTO RequirePermission的当前用户有权访问DocumentRequest Document个对象由属性ObjectId给出。这就是为了检查我的路线,所以它非常干。

Id请求过滤器属性:

在到达服务的操作方法之前,在过滤器属性中完成测试权限的工作。它具有最低优先级,这意味着它将在验证过滤器之前运行。

此方法将获取活动会话,自定义会话类型(详情如下),它提供活动用户的ID和允许他们访问的组ID。它还将确定来自请求的objectId (如果有)

它通过检查请求DTO的属性来确定对象ID,以查找具有RequirePermission属性的值。

使用该信息,它将查询权限来源以找到最合适的权限。

[ObjectId]

权限优先级:

从权限表中读取权限时,将使用最高优先级权限来确定它们是否具有访问权限。权限条目越具体,其结果排序时的优先级越高。

  • 与当前用户匹配的权限优先于所有用户的一般权限,即public class RequirePermissionAttribute : Attribute, IHasRequestFilter { readonly int objectType; public RequirePermissionAttribute(int objectType) { // Set the object type this.objectType = objectType; } IHasRequestFilter IHasRequestFilter.Copy() { return this; } public void RequestFilter(IRequest req, IResponse res, object requestDto) { // Get the active user's session var session = req.GetSession() as MyServiceUserSession; if(session == null || session.UserAuthId == 0) throw HttpError.Unauthorized("You do not have a valid session"); // Determine the Id of the requested object, if applicable int? objectId = null; var property = requestDto.GetType().GetPublicProperties().FirstOrDefault(p=>Attribute.IsDefined(p, typeof(ObjectIdAttribute))); if(property != null) objectId = property.GetValue(requestDto,null) as int?; // You will want to use your database here instead to the Mock database I'm using // So resolve it from the container // var db = HostContext.TryResolve<IDbConnectionFactory>().OpenDbConnection()); // You will need to write the equivalent 'hasPermission' query with your provider // Get the most appropriate permission // The orderby clause ensures that priority is given to object specific permissions first, belonging to the user, then to groups having the permission // descending selects int value over null var hasPermission = session.IsAdministrator || (from p in Db.Permissions where p.ObjectType == objectType && ((p.ObjectId == objectId || p.ObjectId == null) && (p.UserId == session.UserAuthId || p.UserId == null) && (session.Groups.Contains(p.GroupId) || p.GroupId == null)) orderby p.ObjectId descending, p.UserId descending, p.Permitted, p.GroupId descending select p.Permitted).FirstOrDefault(); if(!hasPermission) throw new HttpError(System.Net.HttpStatusCode.Forbidden, "Forbidden", "You do not have permission to access the requested object"); } public int Priority { get { return int.MinValue; } } } 。类似地,特定请求对象的权限优先于该对象类型的一般权限。

  • 用户特定权限优先于组权限。这意味着,可以通过组权限授予用户访问权限,但在用户级别拒绝用户访问,反之亦然。

  • 如果用户属于允许他们访问资源的组以及拒绝他们访问的其他组,则该用户将具有访问权限。

  • 默认规则是拒绝访问。

实现:

在上面的示例代码中,我使用此linq查询来确定用户是否具有权限。该示例使用模拟数据库,您需要将其替换为您自己的提供程序。

UserId == null

自定义会话:

我使用自定义会话对象来存储组成员资格,这些会员资格被查询并在用户通过身份验证时添加到会话中。

session.IsAdministrator || 
(from p in Db.Permissions
 where p.ObjectType == objectType && 
     ((p.ObjectId == objectId || p.ObjectId == null) && 
     (p.UserId == session.UserAuthId || p.UserId == null) &&
     (session.Groups.Contains(p.GroupId) || p.GroupId == null))
 orderby p.ObjectId descending, p.UserId descending, p.Permitted, p.GroupId descending
 select p.Permitted).FirstOrDefault();

我希望你觉得这个例子很有用。如果有什么不清楚,请告诉我。

流利验证:

  

此外,这可以与FluentValidation集成并返回适当的HTTP响应吗?

您不应该在验证处理程序中尝试执行此操作,因为它不是验证。检查您是否拥有权限是一个验证过程。如果您需要根据数据源中的特定值检查某些内容,则不再执行验证。 See this other answer of mine也涵盖了这一点。