从问题标题中你可能会猜到这是什么。我将尝试描述我目前拥有的和我想要存档的内容。
假设一个应用程序处理四个实体:用户,团队,存储库和文档。这些实体之间的关系是:
访问用户的文档不是问题,这些都是存储在他拥有的存储库中的文档。但事情变得复杂,因为我真正需要的是用户可见的所有文档,这就是它的所有文档以及其他人公开并与他共享团队的文档。
目前,我正在数据访问层中实施此授权机制。这意味着获取所有文档并按照上述规则进行一些过滤。我知道这个实现是不可扩展的,我想知道我是否可以通过将授权逻辑移动到数据库来改进我的数据库模型。这样,过滤将由数据库引擎完成,只有被请求的实体才会返回到客户端代码。
这个问题与特定的实现没有关系,但我会为我正在使用的特定工具标记它。也许这对某人的答案很有用。
答案 0 :(得分:3)
首先让我解释为什么使用实体框架(或其他ORM工具)比使用存储过程更优雅。
Stored Procedures are evil。这就是原因。正如链接详细解释的那样,存储过程往往会增长为第二个BL,因此难以维护。当在多个存储过程中使用此列时,重命名列的简单任务将成为一项重大任务。当您使用ORM工具时,visual studio将为您完成大部分工作。
这就说明了实体框架的第二个优势。您可以使用自己喜欢的.net语言撰写查询。实体框架不会直接执行您的查询。您可以控制何时执行查询,因为您可以阅读here。执行此实体框架时,会将Linq语句编译为完整的tsql语句,并针对数据库运行此语句。因此,绝对不需要获取所有数据并循环遍历每条记录。
提示:将光标移到变量名称上,ef将预览它将编译的TSQL语句。
那么你的Linq查询应该怎么样?我根据你的描述编写了一个测试数据库,并建立了一个实体框架(ef6)模型,它看起来像:
这个Linq查询会做你想要的,至少我正确理解你的问题。
private IEnumerable<Document> GetDocumentsOfUser(Guid userId)
{
using (var db = new DocumentRepositoryEntities())
{
// Get owned repositories by the user
var ownedRepositories = db.Repositories
.Where(r => r.Owner.UserId == userId);
// Get all users of teams the user belongs to
var userInOtherTeams =
db.Users.Where(u => u.UserId == userId)
.SelectMany(u => u.Teams)
.SelectMany(t => t.Users);
// Get the public repositories owned by the teammembers
var repositoriesOwnedByTeamMembers =
userInOtherTeams.Where(u => u.Repositories.Any())
.SelectMany(u => u.Repositories)
.Where(r => !r.Private);
// Combine (union) the 2 lists of repositories
var allRepositories = ownedRepositories.Concat(
repositoriesOwnedByTeamMembers);
// Get all the documents from the selected repositories
return allRepositories.SelectMany(r => r.Documents)
.Distinct()
.ToArray(); //query will be composed here!
}
}
请注意,当调用.ToArray()
时,linq语句将被编译为TSQL select语句。
答案 1 :(得分:1)
根据您的描述,目标是找到用户当前有权访问的所有存储库,然后从每个存储库中检索文档。
如果这是我的实现,我会向接受当前用户ID的数据库添加一个存储过程,然后将可访问存储库列表收集到本地表变量中,然后从文档表中选择文档的存储库位于可访问的存储库列表中。
DECLARE
@Teams TABLE (TeamID UNIQUEIDENTIFIER NOT NULL PRIMARY KEY (TeamID))
DECLARE
@Repositories TABLE (RepositoryID UNIQUEIDENTIFIER NOT NULL PRIMARY KEY (RepositoryID))
/* Get the list of teams the user is a member of */
INSERT INTO @Teams
SELECT Teams.TeamID
FROM Teams INNER JOIN TeamUsers ON Teams.ID = TeamUsers.TeamID
WHERE TeamUsers.UserID = @UserID
/* Get the list of repositories the user shares a team member with */
INSERT INTO @Repositories
SELECT RepositoryID
FROM Repositories
WHERE OwnerID = @UserID
OR (OwnerID IN (SELECT DISTINCT TeamUsers.UserID
FROM TeamUsers INNER JOIN @Teams ON TeamUsers.TeamID = @Teams.TeamID)
AND IsShared = 1)
/* Finally, retrieve the documents in the specified repositories */
SELECT Documents.*
FROM Documents INNER JOIN @Repositories ON Documents.RepositoryID = @Repositories.RepositoryID
答案 2 :(得分:1)
虽然答案competent_tech表明是有效的,如果您的需求是一次性的,那么您的理想选择是在外部化的专用层中实施您的授权要求。这样做的原因包括:
要实现外化授权(请参阅此处有关该主题的Gartner report),您需要考虑基于属性的访问控制(ABAC - see here for a report on ABAC by NIST)和可扩展性访问控制标记语言(XACML - more info here)作为实施ABAC的手段。
如果您遵循ABAC方法,您将获得:
在上面的示例中,用户类型,资源类型(文档),操作(视图,编辑),文档的团队,用户的团队以及文档的可见性(私有或公共)都是属性的示例。属性是生命线,是ABAC的基石。
ABAC可以轻松帮助您实施从最简单的授权要求到更高级的授权要求(例如可以在出口法规,合规性法规或其他业务规则中找到)。
这种方法的一个好处是它不是特定于数据库。您可以将相同的原则和策略应用于自行开发的应用程序,API,Web服务等。这就是我称之为外部授权的任何深度架构/方法。下图总结了这一点:
PDP是您的集中授权引擎。