前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Extreme DAX-第5章 基于DAX的安全性

Extreme DAX-第5章 基于DAX的安全性

作者头像
陈学谦
发布于 2022-10-30 01:50:08
发布于 2022-10-30 01:50:08
5K00
代码可运行
举报
文章被收录于专栏:学谦数据运营学谦数据运营
运行总次数:0
代码可运行

阅读其他章节:

Extreme DAX-前言

Extreme DAX-第1章 商业智能中的DAX

Extreme DAX-第2章 模型设计

Extreme DAX-第3章 DAX 的用法

Extreme DAX-第4章 上下文和筛选

Power BI 学谦

终于,第五章来了。

本章介绍的是如何在PowerBI模型中实现各类安全性保障。除了我们所熟知的行级别安全性RLS,本文更是介绍了对象级别安全性、表级别安全性、列级别安全性、值级别安全性等。有待大家根据自己的实际业务场景,实现更加符合要求的安全性要求。

全文目前已经翻译完成,目前正在进行校稿完善阶段。

后续本公众号会按照节奏公布相关文章,感谢大家的支持。

对本章翻译内容的意见与建议,欢迎在评论区或微信群提交,谢谢。

在处理数据时,你可能会遇到一些数据需要对其进行加密处理。即使在组织内部,有些人的权限也会高于其他人。在 Power BI 模型中,也提供了一些可以保证数据的安全性的方法。在本章中,你将学习如何来实现。

请注意,在 Power BI 的报表和仪表板上进行分发或共享的安全性,我们将不做介绍。相反,我们将重点放在 Power BI 模型中的安全性上。常规的方案是,使用同一报表的两个用户根据其权限设置将看到不同的报表内容。

本章涵盖以下几个主题。

  • 使用行级别安全性保护 Power BI 模型。
  • 为分层数据配置安全性。
  • 保护属性或表中的单个列。
  • 确保度量值的聚合级别。

5.1 行级别安全性 (RLS) 简介

使用行级别安全性(Row-Level Security,RLS),可以限制用户查看 Power BI 模型中的数据。RLS 是 Power BI 模型中的主要安全形式。它之所以称为行级别,是因为你可以定义模型中每个表中哪些行对用户可见。值得注意的是,由于 RLS 的设置是对于整个模型起作用的,因此任何基于该模型的可视化报告都将满足其安全策略。

在深入探讨之前,让我们先明确一点:当你需要一个对模型进行权限设置时,建议你一定要使用RLS(或与此相关的对象级别安全性的相关概念,我们将在本章后面讨论),不要试图想办法绕过它,也不要试图通过共享报告(或者不共享)来实现。你无法确定你的报告将来会有何种用途:用户可能会获得对 Power BI 模型的访问权限,也可能意外被添加到安全组,或者其它无法预测的情况。同理,不要试图通过写一些在满足特定条件下返回特定数据的 DAX 度量值的方式来确保数据的安全。基于该模型开发报表的人员可以轻松绕过这些条件。简单来说就是:每个能够访问模型的用户只能看到那些被允许看到的数据。

5.1.1 安全角色

RLS 是基于安全角色实现的。可以基于不同角色设置独立的安全策略。例如,设置高管的安全角色,或者人力资源经理的安全角色、甚至你还可以为单个销售人员设置安全角色。

安全角色是Power BI模型设计的一部分,而角色成员资格则不然。只有在发布模型后,才能将用户添加到角色。你可以根据需要拥有多个安全角色,但同时也要考虑一些注意事项,我们将在本节中介绍这些。

安全角色是通过“管理角色”窗口来定义和维护的,如图5.1所示。

图5.1 管理角色窗口

一旦你发布了定义过安全角色的模型后,除了已发布模型所在工作区的管理员、成员或参与者,其他人是无法访问的。

在 Power BI 服务中,可以通过数据集上下文菜单中的安全性选项来查看是否已定义了安全角色,如图5.2所示。

图5.2 查找安全性选项

我们可以将人员单独添加到安全角色,通过添加电子邮件地址或作为(安全)组的形式。

请注意,将某人添加到安全角色并不能保证他正常访问数据集,必须同时满足下面两个条件才可以。

  • 访问数据集权限,通过共享报表、工作区成员身份,或数据集本身的生成权限。
  • 包含在安全角色中。
DAX 安全筛选器

创建安全角色后,就可以定义该角色的实际安全策略了。我们可以在模型中的一个或多个表上声明 DAX安全筛选器。请注意,DAX 安全筛选器通过角色和表来声明,我们可以在同一个表上具有不同的安全筛选器,只要它们具有不同的安全角色即可。

DAX 安全筛选器确定此安全角色中的用户将在表中看到哪些行。你可以将 DAX 安全筛选器理解为,在表中添加一列,然后判断每一行的值为“真”(TRUE)或“假”(FALSE)。最终只呈现那些判断条件后值为TRUE 的行,比如下面的代码。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Product[Category] = "Components"

可以理解为:在Product表上增加一列,判断表中每个产品的[Category]值是否为Components,如果是则返回返回TRUE,不是则返回FALSE。该筛选器添加到每一个要计算的度量值上,经过筛选后,表只返回那些类别为 Components 的结果。

我们不需要为每个表都设置安全筛选器,因为模型中的关系会将筛选器从一个表传播到另一个表。安全筛选器和度量值一样会考虑上下文。这意味着只需几个安全筛选器即可有效地保护模型。但请注意,模型中的更改可能会破坏安全策略!

图5.3中提供了一个简单示例,示例中含有两张表 (2),他们是一对多关系 (1)。使用安全角色SelectCanada后,它筛选加拿大(3)上的Country列(国家/地区列),fSales表将通该关系进行筛选。

图5.3 安全筛选器通过关系传播

当遇到具有双向交叉筛选的关系(在 PowerBI 中筛选方向是两个)时,在定义 RLS 时需要特别注意。双向关系的默认行为是仅向一个方向传播安全筛选器,那么如何确定这个方向呢?我们可以将交叉筛选器方向设置为“单一”,那么此时的方向就是安全筛选器的传播方向;或者如果关系是一对多,那么就是从一侧到多侧的方向。

你可以通过“编辑关系”窗口在两个方向上启用安全筛选器传播,如图5.4所示。

图5.4 编辑关系窗口

5.1.2 动态行级别安全性

像Product[Category] = "Furniture"这样的静态安全筛选器在实际业务中其实并不常用。但是如果 RLS 是动态的,那么它会变得更加有用,这意味着安全筛选器可以根据访问者的身份来选择相应的权限。

如需下载请参考异步社区本书页面配套资源的“2.1 Row-level security.pbix”文件

我们可以使用DAX函数来确定用户是谁,其中最推荐的是USERPRINCIPALNAME。此 DAX 函数返回用户的电子邮件地址,然后使用该地址给出正确的安全逻辑。

之前的 DAX 函数USERNAME在 Power BI Service中返回用户的电子邮件地址,但在 Power BI Desktop或 Analysis Services 实例中,它返回用户名。由于以这种方式查找用户的身份非常困难,因此引入了USERPRINCIPALNAME以使这更容易一些。使用 Power BI Embedded 时,可以在 Power BI 报表嵌入在其中的应用程序级别上配置安全性(也被称为“应用私有数据”)。在这种情况下,没有用户本身,而是在调用报表时,应用可以向 Power BI 提供标识符(密钥)。密钥可以是用户级标识符,但也可以是其他级别(如组织或部门)上的标识符。在这种情况下,USERPRINCIPALNAME将检索密钥,你可以将该密钥应用于安全筛选器中。

不过,你通常不会在整个模型中使用电子邮件地址作为用户 ID,而是使用数字(HR 系统中的员工编号或生成的密钥)。无论哪种方式,你都需要一个单独的表,其中包含电子邮件地址和用户ID之间的映射。在一些简单模型中,你可以在此表(在本例中是UserSecurity表)与包含用户数据的表之间建立关系;或者,你甚至可以直接使用电子邮件地址来筛选Employee表。

图5.5 用户安全和员工表

在一些更大更复杂的模型中,仅保护Employee表可能还不够。在这种情况下,使用一个与模型中的任何表都不建立关系的UserSecurity表是一个比较好的选择。

如果不这样做,可能会导致从UserSecurity表到多个表的多个关系路径并由此产生一些非活动关系。

使用独立的UserSecurity表时,你需要从表中检索用户ID作为DAX安全筛选器的一部分。例如,为了保护Employee表,DAX 安全筛选器应设置为如下所示。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
VAR ThisUser =
LOOKUPVALUE(
UserSecurity[EmpNr],
UserSecurity[Email],
USERPRINCIPALNAME()
)
RETURN
[EmpNr] = ThisUserRETURN

