Dynamics 365 SCM and Finance API for getting security metadata

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:

Privilege details:

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

No comments:

How to run batch tasks using the SysOperation framework

Overview As you may know, the system has batch tasks functionality . It can be used to create a chain of operations if you want to set an or...