AX为了处理记录集权限,增加了Record Level Security功能,可以通过管理->设置->安全性->记录级安全性 这个窗体设置。本文尝试分析RLS的实现方法并针对一些常见的问题说一下自己的看法。
1.实现方法:
AX针对记录级权限的实现方式很简单,大体翻翻管理->设置->安全性->记录级安全性这个窗体的代码就可以看到了,其中控件 查询的clicked方法要特别注意,该方法的源代码如下:
Code
void clicked()
{
SysQueryRun queryRun;
Query query = new Query();
super();
breakpoint;
if (sysRecordLevelSecurity.RecId)
{
// Set the company to specified by the current RLS restriction
appl.setDefaultCompany(sysRecordLevelSecurity.companyId);
if (sysRecordLevelSecurity.Restriction)
{
queryRun = new SysQueryRun(sysRecordLevelSecurity.Restriction);
}
else
{
query.addDataSource(sysRecordLevelSecurity.TabId);
queryRun = new SysQueryRun(query);
}
queryRun.saveUserSetup(false);
queryRun.prompt();
if (isConfigurationkeyEnabled(configurationKeyName2Id('ReportingServices')))
{
//Synchronize the secure view for the specified table
//when the record-level security criteria has changed.
ttsbegin;
//Save the RLS query to the SysRecordLevelSecurity table.
sysRecordLevelSecurity.Restriction = queryRun.pack();
sysRecordLevelSecurity_ds.write();
//Ensure the secure view is synchronized and picks up the changes
//in record-level security.
if (SRSSecureViewManagerProxy::syncWithTable(sysRecordLevelSecurity.TabId) == 0)
ttscommit;
else
ttsabort;
}
else
{
//Save the RLS query to the SysRecordLevelSecurity table.
sysRecordLevelSecurity.Restriction = queryRun.pack();
sysRecordLevelSecurity_ds.write();
}
}
else
{
this.enabled(false);
}
}
通过这段代码我们可以清晰地看到RLS的相关记录信息都放到了表SysRecordLevelSecurity中,该表在AOT中没有,只能通过数据库去查看其字段的情况,主要包含如下字段:
TableId:表的ID
GroupId:用户组ID
RESTRICTION:记录级权限的内容,该字段是Image类型的,对应X++里的Container类型
RESTRICTION的内容实际上就是QueryRun对象通过pack方法序列化成二进制的结果。
具体是怎么执行的那?由于这部分代码是有Kernel执行的,所以我们无从查起,只能通过跟踪SQL的方式猜测起运行原理。
AX执行查询有几种方式,一种是通过对象化的QueryRun等对象,一种是通过内置的X++语法直接执行,这两种方式下都支持RLS,QueryRun和Query都有recordLevelSecurity这个属性,在默认情况下该属性的值是true,而表变量也有recordLevelSecurity这个属性,在默认情况下是false,可以根据需要开启或者关闭记录级权限控制。我们通过X++语法的方式执行SQL查询,假定我们添加了当前组针对CustTable的记录级权限,只让属于当前用户组的用户看到4000..4008的记录。
Code
static void RLSTest(Args _args)
{
CustTable cust;
;
cust.recordLevelSecurity(true);
while select * from cust
{
print cust.AccountNum;
}
pause;
}
通过SQL跟踪,我们可以看到在执行上述语句时,有两条SQL语句值得关注:
Code
User ID: RLS1
Time: 16:11:29 2008-10-26
Version: Microsoft Dynamics 4.0 (Build number 2501.116)
Database: Microsoft SQL Server
SQL statement: SELECT A.TABID,A.GROUPID,A.COMPANYID,101090,A.RESTRICTION FROM SYSRECORDLEVELSECURITY A,USERGROUPLIST B WHERE (A.COMPANYID=?) AND ((A.GROUPID=B.GROUPID) AND (B.USERID=?)) ORDER BY A.TABID DESC [ID=49, Reused=Yes]
Call stack:
(C)\Jobs\RLSTest - line 6
Code
ser ID: RLS1
Time: 16:11:29 2008-10-26
Version: Microsoft Dynamics 4.0 (Build number 2501.116)
Database: Microsoft SQL Server
SQL statement: SELECT A.ACCOUNTNUM FROM CUSTTABLE A WITH( INDEX(I_077ACCOUNTIDX)) WHERE ((DATAAREAID=?) AND (((ACCOUNTNUM>=?) AND (ACCOUNTNUM<=?)) AND ((ACCOUNTNUM>=?) AND (ACCOUNTNUM<=?)))) [ID=427, Reused=Yes]
Call stack:
(C)\Jobs\RLSTest - line 6
从上述语句我们可以猜测Kernel实现的思路:
1.根据当前用户所属的组ID和当前所在公司的ID从表SysRecordLevelSecurity中查找是否有匹配的记录;
2.从查询的结果中找到当前查询涉及的表CustTable对应的RESTRICTION值;
3.由于RESTRICTION是QueryRun对象序列化的结果,自然可以根据它反序列化出QuerRun对象从而找到查询条件;
4.将查询条件添加到针对表CustTable的查询语句中。
OK,以上只是我的猜测,不见得准确。
2.常见的问题
A.RLS会不会自动关联1:n和n:1关系?
举例来说,我对表CustTable设置了只允许某个组查看4000..4008的记录,那么表CustTrans是不是也可以自动过滤出只有4000..4008的记录?从上述实现的原理来看,显然是不行的,因为他们都是针对单个表添加查询条件的,并没有查看与之关联的表。
B.如何将一个组的RLS设置拷贝给另一个组?
由于只涉及到一个表SysRecordLevelSecurity,问题就很简单了,无非是记录的拷贝而已。
Code
static void RLSCopy(Args _args)
{
SysRecordLevelSecurity srls,srlsNew;
;
while select srls
where srls.groupId == 'RLS1'
{
srlsNew.data(srls);
srlsNew.groupId = 'RLS2';
srlsNew.insert();
}
}
上述代码是将组RLS1的行记录设置拷贝给组RLS2。
OK,本文试图对RLS的实现原理加以阐述,并列举了我在使用AX过程中经常遇到的两个问题,限于我的水平,错误在所难免,还望高手赐教。