请注意,你可以在 DAX 安全筛选器中使用 DAX 变量。变量ThisUser从UserSecurity表中检索EmpNr值,使用USERPRINCIPALNAME()作为要查找的值。RETURN的后面,筛选器将检查Employee表当前行中的EmpNr值是否等于ThisUser变量,从而有效地筛选出适用于当前用户的行。

请记住,UserSecurity表包含敏感数据,你同事需要考虑是否需要保护此表本身!你可以在UserSecurity表上设置特定的安全筛选器,如下所示。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
FALSE()

此筛选器将使表中的任何行对任何用户都不可见。仅当UserSecurity表与其他表无关时,这才有效,因为不应将此筛选器传递到模型的其余部分。

请记住,安全筛选器是同时应用的,因此不会相互依赖,就像CALCULATE函数中的筛选器参数一样。这意味着,当你使用上面的安全筛选器来隐藏UserSecurity表中的所有行时,仍然可以使用上述方法在另一个安全筛选器中检索当前用户。

5.1.3 RLS 的建模注意事项

当然,RLS 的使用并不是一切顺利的,它同时会建模时对你限制一些操作。具体来说,你会发现非活动关系的某些用法失效了。

请看图5.6的示例。此模型包含一个事实表fHours,其中包含员工的工作小时数。员工的工作小时数通常我们指的是直接工时(Direct Hours)。但他们也把时间花在其他事情上的会议、休假、生病等,这些被称为间接工时(Indirect Hours)。每个项目都有一个项目经理,同时他也是该项目的一名员工。

图5.6 fHours表(工时表), Project表(项目表)和 Employee表(员工表)

如需下载请参考异步社区本书页面配套资源的“InactiveRelationship.pbix”文件

在此示例模型中,直接工时数可以通过项目汇总,也可以通过项目经理汇总;当然也可以通过员工汇总间接工时。因此,Employee表与fHours表有两种关系,其中有一组为非活动关系。在本示例中,fHours和Employee两个表之间的关系被设置为非活动状态。

那么,如何计算此模型中的直接工时呢?基本公式其实很简单,代码如下。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Direct Hours = SUM(fHours[Hours])

由于fHours表中的间接工时没有对应的项目,因此会在Project表中添加一个空白行。这么做的目的是为了保证上面的公式在计算时,空白的项目会被呈现出来。当然,你可以通过一个稍微复杂的公式来测试这个结果,代码如下。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Direct Hours =
CALCULATE(
SUM(fHours[Hours]),
NOT(ISBLANK(fHours[ProjectNr]))
)

同样地,计算间接工时也用相似的办法,不过我们此时关心的是ProjectNr列中空白的行。我们需要激活fHours和Employee表之间的关系,使用如下代码。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Indirect Hours =
CALCULATE(
SUM(fHours[Hours]),
ISBLANK(fHours[ProjectNr]),
USERELATIONSHIP(fHours[EmpNr], Employee[EmpNr])
)

根据以上这些度量值,你可以按员工得出直接工时和间接工时(请注意,这里的直接工时是某员工作为项目经理的项目工时),如图5.7所示。

图5.7 按员工得出的直接和间接工时

现在,假设你要保护此模型。让我们创建一个简单的安全角色,该角色仅返回Adams, Doug的结果。我们需要在 Employee表上添加一个如下的 DAX 安全筛选器。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[FullName] = "Adams, Doug"

当此安全角色处于活动状态时,你将看到视觉对象直接返回了一个错误,如图5.8所示。

图5.8 无法加载此视觉错误消息的数据

这似乎是一个奇怪的错误消息。fHours表有两个到Employee表的关系,但USERELATIONSHIP使用的是非活动关系,对吧?当涉及到度量值的计值上下文时,这的确没问题,但对于安全筛选器则不是这样。

实际上,发生这个错误你应该感到庆幸,因为我们正在尝试做的是删除或更改fHours表上的安全性。正常情况是,我们只看到fHours中项目经理为 Doug 时所对应的行。而使用USERELATIONSHIP时,我们告诉模型忽略这些设置,并允许我们访问其他行。为了保证数据的安全,模型不允许你这样做。毕竟,能够执行此操作意味着那些具有自助服务能力的用户,也就是那些可以编写自定义度量值的用户,可能会覆盖原有的安全筛选器并危及模型的安全性。

此行为非常关键,这意味着安全性在模型设计时就应该加以关注,否则就很容易遇到麻烦。这里的示例并不是说应该始终避免非活动的关系,而是如果将来需要用到安全性时能识别出潜在问题。

没有单一的方法来解决这个问题;这完全取决于你的分析需求是什么。例如,你可以将事实表一分为二,其中一个事实表表示直接工时,另一个表示间接工时,就像图5.9所展示的那样。

图5.9 将事实表拆分为 fHoursDirect 和 fHoursIndirect

但是,此解决方案不允许通过员工(作为从事项目工作的人,而不是项目经理)得到直接工作时间。

另一种方案是复制Employee表的内容并将其命名为Project Manager表(项目经理表),如图5.10所示。

图5.10 添加项目经理表

此解决方案需要Project Manager和Employee表都设置 DAX 安全筛选器。

5.1.4 测试安全角色

在设计安全策略之后,你肯定还需要测试这些策略,以查看他们是否按照预定的想法工作。幸运的是,Power BI Desktop 和 Power BI 服务都具有“通过以下角色查看”的功能,允许你查看特定角色中可见的数据。

使用“通过以下角色查看”选项,可以模拟特定用户登录并检查此用户能够看到的内容。在 Power BI Desktop 中,操作很简单:如图5.11所示,在“以角色身份查看”窗口中,选择要测试的角色,也可以使用“其他用户”选项。在框内你可以输入要测试的用户电子邮件地址。

图5.11 Power BI Desktop 中的“以角色身份查看”窗口

在 Power BI Service中,它的工作方式基本相同,但“其他用户”选项有些隐蔽。在数据集上选择“安全性”时,单击安全角色右侧的三个点,然后选择“以角色身份进行测试”,如图5.12所示。

图5.12 Power BI 服务中的“以角色身份测试”选项

当以角色查看报表时,再次单击该角色,在顶部蓝色的“当前查看时的身份为”那里,如图5.13所示,你可以选择输入要模拟的用户的电子邮件地址。

图5.13 选择要模拟的人

测试 RLS 时存在一个限制:如果已发布的 Power BI 模型采用了实时连接,那么你就无法轻松地对其进行测试,因为报表与模型不在同一文件中。这的确是一个问题,因为“实时连接”是部署报表的常用方法。在下一节中,我们将介绍一种使用实时连接测试 RLS 的方法,使测试人员能够轻松模拟任何用户。

5.1.5 在实时连接的报告中进行测试

在许多部署 Power BI 报表的组织中,技术支持人员会收到用户提出的问题。例如“我在报表中看不到任何数据”或者是“我应该看到 X 和 Y,但只能看到 X;哦,还有Z”。还有更加严重的情况,“John应该只看到X,但他却看到了全部的数据”。通常,这些问题是由于用户处于错误的角色(或无意中获取了对 Power BI 模型的编辑权限),要搞清这些问题我们可能需要重新认识安全策略。不管怎么说,能够模拟用户在报告中实际看到的内容还是很有用的。

需要提醒的一点是,你希望能够在生产版本(整个组织中实际使用的版本)上执行此操作,因此需要保证模拟本身必须是安全的。换句话说,这项操作应该仅限于特定用户(例如支持人员),而普通用户无法访问。此外,在生产环境中切换模拟角色应当很方便,这意味着它应该与正在使用的报表和对应的 Power BI 模型一起使用。

此问题的解决方案包含许多特定元素,如下。

  • 用于设置模拟的查询参数:pImpersonation。
  • 特定测试账号:PBITestUser(这应该是组织中具有电子邮件地址和 Power BI 许可证的账号)。
  • 用于测试的 Power BI 工作区:PBITest。

让我们从头来做一遍。

1.模拟模型

我们从一个非常简单的模型开始。其中唯一的元素是查询参数,pImpersonation。

  1. 在 Power BI Desktop 中,启动新模型。
  2. 单击“转换数据”以启动 Power Query 编辑器。
  3. 单击“管理参数/新建参数”以输入查询参数。
  4. 调用参数pImpersonation,并确保取消选中必需框。将类型设置为文本,添加说明,并将当前值暂时留空,如图5.14所示。

图5.14 在管理参数窗口中输入查询参数

  1. 单击确定退出管理参数窗口。这样就创建了一个参数查询。在查询窗格中,它以斜体显示,因为参数未加载到 Power BI 模型中。但在这种情况下,我们确实要加载它!右键单击查询,然后设置启用加载。查询现在以直立文本显示。
  2. 选择关闭并应用。Power BI 将参数加载到模型中,生成了一个包含一行空行的单列表,如图5.15所示。

