Dynamics 365 SCM and Finance API for getting security metadata
Microsoft Dynamics 365 for Finance and Operations offers a new API for getting metadata of AX elements such as tables, form extensions, models, and so on. In my case, this API has been used for creating a security report and I would like to share my experience.
The initial idea was to understand what objects are available for users and what access level they have. I’m going to share a part of the code just to give an example. In a similar way, it should be possible to expand the given example to other security objects.
The security model description of the Dynamics 365 finance and operations is well explained in the standard documentation:
I will not repeat its description here. Our goal was to develop the report that can provide information about forms, tables and other objects that user has access to.
As an example we will consider a user’s role. This role has the following structure:
Duty
details:
In order to get the user access information can be used the code below:
Note: The processing of the sub role root will be not covered in the provided code example.
using Microsoft.Dynamics.AX.Metadata.Storage;
using Microsoft.Dynamics.AX.Metadata.Providers;
using Microsoft.Dynamics.AX.Metadata.MetaModel;
using Microsoft.Dynamics.ApplicationPlatform.Environment;
using
Microsoft.Dynamics.AX.Security.Management.Domain;
using Microsoft.Dynamics.AX.Security.Management;
using Microsoft.Dynamics.AX.Metadata.Core.MetaModel;
class TestClass
{
void run(str _aotName = 'D365TestRole')
{
Microsoft.Dynamics.AX.Metadata.Core.MetaModel.UserLicenseType
maintainLicense;
Microsoft.Dynamics.AX.Metadata.Core.MetaModel.UserLicenseType
viewLicense;
MenuFunction menuFunction;
AxSecurityRole roleSec;
SecurityRole securityRole;
Privilege privilege;
var environment =
EnvironmentFactory::GetApplicationEnvironment();
var
packagesLocalDirectory =
environment.Aos.PackageDirectory;
IMetadataProvider metadataProvider
= new MetadataProviderFactory().CreateDiskProvider(packagesLocalDirectory);
IMetaSecurityPrivilegeProvider privilegeProvider = metadataProvider.SecurityPrivileges;
IMetaSecurityDutyProvider
dutyProvider =
metadataProvider.SecurityDuties;
IMetaSecurityRoleProvider
roleProvider =
metadataProvider.SecurityRoles;
select * from securityRole
where securityRole.AotName ==
_aotName;
if (securityRole)
{
roleSec
= Microsoft.Dynamics.Ax.Xpp.MetadataSupport::GetSecurityRole(securityRole.AotName);
info("--Role
privileges--");
AxSecurityPrivilegeReference privilegesRef;
AxSecurityPrivilege
axSecPrivilege;
var privilegesEnum =
roleSec.Privileges.GetEnumerator();
while (privilegesEnum.MoveNext())
{
privilegesRef = privilegesEnum.get_current();
axSecPrivilege = privilegeProvider.Read(privilegesRef.Name);
this.expandPrivilege(axSecPrivilege);
}
info("--Role
Duties--");
Duty
duty;
AxSecurityDutyReference dutyRef;
AxSecurityDuty
axSecDuty;
var dutiesEnum =
roleSec.Duties.GetEnumerator();
while (dutiesEnum.MoveNext())
{
dutyRef = dutiesEnum.get_current();
axSecDuty = dutyProvider.Read(dutyRef.Name);
info(strFmt('Duty %1 Label %2',
axSecDuty.Name,
SysLabel::labelId2String2(axSecDuty.Label,
currentUserLanguage())));
var dutyPrivileges = axSecDuty.Privileges;
var privilegesEnumerator = dutyPrivileges.GetEnumerator();
while (privilegesEnumerator.MoveNext())
{
privilegesRef = privilegesEnumerator.get_current();
axSecPrivilege = privilegeProvider.Read(privilegesRef.Name);
this.expandPrivilege(axSecPrivilege);
}
}
info("--Role
Sub Roles--");
AxSecurityRole subRole;
var subRolesEnum =
roleSec.SubRoles.GetEnumerator();
while (subRolesEnum.MoveNext())
{
AxSecurityRoleReference roleRef
= subRolesEnum.get_current();
subRole =
roleProvider.Read(roleRef.Name);
info(strFmt('SubRole %1 Label %2',
subRole.Name,
SysLabel::labelId2String2(subRole.Label,
currentUserLanguage())));
//expand sub-roles
. . . . . . . . . .
. . . . . . . . . .
}
info("--Role
Permissions--");
var accessPermissionsEnum =
roleSec.DirectAccessPermissions.GetEnumerator();
while
(accessPermissionsEnum.MoveNext())
{
AxSecurityDataEntityReference roleReference;
roleReference =
accessPermissionsEnum.get_current();
AxTable table;
table = Microsoft.Dynamics.Ax.Xpp.MetadataSupport::GetTable(roleReference.Name);
if (table)
{
var fieldsRefEnum =
RoleReference.Fields.GetEnumerator();
while (fieldsRefEnum.MoveNext())
{
AxSecurityDataEntityFieldReference fieldsRef;
fieldsRef =
fieldsRefEnum.get_current();
AccessGrant grant;
grant =
fieldsRef.Grant;
str grantRead = enum2Str(grant.Read);
str grantUpdate = enum2Str(grant.Update);
str grantCreate = enum2Str(grant.Create);
str grantDelete = enum2Str(grant.Delete);
str grantCorrect= enum2Str(grant.Correct);
info(strFmt('Table %1 Field %2', table.Name, fieldsRef.Name));
Info(strFmt('Read %1 Update %2 Create %3
Delete %4 Correct %5',
grantRead,
grantUpdate,
grantCreate,
grantDelete,
grantCorrect));
}
}
}
}
}
void expandPrivilege(AxSecurityPrivilege _axSecPrivilege)
{
Microsoft.Dynamics.AX.Metadata.Core.MetaModel.UserLicenseType
maintainLicense;
Microsoft.Dynamics.AX.Metadata.Core.MetaModel.UserLicenseType
viewLicense;
AxSecurityEntryPointReference entryPoint;
MenuFunction menuFunction;
AccessGrant grant;
info(strFmt("Privilege %1", _axSecPrivilege.Name));
info("--Privilege
EntryPoints--");
var privilegeEntryPoints =
_axSecPrivilege.EntryPoints;
System.Collections.IEnumerator
dotNetEnumerator = privilegeEntryPoints.GetEnumerator();
while (dotNetEnumerator.MoveNext())
{
entryPoint = dotNetEnumerator.get_Current();
switch (entryPoint.ObjectType)
{
case
Microsoft.Dynamics.AX.Metadata.Core.MetaModel.EntryPointType::MenuItemDisplay :
menuFunction
= new MenuFunction(entryPoint.ObjectName,
Microsoft.Dynamics.AX.Metadata.Core.MetaModel.MenuItemType::Display);
break;
case
Microsoft.Dynamics.AX.Metadata.Core.MetaModel.EntryPointType::MenuItemOutput :
menuFunction
= new MenuFunction(entryPoint.ObjectName,
Microsoft.Dynamics.AX.Metadata.Core.MetaModel.MenuItemType::Output);
break;
case
Microsoft.Dynamics.AX.Metadata.Core.MetaModel.EntryPointType::MenuItemAction :
menuFunction
= new MenuFunction(entryPoint.ObjectName,
Microsoft.Dynamics.AX.Metadata.Core.MetaModel.MenuItemType::Action);
break;
//other
types can be added here
. . . . . . . . . .
. . . . . . . . . .
}
if (menuFunction)
{
maintainLicense =
menuFunction.maintainUserLicense();
viewLicense = menuFunction.viewUserLicense();
Info(strFmt('Privilege %1 Object %2
MainLicense %3 ViewLicense %4',
_axSecPrivilege.Name,
entryPoint.ObjectName,
enum2Str(maintainLicense),
enum2Str(viewLicense)));
}
grant = entryPoint.Grant;
str grantRead =
enum2Str(grant.Read);
str grantUpdate = enum2Str(grant.Update);
str grantCreate = enum2Str(grant.Create);
str grantDelete = enum2Str(grant.Delete);
str grantCorrect= enum2Str(grant.Correct);
str grantInvoke = enum2Str(grant.Invoke);
Info(strFmt('ObjectType %1 Name %2 ChildName %3',
enum2Str(entryPoint.ObjectType),
entryPoint.ObjectName,
entryPoint.ObjectChildName));
Info(strFmt('Read
%1 Update %2 Create %3 Delete %4 Correct %5',
grantRead,
grantUpdate,
grantCreate,
grantDelete,
grantCorrect));
}
info("--Privilege
DataEntityPermissions--");
var dataEntityPermissions =
_axSecPrivilege.DataEntityPermissions;
System.Collections.IEnumerator
dataEntityEnumerator = dataEntityPermissions.GetEnumerator();
while
(dataEntityEnumerator.MoveNext())
{
AxSecurityDataEntityPermission entityPermision =
dataEntityEnumerator.get_current();
AccessGrant grantEntity;
grantEntity = entityPermision.Grant;
str grantRead = enum2Str(grantEntity.Read);
str grantUpdate =
enum2Str(grantEntity.Update);
str grantCreate =
enum2Str(grantEntity.Create);
str grantDelete =
enum2Str(grantEntity.Delete);
str grantCorrect=
enum2Str(grantEntity.Correct);
info(strFmt('DataEntity
%1',
entityPermision.Name));
Info(strFmt('Read
%1 Update %2 Create %3 Delete %4 Correct %5',
grantRead,
grantUpdate,
grantCreate,
grantDelete,
grantCorrect));
}
//Permissions,
entry points and so on . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
}
public static void main(Args _args)
{
TestClass workClass = new TestClass();
workClass.run();
}
}
Note: It is an example. It covers not all possible security cases. It can be used as a starting point for development.
The result of this code for the D365TestRole will be like this:
--Role privileges--
Privilege AbbreviationsEntityView
--Privilege EntryPoints--
--Privilege DataEntityPermissions--
DataEntity AbbreviationsEntity
Read: Allow Update: Unset Create: Unset Delete: Unset Correct: Unset
--Role Duties--
Duty AccountingDistCustFreeInvoiceMaintain Label Maintain accounting distribution for customer free text invoice duty
Privilege AccountingDistCustFreeInvoiceDocumentMaintain
--Privilege EntryPoints--
Privilege AccountingDistCustFreeInvoiceDocumentMaintain
Object AccountingDistributionsDocumentView
MainLicense Universal ViewLicense Universal
ObjectType MenuItemDisplay Name AccountingDistributionsDocumentView
Read: Allow Update: Allow Create: Allow Delete: Allow Correct: Allow
--Privilege DataEntityPermissions--
--Role Sub Roles--
SubRole AnonymousApplicant Label Applicant anonymous (external)
--Role Permissions--
Table ActualWorkItemEntry Field Currency
Read:Allow Update: Allow Create: Allow Delete: Unset Correct: Allow
Table ActualWorkItemEntry Field Customer
Read: Allow Update: Unset Create: Unset Delete: Unset Correct: Unset