在 DAX 中读取活动的 Power BI 安全角色

本文翻译自Marco Russo& Alberto Ferrari的文章—《Reading active Power BI security roles in DAX》来源:SQLBI 本文描述了如何在 Power BI 或 Analysis Services 的 Tabular 模型中读取活动的安全角色。通过这种方式,您可以使用度量值和计算组来根据当前用户的活动安全角色动态地自定义报表。

在 Power BI 和 Analysis Services 中,用户可以是一个或多个安全角色的成员。这些角色决定了数据的可见性,根据所分配的角色,某些数据可访问,而某些数据不可访问。Power BI 可通过使用 USERNAME 和 USERPRINCIPALNAME 等函数返回当前用户名。然而,仍然存在一个问题,因为没有内置的功能来识别正在运行报表的当前用户的活动安全角色。

安全角色会自动限制 Tabular 模型中可见的数据。然而,为了提供更加个性化的用户体验,报表作者可能希望进一步定制报表的元素。例如,他们可以根据活动安全角色的不同,修改可视化图表的颜色,添加和修改报表信息等。

本文探讨了如何在卡片视觉中显示活动安全角色。我们还将概述如何实施度量来确定当前用户是否属于特定的安全角色,并提供更多的工具来根据活动安全角色来自定义用户体验。

创建安全角色表

为了获取活动角色列表,我们对一个表格应用安全筛选器,该表格包含每个安全角色的一行。这个表格的列仅包含角色名称。我们将这个表格称为Roles,其中的列称为Role。我们可以将这个表格创建为计算表格,也可以从外部源导入。在文章的后面部分,我们将看到如何使用 Tabular Editor 中的 C# 脚本生成所有所需的构件。这个表格将是模型中一个隐藏的、断开的表格。

举个例子,在示例模型中,我们有五个安全角色:Legal(法务)、Marketing(市场营销)、Operations(运营)、Production(生产)和Sales(销售)。Roles(角色)表还可以有一个额外的行(Admin)用于标识用户以管理员身份访问模型 - 这对于在报表中显示信息可能很重要,因为管理员不受任何安全限制。我们可以使用以下 DAX 表达式来创建 Roles 表:

在Roles表中创建计算表格

Roles =

SELECTCOLUMNS (

{ "Sales", "Legal", "Marketing", "Operations", "Production", "Admin" },

"Role", [Value]

)

一旦定义了Roles表,我们就为模型中的每个角色应用一个筛选条件,将可见性限制为与安全角色名称相等的role列值。例如,Legal角色筛选Roles表的条件是Roles[role] == " Legal "。

获取活动安全角色列表

如果假设用户属于单个安全角色,您可以使用以下“Active Role”度量来获取活动的安全角色:

在Roles表中的度量

Active Role :=

SELECTEDVALUE ( Roles[Role] )

然而,如果当前用户名具有多个活动的安全角色,Active Role度量的结果将始终为空白。因此,如果目标是在报表中显示活动安全角色,最好依赖于以下Active Roles度量,它会显示活动角色的列表:

在Roles表中的度量

Active Roles :=

CONCATENATEX (

VALUES ( Roles[Role] ),

Roles[Role],

", "

)

使用Power BI中建模/安全性选项卡中的“模拟视图”功能,我们可以模拟一个用户属于一个或多个角色的行为。例如,我们可以从一个单一角色开始,比如“营销”。

在这种情况下,这两个度量返回相同的结果。

如果我们为当前用户启用了两个角色(销售和营销),我们可以注意到Active Role返回空白,而Active Roles度量则返回包含销售和营销的字符串。

测试安全角色

因为一个用户可以属于多个安全角色,所以在可能的情况下,最好只测试特定安全角色是否处于活动状态,而不要依赖于对Active Roles结果使用CONTAINSSTRING函数,因为在角色名称相似的情况下,这种方法可能会变得且不缓慢准确。在这种情况下,最好依赖以下DAX代码来测试单个安全角色:

在“Is Marketing Role”表中的度量

Is Marketing Role :=

"Marketing" IN VALUES ( Roles[Role] )

因为"Admin"是我们在Roles中添加的一个字符串,而没有任何安全角色使用该名称,所以只有在未应用安全角色时该行才可见 - 这只会在用户是管理员时发生。我们可以通过检查角色[Role]的筛选上下文中是否存在该名称来测试用户是否为管理员:

在“Is Admin Role”表中的度量

Is Admin Role :=

"Admin" IN VALUES ( Roles[Role] )

我们在示例文件中为每个安全角色创建了一个卡片可视化页面。

要测试度量或计算组中是否存在安全角色,您应该使用DAX代码来测试定义的一个或多个相应的Is…Role度量。例如,下面的Secured Sales度量如果安全性处于非活动状态,则会引发错误。因此,在进行安全性测试时,如果您没有使用正确的用户来验证结果,您将会得到一个明确的错误提示:

在“Is Admin Role”表中的度量

Secured Sales :=

IF (

[Is Admin Role],

ERROR ( "Security not applied for administrators" ),

[Sales Amount]

)

编写Tabular Editor代码脚本

由于所描述的技术不依赖于模型中的其他数据,因此我们为Tabular Editor创建了一个c#脚本,该脚本可以自动创建角色表和本文中描述的度量。示例文件包括两个脚本,一个用于表格编辑器2,另一个用于表格编辑器3 (TE3):唯一的区别是TE3版本使用最新的c#语法来操作字符串。

要使用该脚本,请在Tabular Editor中打开.csx文件,并执行一次,然后将更改应用到模型中,并在Power BI中刷新计算表以确保内容有效。如果您想生成不同的表和度量名称,可以通过修改常量字符串来自定义代码的前几行。

// Define the table and column name for the list of roles

const string roleTableName = "Roles";

const string roleColumnName = "Role";

const string adminName = "Admin";

const string measureIsPrefix = "Is ";

const string measureIsSuffix = "";

const string activeRoleMeasureName = "Active Role";

const string activeRolesMeasureName = "Active Roles";

// Stop if there are no roles

if (Model.Roles.Count < 1) {

throw new Exception( "No roles defined in the model");

}

// Create list of role names + Admin

var roleNames = Model.Roles.Select( r => r.Name ).ToList();

if (!string.IsNullOrWhiteSpace(adminName)) {

if (roleNames.Contains(adminName)) {

throw new Exception( "Role {adminName} is in conflict with the special adminName constant.");

}

roleNames.Add(adminName);

}

// Create the table with the roles if it doesn't exist, or update the DAX code if it exists

string listRoleNames = roleNames.Skip(1).Aggregate("\"" + roleNames[0] + "\"", (current, r) => current + ", \"" + r + "\"");

string roleTableDax = "SELECTCOLUMNS ( { " + listRoleNames + " }, \"" + roleColumnName + "\", [Value] )";

var roleTable = Model.Tables.FindByName(roleTableName);

if (roleTable == null) {

roleTable = Model.AddCalculatedTable(roleTableName,roleTableDax);

}

else {

(roleTable as CalculatedTable).Expression = roleTableDax;

}

// Apply security to existing roles

foreach ( var role in Model.Roles ) {

role.RowLevelSecurity[roleTable] = roleTableName + "[" + roleColumnName + "] == \"" + role.Name + "\"";

}

// Create measures to test active roles + Admin

foreach ( var roleName in roleNames ) {

string measureName = measureIsPrefix + roleName + " " + roleColumnName + measureIsSuffix;

string measureDax = "\"" + roleName + "\" IN VALUES ( " + roleTableName + "[" + roleColumnName + "] )";

var measure = roleTable.Measures.FindByName(measureName) ?? roleTable.AddMeasure( measureName, measureDax );

measure.IsHidden = true;

}

// Create measures for Active Role and Active Roles

string activeRoleMeasureDax = "SELECTEDVALUE ( " + roleTableName + "[" + roleColumnName + "] )";

var measureActiveRole = roleTable.Measures.FindByName(activeRoleMeasureName) ?? roleTable.AddMeasure( activeRoleMeasureName, activeRoleMeasureDax );

string activeRolesMeasureDax = "CONCATENATEX ( VALUES ( " + roleTableName + "[" + roleColumnName + "] ), " + roleTableName + "[" + roleColumnName + "], \", \" )";

var measureActiveRoles = roleTable.Measures.FindByName(activeRolesMeasureName) ?? roleTable.AddMeasure( activeRolesMeasureName, activeRolesMeasureDax );

// Hide table and active role measures - comment or change if they should be visible

roleTable.IsHidden = true;

measureActiveRole.IsHidden = true;

measureActiveRoles.IsHidden = true;

结论和致谢

本文描述了如何获取已连接用户的活动安全角色列表,并如何创建度量来测试特定安全角色是否处于活动状态。

本文的想法来自David Bojsen。他针对特定的客户需求实现了这种技术,以基于安全角色而不是个别用户名来允许报表定制。虽然数据安全性应该基于安全角色的结果而不需要任何对报表层的干预,但基于安全要求定制报表布局的需求可能会产生相同报表的多个版本,以提供更好的用户体验给不同的受众。


如果您想深入学习微软Power BI,欢迎登录网易云课堂试听学习我们的“从Excel到Power BI数据分析可视化”系列课程。或者关注我们的公众号(PowerPivot工坊)后猛戳”在线学习”。



长按下方二维码关注“Power Pivot工坊”获取更多微软Power BI、PowerPivot相关文章、资讯,欢迎小伙伴儿们转发分享~


Power Pivot工坊