图5.15 pImpersonation表

  1. 保存模型,为其命名为Impersonation,然后将其发布到PBITest工作区。

如需下载请参考异步社区本书页面配套资源的“2.1 Impersonation.pbix”文件

2.将 pImpersonation 表添加到模型

在需要测试的原始 Power BI 模型中,现在可以通过以下步骤以 DirectQuery 的方式连接到pImpersonation表。该模型将成为一个复合模型。

  1. 在 Power BI Desktop 中打开模型,然后单击功能区中的Power BI 数据集
  2. 现在可以选择要连接的 Power BI 模型。选择PBITest 工作区中的Impersonation数据集。
  3. 此时,pImpersonation表已添加到模型中。你无法查看表中的数据,但可以使用pImpersonation列创建视觉对象以查看其中的内容(目前为空),如图5.16所示。

图5.16 添加到模型中的pImpersonation 表

3.添加测试安全角色

接下来,创建一个新的安全角色UserTest,该角色将检查pImpersonation 的值。如果它包含有效的电子邮件地址,则安全筛选器将采用该电子邮件地址来模拟用户。如果该值为空,则不应用任何安全筛选器。

例如,以下的代码是Employee表的适配安全筛选器。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
VAR Impersonation =
SELECTEDVALUE(pImpersonation[pImpersonation])
VAR User =
LOOKUPVALUE(
UserSecurity[EmpNr],
UserSecurity[Email],
Impersonation
)
RETURN
ISBLANK(User) || [EmpNr] = User

第一个变量Impersonation 使用 SELECTEDVALUE检索pImpersonation参数(此模型中的列)的值。SELECTEDVALUE通常用于从列中检索值,当且仅当该列中只有一个唯一值;在本例中,总是只有一个值,因为在pImpersonation表中只有一行。

第二个变量User尝试使用LOOKUPVALUE从UserSecurity表中检索EmpNr值。请注意,UserSecurity用于将用户电子邮件地址转换为用户ID;如果找到EmpNr值,则安全筛选器的其余部分将使用该值。

请注意,当pImpersonation为空白值或电子邮件地址无效时,LOOKUPVALUE将返回BLANK。在这种情况下,我们不希望进行任何筛选。代码的最后一行ISBLANK(User))||[EmpNr] = User,意思是当变量User为空时,对于表中的每一行,ISBLANK(User)都为真。如果不是,则公式仅对列 EmpNr包含检索到的User值的行返回真值。

4.万事俱备

新的安全角色就位后,现在就可以发布模型了。不过,要使模拟角色起作用,你仍然需要处理以下几件事。

  1. 将 PBITestUser 帐户添加到 UserTest 安全角色。
  2. 将 PBITestUser 添加到发布模拟数据集的 PBITest 工作区。
  3. 与 PBITestUser 共享要测试的(实时连接)报告。
  4. 不要授予 PBITestUser对底层模型的完全访问权限。

若要模拟用户并测试报表,PBITestUser 应登录到 Power BI,导航到 Impersonation 数据集,然后将数据集设置下的pImpersonation参数值更改为要模拟的电子邮件地址。不要忘记,更改参数值后需要刷新模拟数据集。由于该参数是模型中唯一的内容,因此刷新在一瞬间即可完成。接下来,找到与 PBITestUser 共享的报表并打开。

由于该报表的模型与Impersonation是采用DirectQuery连接方式,因此参数值更改后马上就可以用了。安全角色将选取电子邮件地址并相应地筛选报告。请注意,通常来说,在用户在登录会话期间首次连接到模型时,安全筛选器即应用。当你在查看报表时更改参数,它不会立刻选取新值。相反,请导航离开报告并等待一段时间再回来(在我们的测试中,大约10分钟就足够了)。

只要 PBITestUser 是 UserTest 角色中的唯一帐户,此方法就是安全的。由于没有其他角色使用pImpersonation值,因此Impersonation数据集甚至不需要去保护,毕竟它只是一个电子邮件地址。

若要以这种方式测试不同的安全角色,可以为模型上定义的每个安全角色创建一个特定的测试角色。这样,你甚至可以通过将 PBITestUser 添加到相应的测试角色来测试当用户是多个角色的成员时会发生什么情况。

我们使用了单独的测试工作区来托管Impersonation数据集,但你可能希望以其他方式解决此问题。重要的是,测试用户必须对此数据集具有读取访问权限,并且不得对要测试的模型具有完全访问权限(例如,成为模型所在工作区的管理员)。这是因为作为工作区管理员、成员或参与者,安全角色不会被应用。

到目前为止,我们已经讨论了基于用户身份保护数据的可能性和一些陷阱。以上的讨论假设数据以某种方式直接与用户相关。在许多情况下,这是远远不够的,因为用户是更大的具有特定结构的组织的一部分。下一节将介绍这些情况。

5.2 使用 PATH 函数保护层次结构

在大多数组织中,数据并不直接与有权访问数据的单个用户相关。相反,有一群人每个人都可以访问不同的数据集。例如,经理可以访问向他们报告的员工的数据。DAX 包含一组函数来处理父子层次结构,如下所示:PATH函数。

5.2.1 分层表

首先,让我们看一个典型的组织结构,在本例中,我们的示例公司QuantoBikes的组织结构。QuantoBikes被组织成几个与大洲一致的部门,每个部门由多个团队组成。

组织结构图如图5.17所示。

图5.17 QuantoBikes 组织结构图

如图5.18所示,在 Employee 表中,此组织层次结构通过具有名为 MngrNr 或经理编号的列进行识别。此列包含每个员工的直接经理的员工编号;只有CEO这个职位没有对应的经理。

图5.18 QuantoBikes 的员工表

Power BI 假定一个员工没有多个直接经理,这是一个合理的假设。有些时候你可能需要处理具有多个父级的层次结构(例如,家谱):这些情况过于复杂,无法仅用PATH函数解决。我们不会在本书中介绍这些内容。当表中的多行在父列中包含空白值时,层次结构可以由多个树组成。

5.2.2 介绍 PATH 函数

如果我们设计一张表,表中对于父子层次结构重新编排,则可以得到一张包含所有信息的表。在我们的示例中,指的是从员工到经理,再到经理的经理,一直到层次结构的顶部。DAX 包含一系列可为你执行此操作函数,并提供有关层次结构的有用信息。

1.PATH

PATH(Employee[EmpNr],Employee[MngrNr])必须在 Employee 表的行上下文中进行计算。它将层次结构的两列作为参数,并返回从层次结构顶部到当前 EmpNr 的路径。结果是一个文本字符串,其中包含由竖线字符分隔的所有 EmpNr 值的串联。例如,Leo Johnson(员工 10106)的路径如下所示。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
10001|10010|10101|10106

请注意,路径始终是文本,即使层次结构列是数字时也是如此。

2.PATHCONTAINS

函数 PATHCONTAINS 将路径和值作为参数,当值包含在路径中时返回TRUE。再次以Leo Johnson为例,以下代码的结果为TRUE。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
PATHCONTAINS(
<Leo's path>,
10010
)
3.PATHLENGTH

函数 PATHLENGTH 返回路径中的项数。换句话说,它返回层次结构中当前项显示的级别。例如,以下代码将返回值 4。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
PATHLENGTH(<Leo's path>)
4.PATHITEM

函数PATHITEM采用路径和数字N作为参数,返回层次结构从头计数(或层次结构的顶部)的第 N 项。例如,以下代码会返回10101。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
PATHITEM(<Leo's path>, 3)

5.PATHITEMREVERSE

函数 PATHITEMREVERSE 与 PATHITEM 函数类似,但从路径的末尾(层次结构的底部)开始计数。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
PATHITEMREVERSE(<Leo's path>3)

以上返回10010,这是Leo跨职级经理的编码。

同样,请注意,PATHITEM和PATHITEMREVERSE返回文本值,即使路径是从数值创建的也是如此。

PATH 函数的正常用法是使用 PATH 创建路径,并将其用作其他函数的输入。但是,你可以按照自己喜欢的任何方式创建路径字符串。PATH函数对路径的方式没有任何隐藏的知识被创造;他们只处理文本字符串并搜索管道字符。

5.2.3 在 RLS 中使用 PATH 函数

当数据具有分层结构时,可以使用PATH函数实现更复杂的安全逻辑。假设你想要经理有权直接或间接获取下属所有员工的数据安全策略。首先需要在Employee表中创建一个列,其中包含每个员工的层次结构路径,如下。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Path = PATH([empNr], [MngrNr])

Employee表上的 DAX 安全筛选器将再次检索已登录用户的员工编号,代码如下。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
VAR ThisUser =
LOOKUPVALUE(
serSecurity[EmpNr],
UserSecurity[Email],
USERPRINCIPALNAME()
)

接下来是检查当前用户是否在员工的路径中。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
RETURN
PATHCONTAINS([Path], ThisUser)

当登录用户位于员工的路径中时,PATHCONTAINS函数返回TRUE,否则返回 FALSE。因此安全筛选器的结果是,用户下层次结构中的所有员工都可见,而其他员工是不可见的。

5.2.4 RLS 中的高级层次结构导航

通过巧妙地使用PATH函数,你可以实现各种高级安全规则。接下来我们看一个例子,我们创建一个策略:经理可以查看向他汇报的所有员工以及向他同级汇报的所有员工的数据。以下详细说明。

  • 如果 John 是经理,他可以查看自己及其所有直接或间接向他汇报的员工的数据。
  • 如果 John 是经理,他可以查看向同级(直接向经理汇报的员工)汇报员工的数据。
  • John 无法查看其经理或同级的数据。
  • 如果 John 不是经理,他只能查看自己的数据(即使有同级的人向她汇报)。

我们将需要相当多的代码来实现此策略,并会使用 DAX 变量来记录程序执行的过程。该代码涵盖以下步骤。

  1. 确定John是否为经理。
  2. 确定哪些员工向 John 的经理汇报,并做出筛选。
  3. 从筛选中删除 John 的同级。
  4. 最终生成一组对 John 可见的员工清单。

首先,让我们看看如何确定 John 是否为经理。这并不简单,因为汇报路径只能向上,而我们又没有可用的组织架构。但是,我们可以遍历 Employee 表并计算 John 在路径中出现的次数。不是管理者的人只会出现在他们自己的向上汇报路径上,而不会出现在其他人的路径上。要使用PATH函数,我们首先需要从UserSecurity表中检索 John 的员工编号。下面是 DAX 代码。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
VAR ThisUser=
LOOKUPVALUE(
UserSecurity[EmpNr],
UserSecurity[Email],
USERPRINCIPALNAME()
)
VAR IsManager=
IF(
COUNTROWS(
FILTER(Employee,
PATHCONTAINS(Employee[Path],ThisUser)
)
)>1,
TRUE(),
FALSE()
)

当用户是管理员时,变量IsManager为TRUE。我们可以继续寻找向 John 汇报工作的人,但根据我们的策略,John 可以查看直接或间接向其经理汇报工作的员工数据。因此,从查看John的经理是否在员工的路径上是有意义的。我们使用LOOKUPVALUE检索已登录用户的MngrNr值(变量ThisUser),然后使用PATHCONTAINS检查John的经理是否出现在员工的路径中,代码如下。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
VAR ThisUserMngr=
LOOKUPVALUE(
Employee[MngrNr],
Employee[EmpNr],
ThisUser
)
VAR ReportsToManager=PATHCONTAINS([Path],ThisUserMngr)

如果我们使用变量ReportsToManager,我们会把过多的员工选上。我们应该跳过John的经理和John的同级。要做到这一点,需要John和他的经理的职级;只有比 John 的经理低至少两级的员工才能被 John 查看。你可以使用PATHLENGTH函数来执行此操作,代码如下。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
VAR ThisUserPath=
LOOKUPVALUE(
Employee[Path],
Employee[EmpNr],
ThisUser
)
VAR MngrLevel=PATHLENGTH(ThisUserPath)1

请注意,安全筛选器是以Employee表作为迭代对象的,在每一行计算时我们都希望得到 John 经理的级别。有了这些信息,我们就可以推断出哪些员工应该是可见的,代码如下。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
VAR ShouldBeVisible=
ReportsToManager
&& PATHLENGTH([Path]) >= MngrLevel+2

但是此处需要留意一下:有一个员工的ShouldBeVisible值返回FALSE,而此员工实际上应该可见。那就是John本人。因此,我们需要对以上变量做一下微调,代码如下。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
VAR ShouldBeVisible=
(ReportsToManager
&& PATHLENGTH([Path]) >= MngrLevel+2)
||[EmpNr]=ThisUser

现在,我们可以将所有内容整合在一起,完成安全筛选器的设置,代码如下。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
RETURN
IF(
IsManager,
ShouldBeVisible,
[EmpNr]=ThisUser
)

还剩下一个比较重要的会出错的情况:首席执行官,因为他没有上级经理,导致的结果是他只能看到自己。这是因为ThisUserMngr变量为BLANK,因此没有员工被标识为向她报告;毕竟,不包含任何路径就表示空白值,因此变量ReportsToManager对于任何员工都是FALSE。为此,我们需要新增一个变量来检测这种情况:当用户没有上级时,MngrLevel为零。因此,最终的筛选器应是如下所示。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
RETURN
SWITCH(TRUE(),
MngrLevel=0,TRUE(),
IsManager,ShouldBeVisible,
[EmpNr]=ThisUser
)

这里的SWITCH语句首先检查用户是否为首席执行官,如果是,则可以查看所有员工;然后,它会检查用户是否为经理,如果是,就应用经理的安全规则。如果用户既不是首席执行官也不是经理,则只允许用户看到自己。

既然你已充分了解行级别安全性的实际应用,我们将讨论如何使用 RLS 实现更复杂的安全策略:保护属性,以及本章后面会介绍的聚合级别保护。

5.3 保护属性

在本节中,我们将以完全不同的方式介绍 Power BI 模型中的安全性。在前面的部分中,我们重点介绍了限制模型表中对“”是否可见的方法,这是最常见的安全需求。但实际业务场景中也会有其他形式的安全需求。如果你将行级别安全性视为“横向”安全性,那么考虑“垂直”安全性的也是有意义的。换句话说,我们是否可以保护列或属性?

5.3.1 安全属性的情况

只有当使用 Power BI 模型的人员群体较大时,才真正需要使用 RLS 保护该模型。如果你的模型仅由高管使用,则可能根本不需要 RLS。因为每个用户都可以查看所有数据。只有当受众群体变大时,才需要根据地理位置、客户细分,或如前面的章节中所述的根据组织结构对数据进行细分管理。

同样,如果你的模型仅适用于特定的业务流程(如销售和机会管理),则不需要保护特定属性。例如,该模型可能包含销售人员的姓名及其职责,但不包含他们的工资级别、出生日期或社保号码。当有关不同业务流程的数据组合到一个模型中时,如销售和人力资源管理数据,你需要包含不与用户共享的其他属性。

5.3.2 对象级别安全性及其限制

保护属性的一种方法是对象级别安全性(Object-level security),有如下的两种形式。

  • 表级别安全性(Table-level security):在安全角色中使整个表从视图中消失。
  • 列级别安全性(Column-level security):使表中的一个或多个列消失。

我们有意识地在这里使用“消失”这个词。例如,当使用表级别安全性保护Product表时,模型的行为就好像根本没有Product表一样。当使用列级别安全性保护列时,也会产生类似的效果。

我们不在这里详细展开介绍对象级别安全性的方方面面,部分原因是它是在不使用 DAX 的情况下实现的。本书重点想介绍的是稍后会提到的方法。当然,使用对象级别安全性也是有特定案例的。官方文档指出,它限制了对“敏感”表名和列名的访问,有些时候,你可能会有这么一些数据,它们敏感到连表名和列名都需要被限制在特定的几个人知道,更何况其中的内容了。(为什么此时我想到了一个表名:UFO类型,哈哈,但我觉得肯定有更严肃的例子)。

但是,表和列的消失会给 Power BI 模型和报表带来新的问题。当普通表与受保护的表建立关系时,你可能因为权限问题无法访问。更重要的是,当引用受保护的列或受保护的表中的列时,Power BI报表因为无法访问这些列或表而引发错误。

换言之,使用对象级别安全性会强制你将报表划分为对具有安全对象访问权限的用户版本,以及没有访问权限的用户版本。

有了这个,你可能会想,对于受保护的数据,使用一个单独的模型不是更好吗?毕竟,它只会被一个自定义报表使用。

尽管 Power BI 模型支持 OLS,但目前无法使用 Power BI Desktop 对其进行配置。

5.3.3 动态保护属性:值级别安全性

现在,我们引入一种略有不同的保护属性的方法,它可以在一张报表中实现允许访问的用户和不允许访问的用户同时使用。我们创造了术语值级别安全性(value-level security,VLS),因为它是行级别和列级别安全性的混合体。使用值级别安全性,可以授予用户访问某些行中列的值的权限,但不能访问其他行中的列值。

通过使用 VLS,你可以实施安全策略,例如:经理可以看到向他们报告的员工工资级别,但看不到向同级报告的员工工资级别,哪怕他们可以看到这些员工及其销售数字。而另一个完全不同的例子中情况可能是这样的:教师可以看到学生的姓名,数字和成绩,但只有班主任才能看到学生的地址。作为某些学生班主任的教师可以看到他们的地址,但看不到其他学生的地址。

你可能希望使用 DAX 度量值来实现此目的。但从长远来看,这不是一个安全的解决方案。如果用户获得了 Power BI 模型的设计权限,他们可以创建自己的报表 (这是使用 Power BI 的过程中可能经常遇到的情况), 还可以创建自己的 DAX 度量值。这样,他们就可以随意访问模型中不受保护的所有内容。这意味着可以通过创建度量来绕过以度量值的方式实现的任何安全性。

更重要的是,一个严肃的模型可以包含数十个或数百个度量值。当每个度量都需要配备逻辑来实现属性安全性,你犯错的概率就会陡然增大,维护成本也会居高不下。相反,我们希望有一个解决方案,该方案可以适用于模型的任何度量值。

实现 VLS 需要建模和 DAX 安全筛选器的复杂组合。在下面的部分中,我们将重点介绍这些内容。

如需下载请参考异步社区本书页面配套资源的“2.1 Value-levelsecurity.pbix”文件

1.值级别安全性:建模

设计 VLS 解决方案时,首先要知道一个安全的报表是什么样子的。我们不希望有任何的错误信息,因此图5.19中的视觉对象是最佳选择。在此示例中,部门员工的SSN 正确显示,其他员工显示为空。

图5.19 受 VLS 保护的报告

这里需要特别注意一点是,受保护的值在报告中是不显示的。但在此示例中,由于列 SSN是标签而不是度量值的结果,因此模型中必须有一个值才能在视觉对象中显示。这可以是空文本、BLANK值或其他值,并且该值必须真实存在于表的行中。

现在,如果你意识到对于某些用户,这些值应该是可见的,而对于其他用户来说,则要将保护的表(在本例中为Employee)拆分为两部分:一部分用于可公开访问的列(当然,受 RLS 限制的约束),另一部分包含私有列,如图5.20所示。

图5.20 将 Employee 表拆分为公共部分和私有部分

我们仍然需要将对应的行相互链接,因为我们在两个表中都有EmpNr列。你可以在两个表之间创建关系,这种关系甚至是一对一的。但这对我们没有帮助,它肯定不会为私有列提供空白值;相反,它把我们重新回到只有一张表的情况。

解决方案是向私有表中添加行。对于Employee表, Employee (private)表包含的行数必须是Employee表的两倍。我们将其分为两组,一组行包含EmpNr的所有值,以及所有私有数据,我们将这些行称为正行

另一组行还包含EmpNr的所有值,但在私有列中是空白值(或你选择的任何其他显示方式),我们将这些行称为负行。附加列 Private 有助于区分正行和负行。图5.21示意性地显示了这一点。

图5.21 向Employee (private)表添加行

Employee (private)表通过多对一关系链接到Employee表,其中Employee (private)处于关系的“多”端。该关系需要启用双向交叉筛选。例如,通过FullName选择员工时,你希望选择该员工的私人数据。反之亦然:在选择薪酬水平时,你希望选择具有该薪酬水平的所有员工。因此,筛选器需要从员工传播到 Employee (private),反之亦然。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
可以使用 M 脚本轻松创建中间表(此脚本假定你有一个sEmployee查询,该查询为 Employee 表提供基本数据,并且该查询本身未加载到模型中)。
letSource = sEmployee,PositiveRows = Table.SelectColumns(Source,{"EmpNr", "SSN", "DateOfBirth", "PayLevel"}),AddPrivate = Table.AddColumn(PositiveRows,"Private", each 1, Int64.Type),NegativeRows = Table.SelectColumns(Source,{"EmpNr"}),AddPrivate2 = Table.AddColumn(NegativeRows,"Private", each 0, Int64.Type),Combine = Table.Combine({AddPrivate, AddPrivate2})inCombine
此脚本创建sEmployee 表的两个副本,一个包含私有列,另一个仅包含EmpNr列。Private 列将分别添加到值为 10 的两个副本中。最后,对两个副本进行追加查询。
添加其他列可能很有用。例如,如果要在私有属性的安全策略中使用组织层次结构,则在两个副本中也包含MngrNr列是有意义的。

确保不要在关系上启用“在两个方向上应用安全筛选器”设置。正如你将在下一节中看到的那样,我们将使用Employee(private)上的安全筛选器来保护私有属性。这是对 Employee表的安全筛选器的补充。在这种情况下,该模型不允许针对默认方向相反的方向传播安全筛选器。

2.值级别安全性:安全筛选器

当你使用Employee 和 Employee(private)表中的列创建一些输出时,你会注意到每个员工的输出有两个副本:一个具有实际的私有属性(正副本),另一个具有空白私有属性(负副本)。产生这种情况的原因是我们设计Employee(private)表的方式,如图5.22所示。

图5.22 查看每个员工的两个输出行

但这意味着你现在可以使用行级别安全性来选择要显示的副本。对于其私有属性应可见的每个员工,请确保Employee(private)中的相应正值行可见,同时隐藏负值行。对于不应显示其私有属性的每个员工,使用非空白私有属性隐藏Employee(private)中的正值行,并使负值行可见。

Employee(private)上安全筛选器的 DAX 公式如下所示。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
(
<when to show>
&& [Private] = 1
)
|| (
NOT(<when to show>)
&& [Private] = 0
)

我们将第一个<when to show>子句称为正子句,另一个子句称为负子句。例如,若要仅显示员工 10203 的私有属性,筛选器将为如下。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
(
[EmpNr]=10203
&&[Private]=1
)
||(
[EmpNr]<>10203
&&[Private]=0
)

此表达式适用于EmpNr = 10203的单行和私有数据,以及具有其他值(用于 EmpNr

和空白值)的行。对于Employee(private)中的所有其他行,表达式为 false,并且这些行处于隐藏状态。

如你所见,对于每个员工,Employee(private)中只有一行可见。可以通过设置安全性来隐藏根本不应该可见的员工像往常一样在Employee表上进行筛选。如果在Employee上设置安全筛选器 [MngrNr] = 10201,则会得到如图5.23所示的结果(Faustina Bailey 是员工 10203)。

图5.23 查看一个员工的 SSN

3.值级别安全性:高级方案

在私有表的安全筛选器中,你可以像往常一样在 DAX 中应用所有可能的内容。例如,要这个实现安全策略:经理可以看到其直接下属的私有属性,但不能看到间接下属的私有属性,需要首先在Employee(private)表中有 MngrNr 和 Path 这两列。表的安全筛选器中的第一步是检索用户,代码如下。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
VAR ThisUser =
LOOKUPVALUE(
UserSecurity[EmpNr],
UserSecurity[Email],
USERPRINCIPALNAME()
)

接下来,确定用户的路径和组织级别,代码如下。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
VAR ThisUserPath =
LOOKUPVALUE(
'Employee (private)'[Path],
'Employee (private)'[EmpNr],
ThisUser
)
VAR ThisUserLevel = PATHLENGTH(ThisUserPath)

“只有直接报表”这个限制内容的意思是:为向此用户报告的员工(用户出现在其路径中),并且还得是直接下属(其级别比报告使用者级别低一级)。实现这一要求的 DAX 公式如下。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
PATHCONTAINS([Path], ThisUser)
&& PATHLENGTH([Path]) <= ThisUserLevel + 1

请注意,此处使用“<=” (而不是只有“=”),目的是为了包含ThisUser,ThisUser是她自己路径中唯一与她级别相同的员工(根据定义,ThisUser不属于级别小于或级别高于ThisUserLevel的任何员工的路径,因此不包括更高的经理)。因此,对Employee(private)的筛选将是如下所示。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
RETURN
(
PATHCONTAINS([Path], ThisUser)
&& PATHLENGTH([Path]) <= ThisUserLevel + 1
&& [Private] = 1
)
||
(
NOT(
PATHCONTAINS([Path], ThisUser)
&& PATHLENGTH([Path]) <= ThisUserLevel + 1
)
&& [Private] = 0
)

不要忘记在 PATHCONTAINS 子句和 PATHLENGTH 子句上使用 NOT 函数,特别要注意括号!

到目前为止,我们假设有一组员工属性是私有的,并且可能看到某些员工的私有属性的用户可以看到其所有私有属性。你可以更进一步,定义多组私有属性,例如,将公共属性与联系人详细信息分开,以供同级使用,并与只能由经理查看的 HR 属性分开。但是,要小心:这么多复杂的逻辑与操作,很快就会使模型变得异常复杂,从而难以管理,当然。这些操作也会对模型的性能产生影响。

但如果需要,你可以复制此结构以容纳多组私有属性。可以创建一个模型,如图5.24所示。

图5.24 设置多组私有属性

两个私有表都直接链接到公用表。对此设置后,你可以通过将 RLS 筛选器应用于各自的表来单独保护这两组私有属性。同样,公共表上的 RLS 确定哪些行完全可见。

4.如何在含有值级别安全性的模型中进行开发

最大的问题是针对启用了 VLS 的模型开发报表会变得混乱。正如我们在上一节中所看到的,当你对模型具有完全访问权限时,每当在报表中使用私有属性时,都会对行进行复制。

在模型本身中工作时,一种简单的方法是取消加载私有表的负行。这样,你将看到所有私有属性,至少你不会看到重复项。执行此操作的最佳方法是使用参数来设置是否加载负行。这样,你可以通过更改参数值一次切换所有的私有表。

另一种方法是创建一个Development表,其中包含值为0和1的 Private列。然后,可以创建从所有专用表到 Development 表的 Private 列之间的关系。这允许你在报表中设置筛选器(Development[Private] = 1)以关闭所有负行。这样,你可以轻松地从报表中切换,而不必公开私有表本身,如图5.25所示。

图5.25 创建与私有表有关系的开发表

使用这些方法,你将能够像用户一样在查看数据的同时生成报表。你仍需要确保用户看不到所有数据,因为他们对 Power BI 模型具有编辑权限。

行级别安全性的另一个应用是保护聚合级别,下一节将对此进行介绍。你可以使用类似的方法来保护属性,但同样有一些注意事项。

5.4 安全聚合级别

Power BI 模型安全的另一个条件与聚合级别相关。你可能会有这样的需求:“工资成本可以按团队查看,但单个员工的工资只能由他们的直接经理查看”。在本节中,我们将探讨确保在不同聚合级别上查看结果的方案。

5.4.1 度量值不能保证安全,但事实表可以

我们在本章前面已经提到过:在度量值中通过 DAX 实现安全性是不安全的。在设计模型时,应始终考虑自助服务用户可能的需求,用户将能够针对模型编写自己的度量值。这样,你不必在度量值安全上面再花费功夫。

相反,安全性必须仅依赖于模型结构和 RLS。这意味着并不是你能想到的每个安全策略都可以实现。例如,你的用户可以要求按个人查看销售信息,但只能按团队查看销售利润。由于这两个度量值的计算都来自同一事实表的数据,因此无法满足此需求。在其他情况下,数据取自不同的事实表(例如,一个是按个人计算的销售额,一个是按团队计算的工资成本)。

5.4.2 限制事实表粒度

确保工资只能按团队查看而不是按员工查看,最安全方法是不在员工级别加载这些数据。你可以创建一个工资事实表,其中包含每个团队的数据。这里明显的问题是,如何让授权用户在员工级别获得工资数据。可以使用其他数据集来执行此操作。

Power BI 较少使用的功能之一是跨报表钻取。这可不是一项能够容易实现的功能,因为它在很大程度上取决于模型和报表在 Power BI 服务中的发布方式。我们在此处就不详细介绍跨报表钻取了,但是要说明一点其功能,当你启用跨报表并且报表位于同一工作区中时,可以在报表中启用钻取操作,这些操作不只是可以跳转到同一报表中的另一页,还可以跳转到另一个报表中的某一页。

若要使跨报表钻取正常工作,只需要确保用于钻取操作的两个报表中的字段具有相同的名称,以便 Power BI 可以将它们识别为同一个字段。有趣的是,这些报表不必使用相同的底层模型。这意味着你可以创建一个包含按团队划分的工资成本的报表,并对显示特定团队里按员工的工资成本的详细报表进行钻取。详细报表的底层模型可以实现自己的安全策略,因此可以阻止未经授权的用户查看详细数据。

5.4.3 使用复合模型保护聚合级别

复合模型是混合了 DirectQuery 事实表和导入的事实表的 Power BI 模型。导入的表可以是DirectQuery 表的聚合版本。此功能旨在能够报告和分析数十亿行数据,并且基于(合理的)假设,即用户很少需要查看其数据中的较低详细级别。根据所问的问题,模型将选择从聚合表中检索结果,或者在需要时从 DirectQuery 表中检索结果。根据请求的聚合级别自动进行选择。为了确保聚合级别,这不是我们想要的;相反,我们希望基于安全规则进行选择。

幸运的是,这是可以做到的。你可以有一个事实表,比如fSalaryTeam,其中包含团队级别的工资成本,另一个数据表包含员工级别的工资成本,fSalaryEmployee。使用 RLS,你可以保护fSalaryEmployee避免未经授权的用户的访问,甚至可以仅允许用户访问fSalaryEmployee的部分数据,例如与他们自己的团队相关的部分数据。

在本节的其余部分中,我们将使用一个安全角色,该角色可以访问Teams表中的欧洲部门,以及fSalaryEmployee表中的Europe 2团队(TeamNr = 9),作为一个简单的示例,如图5.26所示。

图5.26 fSalaryTeam、fSalaryEmployee、Team 和 Employee 表

安全筛选器包括如下代码。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[Division] = "Europe" // in table Teams
RELATED(Employee[TeamNr]) = 9 // in table fSalaryEmployee

如需下载请参考异步社区本书页面配套资源的“2.1 Aggregation security1.pbix”文件

这里的挑战是,你需要更改度量值的 DAX 代码;不是为了实现安全性本身,而是从一个事实表无缝切换到另一个事实表。理想情况下,你需要一个度量值,在团队级别或更高级别上进行计算时,从fSalaryTeam获取工资成本,但在详细级别上,计算在fSalaryEmployee表中进行。

此挑战归结为确定度量值的计值上下文到底是什么。在第4章 上下文和筛选中,你已经看到了几个对此有帮助的 DAX 函数(ISFILTERED、ISCROSSFILTERED等)。使用这些函数,可以构建一个在不同事实表之间切换的度量值。然而,真正操作起来这并不容易,并且会发生一些意外情况。你的第一次尝试可能是按照如下代码。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Salary Costs =
IF(
HASONEVALUE(Employee[EmpNr]),
SUM(fSalaryEmployee[Salary]),
SUM(fSalaryTeam[Salary])
)

这看起来没问题:每当选择一个员工编号时,无论以何种方式,从fSalaryEmployee表中计算工资,或者从fSalaryTeam表中计算。HASONEVALUE函数通常被那些没有太多经验的 DAX 开发人员使用,主要用于以下构造。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
IF(HASONEVALUE(Table[Number]),
VALUES(Table[Number]) * 5
)

我们当然有更好的方法从列中提取单个值,不过除此之外,HASONEVALUE(以及类似地,HASONEFILTER)函数还有一个经常被忽视的属性:当在列中选择一个值时,它们返回真(true)。当该数字大于 1 时,它们返回假(false),但当它为0时,它们也会返回假!

图5.27显示了我们的示例安全角色中薪资成本度量的输出。

图5.27 薪金成本输出

请注意,有多少员工似乎具有相同的工资成本,并且它们实际上等于团队的工资成本。还要注意,像Kai Bell这样的员工似乎属于两个团队!事实上,我们看到所有不属于团队的员工都以整个团队的结果出现。其原因是在这些情况下,HASONEVALUE返回0,导致度量值选择fSalaryTeam进行计算。有关评估这些员工的原因的深入讨论,请参见第8章 自动匹配。

现在,你可以尝试在 DAX 公式中使用ISFILTERED而不是HASONEVALUE。但是,按工资水平等方式报告工资成本无济于事。最好确定所选内容是否是团队的子集,在这种情况下,请切换到员工级别的数据。

一种方法是简单地计算员工数量,并将该数字与团队中的员工总数进行比较,代码如下。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Salary Costs =
IF(
COUNTROWS(Employee) =
CALCULATE(
COUNTROWS(Employee),
ALL(Employee),
VALUES(Team[TeamNr])
),
SUM(fSalaryTeam[Salary]),
SUM(fSalaryEmployee[Salary])
)

图5.28显示了此度量值的示例输出,包括员工姓名和性别(这两个值都是Employee表中的列)。

图5.28 Europe 2的薪资成本,按雇员姓名和性别分列

安全角色仅显示Europe 2的员工级别的欧洲部门和薪资成本。从输出中可以看出,欧洲境内所有团队的工资成本都返回了。这清楚地表明,这些结果取自fSalaryTeam表。个人员工的工资成本仅返回Europe 2;对于其他团队,度量值会从fSalaryEmployee表中正确检索数据,但什么也不会收到,因为安全筛选器起到了作用。在按性别报告薪酬成本时,我们不会考虑员工个人。不过,该度量值会识别出请求低于团队级别的聚合级别,并从fSalaryEmployee表中获取结果。

5.4.4 将聚合安全性与值级别安全性相结合

可以使用 RLS 将保护聚合级别与保护私有数据相结合,但执行此操作时需要注意一些其他事项。扩展模型将如图5.29所示。

图5.29 使用 Employee(private)表进行扩展

如果要将输出更改为按团队和薪资级别划分的薪资成本(Salary Costs),你会看到,对于每个薪资级别,你将获得每个团队的薪资成本。当然,原因是我们确定一个完整的团队是否在上下文中的方法现在使用另一个表而变得不尽如人意。应向公式中添加一个附加的ALL子句,代码如下。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Salary Costs =
IF(
COUNTROWS(Employee) =
CALCULATE(
COUNTROWS(Employee),
ALL(Employee),
ALL('Employee (private)'),
VALUES(Team[TeamNr])
),
SUM(fSalaryTeam[Salary]),
SUM(fSalaryEmployee[Salary])
)

这样,度量值就可以返回正确的结果,如图5.30所示。

图5.30 按团队和薪级分列的薪资成本

永远记住,私有表中的一半行必须隐藏以避免重复输出(对于图5.30来说,应用了安全筛选器[Private] =1)。当然,你现在可以实施更复杂的规则。例如,如果你想对更高的工资水平进行保护,则可以在Employee(private)上使用以下安全筛选器。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
([Paylevel] <= 33 && [Private] = 1)
||
([Paylevel] > 33 && [Private] = 0)

结合上面讨论的部门和团队筛选器,其结果如图5.31所示。

图5.31 按团队和薪级分列的薪资成本,保护较高的薪资水平

请注意,对于高于33级的薪酬水平,我们不仅看不到任何结果,而且这些数字也没有累加。不过等等,难道不应该在34级以上的结果中得到一个空行吗?当你仔细查看安全筛选器时,你会注意到否定子句不执行任何操作。Employee(private)表中没有Paylevel 34及以上且 Private= 0 的行:毕竟,用Private= 0表示的行已经说明其他列为空!这意味着我们不是仅仅隐藏了一半的行,而是隐藏了更多的行。

如果你喜欢它的输出,那当然没问题。否则,则必须确定哪些员工的工资水平较高,并根据他们的员工人数进行筛选。执行此操作时(我们使用一些员工编号的延迟枚举),将显示这些空白值。

图5.32 按团队和薪级分列的薪金成本,保护较高的薪资水平,并显示为空白

正如你所看到的那样,让一切都按照你所期望的方式工作并不容易。我们现在不仅在Europe 2团队中看到空白,其他团队也显示了空白。毕竟,我们确实有权限查看到这些团队。这些团队中的所有员工都分组在空白薪资水平,因此薪资成本度量的逻辑决定了我们正在查看团队的所有员工并返回团队的薪资成本。

通过微调Employee(private)上的安全筛选器,你甚至可以进一步控制这一点,让空白输出仅针对团队 Europe 2显示。我们将这个问题留待你自己来探究解决。

5.4.5 将聚合级别作为属性进行保护

上面,我们已经讨论了基于每个事实表的聚合级别。实现聚合级别安全性的另一种方法是将聚合级别视为属性。这样,所有连接的事实表以及因此的所有度量值都受安全策略的约束。这种方法不如我们之前的方法灵活,但好处是你不必编写特定的 DAX 度量值,并且更容易设置。

本节内容提供实例文件下载。如需下载请参考异步社区本书页面配套资源的“2.1 Aggregation security2.pbix”文件。

这里的基本思想是,当我们想要确保单个员工级别的输出时,我们可以将每个员工属性视为私有属性。换句话说,公共的Employee表将仅包含主键(在我们的示例中为EmpNr和TeamNr列),所有其他列将移动到 Employee(private)表,如图5.33所示。通过本章中介绍的值级别安全性方法,员工数据可以得到有效保护。

图5.33 只保留 Employee 表中的主键

假设你要实现这样的安全需求:只能查看欧洲部门的数据;只有Europe 2队才能在员工层面上看到它。这可以通过Team和 Employee (private)表上的两个安全筛选器来完成。在 Team 表上设置如下筛选器。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[Division] = "Europe"

在Employee(private)表上(此处用到了一个事实,团队 Europe 2在Employee表的TeamNr列上值为9)按照如下这样设置。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
(RELATED(Employee[TeamNr]) = 9 && [Private] = 1)
||
(RELATED(Employee[TeamNr]) <> 9 && [Private] = 0)

通过一个简单的销售度量,你可以获得图5.34所示的结果。

图5.34 使用 VLS 的安全策略的结果

同样,你可以通过拆分 Employee (private)表并创建Employee(very private)表来保护员工的某些属性(尽管此时我们建议使用不同的命名方案)。这与之前讨论过的不同私有属性集的解决方案的原理完全相同。

这两个表都与原始Employee表建立关系,如图5.35所示。

图5.35 添加Employee(very private)表

现在,你可以对Employee(very private)表应用不同的安全筛选器,例如,使用正/反筛选器结构,仅允许访问雇员 10220 的非常私密的数据,代码如下。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
([EmpNr] = 10220 && [Private] = 1)
||
([EmpNr] <> 10220 && [Private] = 0)

表视觉对象中的结果如图5.36所示。

图5.36 访问员工的私密数据

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
这种保护聚合级别的方法不是 100% 安全的,因为事实表仍然使用员工级别的粒度。自助服务用户可以使用如下公式编写度量值来检索特定员工的结果。
Sales 10201 =CALCULATE([Sales],fSales[EmpNr] = 10201)
不过,想要做到这一点,用户必须知道使用了数字10201以及它代表什么含义。如果你要实现此方法,则至少不应使用组织中众所周知的业务关键字(如员工编号)

总结

通过阅读本章内容,相信你已从多个方面了解如何保护 Power BI 模型。行级别安全性的功能非常有用,主要是因为你可以使用 DAX 实现复杂的安全筛选器。

在 Power BI 模型中实现安全性时,需要仔细设计,这主要是因为模型可能具有多个安全角色,并且用户可能是多个角色的成员。并非所有安全角色都可以在同一模型中有效地组合,因此安全性甚至会影响拆分模型的决策。

使用 DAX,你可以检索用户的标识,并使用它来确定哪些数据是可见的,从而实现高度个性化的安全设置。你甚至可以使用 DAX 里的 PATH函数导航组织的层次结构。

你还了解到,通过建模、DAX 和行级别安全性的有效组合,你可以实现其他形式的安全性,例如用于保护属性的值级别安全性,以及用于保护聚合级别。

总体结论是:DAX 的强大功能不仅表现在它作为一门数据分析语言,而且在实现安全策略方面也具有巨大的功能。

在下一章中,我们将重点介绍一个完全不同的主题:可视化效果,以及如何使这些可视化效果比 Power BI本身的视觉对象更具动态性。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-08-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 PowerBI生命管理大师学谦 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
Extreme DAX-第3章 DAX 的用法
Power BI 模型的真正强大之处在于通过使用 DAX 语言进行计算。虽然许多 Power BI 用户专注于模型并试着完全避开使用 DAX,但是除了最简单的基础聚合运算以外,其他所有的计算都需要通过 DAX 来实现。而且,你迟早会在 Power BI 中遇到更复杂的计算需求。根据我们的经验,典型的情况会是:你精心制作的一个 Power BI 报告初稿,会引出有关这些数据的越来越多、越来越复杂的问题。
陈学谦
2022/05/24
7.4K0
Extreme DAX-第3章 DAX 的用法
Extreme DAX-第4章 上下文和筛选
编写 DAX 公式时要掌握的核心概念是上下文。DAX 作为一门动态数据分析语言,与 Excel 函数、SQL 查询 和 Power Query 脚本有着根本不同的原因就在于上下文的概念。以上所述的所有其他语言的公式只会在数据发生变化时才会返回不同的结果(除了一些例外情况,例如使用参数时),但是单个 DAX 公式就可以同时提供多个不同的结果,具体取决于您使用它的位置和方式,也就是:上下文。
陈学谦
2022/05/24
5.9K1
Extreme DAX-第4章 上下文和筛选
Extreme DAX-第 2 章 模型设计
设计优良的分析模型是 DAX 高效运行的前提。在本章中,我们将讨论许多与建模有关的主题,这些主题对于理解性能强劲的模型设计非常重要。
陈学谦
2022/05/24
3.6K0
Extreme DAX-第 2 章 模型设计
《DAX进阶指南》-第6章 动态可视化
开始本章翻译时,是5月初。当时并不知道平平无奇的5月Power BI会带来一大波更新,尤其是大杀器“字段参数”(字段参数参考文章)。
陈学谦
2022/10/30
5.8K0
《DAX进阶指南》-第6章 动态可视化
PowerBI 企业级权限控制全动态终极解决方案
PowerBI中的权限控制是分层次的,具体请以官方文档为准。但为了便于快速理解,这里特此编制了一个权限结构图:
BI佐罗
2019/09/23
3.5K0
PowerBI 企业级权限控制全动态终极解决方案
DAX中的基础表函数
👆点击“博文视点Broadview”,获取更多书讯 本文将介绍DAX中的基础表函数。 表函数是DAX中的一种常规函数,它返回的结果不是一个标量值,而是一个表。当需要编写DAX查询和迭代表的高级计算时,表函数非常有用。本文会介绍相关的计算示例。 本文的目标是介绍表函数的概念,而并非提供所有DAX表函数的详细说明。 《DAX权威指南》一书的第12章和第13章中介绍了更多的表函数。本文将解释DAX中最常见和重要的表函数的作用,以及如何在常见的场景中,包括标量表达式中使用它们。 01 表函数介绍 到目前为止,你
博文视点Broadview
2023/05/06
2.8K0
DAX中的基础表函数
Power BI: 不同角色的动态权限管理
文章背景: 在工作中,针对同一份PBI报表,希望不同用户打开该报表时,只能看到跟自己有关的内容,这个需求可以通过动态权限表来完成。
Exploring
2024/03/21
1.2K0
Power BI: 不同角色的动态权限管理
PowerBI 实现不同角色看到内容不同支持动态权限管理
合适合理的人可以看相应的报告数据,如果不具备地区(店铺)的权限,数据计算会自动适应。这个功能在PowerBI中又叫做:动态权限控制。这需要根据登陆的用户的不同来决定它的计算。但本文的讨论将远远超过这个基本需求,将现实中几种复杂需求进行讨论并给出解决方法。
BI佐罗
2019/09/23
4.8K0
PowerBI 实现不同角色看到内容不同支持动态权限管理
DAX 2 - 第一章 什么是 DAX
本文来自社区伙伴对《DAX 权威指南(第二版)》的学习笔记,有问题可以留言或联系BI佐罗修改,感谢你的支持。
BI佐罗
2020/04/27
4.8K0
内行才能看懂的 PowerBI DAX 引擎重大更新来了
2019年3月1日,在SqlBits大会上,微软宣布DAX引入一项重大更新:Calculation Group(暂且不做翻译)。这项更新将对PowerBI及SSAS均构成重要影响。为此,微软SSAS团队官方,SQLBI.com以及Chris Webb分别在各自博客记录这一内容。(后两者为SSAS领域国际顶级专家博客)
BI佐罗
2019/09/23
4.2K0
内行才能看懂的 PowerBI DAX 引擎重大更新来了
大数据分析工具Power BI(七):DAX使用场景及常用函数
Power BI中DAX函数非常多,功能非常强大,下面结合一些实际场景来讲解DAX一些常用的函数,这些场景包含求和、计数、相除、排序、累计、环比、同比,为了更方便后续的可视化展示数据,我们新创建可视化展示的页面,创建一个新表存储后续展示的度量值,具体操作如下:
Lansonli
2023/03/27
10.8K0
大数据分析工具Power BI(七):DAX使用场景及常用函数
如何以正确的方法做数据建模?
数据模型是进行报告分析的基础。为此提供了结构和有序的信息。为确保提供更好的性能、可靠性和准确性,将数据加载到正确设计的模型中是数据分析很重要的一项工作。
Banber可视化云平台
2021/04/29
3.3K0
如何以正确的方法做数据建模?
一个度量,是怎样炼成的? | DAX重要思路
前面,我在文章《DAX的核心,其实只有4个字!》里提到,DAX核心思想,就是“筛选、计算”四个字,当然,这个总结非常抽象,接下来,我会用一个又一个的例子来给大家具体讲,大家将慢慢体会到,几乎所有的度量都紧紧围绕这个思想而展开。
大海Power
2022/04/11
6930
一个度量,是怎样炼成的? | DAX重要思路
一次性学懂Excel中的Power Query和Power Pivot使用
👆点击“博文视点Broadview”,获取更多书讯 传统的Excel单表虽然可以有100万行数据的承载量,但是在实际分析时,20万行的数据就已经让传统的Excel非常吃力了。 但是,如果使用Excel中的Power Query和Power Pivot商务智能组件,即使是上百万行数据,也可以在短时间内快速完成处理和分析。 Power Query在Excel和Power BI Desktop中都是内置组件,并且管理界面和知识体系保持了高度一致。 其实,Power BI中的Power Query和Power P
博文视点Broadview
2022/10/10
9.6K0
一次性学懂Excel中的Power Query和Power Pivot使用
BI技巧丨权限管控
这个问题相信很多小伙伴都遇到过,或者被其他人问过,白茶总结了一下用户比较在意的几个点:安全性、自助性、权限管控、易用性、兼容性、扩展性、便捷性、反应速度等。
PowerBI丨白茶
2022/01/22
1.4K0
BI技巧丨权限管控
PowerBI 2018年11月更新 支持PowerBI工程式开发
到了年底,PowerBI积累了一年的功能来了波大的,本次更新的功能涉及几处重大改进。更新功能列表如下:
BI佐罗
2019/09/23
4.2K0
PowerBI 2018年11月更新 支持PowerBI工程式开发
Power BI: 理解ALLSELECTED函数和影子筛选上下文
ALLSELECTED函数是唯一一个使用影子筛选上下文的DAX函数。我们首先研究ALLSELECTED的行为,然后介绍影子筛选上下文。
Exploring
2023/09/10
2.1K0
Power BI: 理解ALLSELECTED函数和影子筛选上下文
PowerBI 2019年4月更新 PowerBI团队开挂大幅更新
兄弟们慢点,4天发布6项更新,你们不考虑下大家有时间学不,还有竞争对手会蒙圈的~ 产品经理无奈回复:这不是老大你安排的嘛。
BI佐罗
2019/09/23
4.8K0
PowerBI 2019年4月更新 PowerBI团队开挂大幅更新
DAX - 正确地提出好问题 - 你真的理解SUM吗
在学习 Power BI 的 DAX 过程中,不免会遇到一些问题和你想的不一致。例如以下问题来自伙伴在实际业务中涉及到的公式,我们来拆解并帮助大家梳理对于 DAX 的理解。
BI佐罗
2021/08/25
1.1K0
全网首发 Power BI DAX 纯原生高性能分页矩阵
在 Power BI 中显示一个大型的表,并不擅长,因为 Power BI 更倾向于制作高度聚合的可视化图表,但如果就是希望做到可以显示大篇幅的分页表格怎么办呢?本文就是来给出答案的。
BI佐罗
2021/03/25
2.7K0
全网首发 Power BI DAX 纯原生高性能分页矩阵
相关推荐
Extreme DAX-第3章 DAX 的用法
更多 >
LV.1
这个人很懒,什么都没有留下~
目录
  • 5.1 行级别安全性 (RLS) 简介
    • 5.1.1 安全角色
      • DAX 安全筛选器
    • 5.1.2 动态行级别安全性
    • 5.1.3 RLS 的建模注意事项
    • 5.1.4 测试安全角色
    • 5.1.5 在实时连接的报告中进行测试
      • 1.模拟模型
      • 2.将 pImpersonation 表添加到模型
      • 3.添加测试安全角色
      • 4.万事俱备
  • 5.2 使用 PATH 函数保护层次结构
    • 5.2.1 分层表
    • 5.2.2 介绍 PATH 函数
      • 1.PATH
      • 2.PATHCONTAINS
      • 3.PATHLENGTH
      • 4.PATHITEM
    • 5.2.3 在 RLS 中使用 PATH 函数
    • 5.2.4 RLS 中的高级层次结构导航
  • 5.3 保护属性
    • 5.3.1 安全属性的情况
    • 5.3.2 对象级别安全性及其限制
    • 5.3.3 动态保护属性:值级别安全性
      • 1.值级别安全性:建模
      • 2.值级别安全性:安全筛选器
      • 3.值级别安全性:高级方案
      • 4.如何在含有值级别安全性的模型中进行开发
  • 5.4 安全聚合级别
    • 5.4.1 度量值不能保证安全,但事实表可以
    • 5.4.2 限制事实表粒度
    • 5.4.3 使用复合模型保护聚合级别
    • 5.4.4 将聚合安全性与值级别安全性相结合
    • 5.4.5 将聚合级别作为属性进行保护
  • 总结
加入讨论
的问答专区 >
    领券
    💥开发者 MCP广场重磅上线!
    精选全网热门MCP server,让你的AI更好用 🚀
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档