After reading the six posts about the Security Development Tool from André, I got excited about the tool and started using it on a regular basis. It proved to be a powerful tool which cut down development time quite a bit. Almost everything you need for security development is available. But there was one scenario where I missed a functionality in the tool. I was redesigning a few security roles to fit the users need and because the basis was done very poorly I decided to create new roles. Everything worked fine except for 1 tiny little thing that wasn’t picked up by the registration. So what I wanted to do is compare the old role to the new role to figure out what was missing. There is no functionality in the tool for this, and as I’m firmly against using excel, I made it my mission to get this information directly from the tool. In my search I found a ‘dirty and limited’ way to do it and a ‘nicer and more extended’ way to get the job done. I’ll explain them both for a complete picture.

Option 1: ‘Dirty but effective’ way to compare roles in Security Development Tool

If the only security objects you want to compare are Roles then there is a simple but ‘dirty way’ to do it. When you open the ‘Security entry point permissions’ form it will show you the entry point permissions of the System User Role. We could fill this column of the form with a different role to compare it to the role we’re going to edit.

The way to accomplish this is fairly easy. In the AOT open up the form ‘Security entry point permissions’ and under ‘methods’ find the method ‘LoadSystemUserPermissions’. In line 12 you see :

Select firstonly Recid from systemUserRole Where systemUserRole == 'SystemUser';

Simply change ‘SystemUser’ to the AOTname of the desired Role. After a quick compile the rights of the choosen role will be presented in the column ‘System User Role Acces Level’ next to the access level of the role you are developing.

Option 2: ‘Nicer and more extended’ way to compare object in Security Development Tool

The first way is good for developers and gives fast results for when you only need this functionality ones. Now let’s build something lasting, that consultants and end users can use as well. And to make things even more interesting let’s build some filtering options as well.

So the general idea is to build something like this:

Security Development Tool including Comparison and filtering

Security Development Tool including Comparison and filtering

Where you can compare roles/duties and privileges and use filters to easily find the differences between to objects of the same type.

More elaborated what we want to achieve is:

  1. A possibility to add a column with access rights to the grid based on the selection in the ‘Compare’ combobox. The list of the combobox should depend on the selection made in the ‘Type’ combobox, like the list of the ‘Name’ combobox.
  2. A filtering mechanism to quickly identify the differences between the ‘acess level’ column and the newly created ‘Compare Results’ column in the grid.

Part 1 comparison in Security Development Tool

To be able to extend functionality you first need to understand how the existing functionality (technically) works. As mentioned before the behavior of our ‘Compare’ combobox has to look quite similar to the behavior of the ‘Name’ combobox. So let’s see what the flow of the functionality is:

  1. On initiating the form ‘SysSecEntryPointManager’ a ‘in memory’ temp table is being filled with all security objects and their access rights for the ‘SystemUser’.
  2. After choosing a ‘Type’ in the combobox the list of the combox is changed.
  3. After choosing a security object in the ‘Name’ combobox, the code will check if it is a role otherwise (duty or priviledge) create a custom role. And insert the data in the temp table.

Data dictionary

First thing to do is create a column in the forms datasource for storing the access rights associated with the security object we are comparing with.

In the Table ‘SysSecEntryPointTmp’ we have to create a column called ‘CompareUserRight’ based on the Enum ‘AccessRight’. The label property should contain something like ‘Comparison Results’.

(The ‘Name’ object populates the field ‘AccessRight’.)

Creating a new field on the datasource of the Security Development Tool form

Creating a new field on the datasource of the Security Development Tool form

Put the field in the ‘DataManager’ group to make it automatically available on the form.

 

DesignFunctionality change

Let us first set a few variables in the ClassDeclaration that we are going to need throughout the next steps:

In the classDeclaration insert the following at line 65

//BWAL Security Development Tool Addon: compare User rights
    Map                             compareUserAccessRightsMap;
    SecurityRoleAotName             compareRoleAOTName;
    recid                           compareRoleID;
    IdentifierName                  compareDevelopmentObject;
    SecurityRoleName                compareRoleName;
//END BWAL Security Development Tool Addon: compare User rights

Create a method called ‘CompareDevelopmentObject’ like this in the root of the form to be able to use it later on:

//BWAL Security Development Tool Addon: compare User rights
public IdentifierName CompareDevelopmentObject()
{
    return compareDevelopmentObject;
}
//END BWAL Security Development Tool Addon: compare User rights

In the form ‘SysSecEntryPointManager’ in the design node create a new group beneath ‘Group:Settings’ and call this one ‘Compare’.

Create a new group for Security Development Tool Comparison

Create a new group for Security Development Tool Comparison

Set its properties as:

Width = Column Width
RightMargin = 2
ColumnSpace = 2

Let’s create a combobox similar to the ‘Name’ combobox including it’s methods.

In the group ‘Compare’ we create a stringEdit called ‘Compareobject’ with the following properties:

AutoDeclaration = yes 

On this stringEdit we create 2 methods:

First:

//BWAL Security Development Tool Addon: modified method compareObject control.
public boolean modified()
{
    boolean ret;

    ret = super();

    element.SelectObject();
    return ret;
}
//END BWAL Security Development Tool Addon: modified method compareObject control.

What does this do? If you choose a security object it will then insert or update the access rights in the temp table.
Second:

//BWAL Security Development Tool Addon: Lookup form for compareObject control.
public void lookup()
{
    str lookupForm;
    SysSecDevelopmentType selectedType = ObjectType.selection();
    FormRun formRun;
    Args args;

    switch(selectedType)
    {
        case SysSecDevelopmentType::Role:
            lookupForm = formStr(SysSecRoleLookup);
            break;
        case SysSecDevelopmentType::Duty:
            lookupForm = formStr(SysSecDutyLookup);
            break;
        case SysSecDevelopmentType::Privilege:
            lookupForm = formStr(SysSecPrivilegeLookup);
            break;
    }

    if(lookupForm != '')
    {
        args = new Args(lookupForm);
        args.caller(this);
        formRun = classfactory.formRunClass(args);
        formRun.init();
        this.performFormLookup(formRun);
    }
}
//END BWAL Security Development Tool Addon: Lookup form for compareObject control.

What does it do? This method changes the list of the combobox making it dependable on the selection in the ‘Type’ combobox.

To make sure that when you choose a different type the ‘Compare’ combobox selection is deleted insert the following code:
In the ‘modified’ method of the ‘Combox:ObjectType’ at line 8 insert:

//BWAL Security Development Tool Addon: Clear CompareObject
    CompareObject.text('');
//END BWAL Security Development Tool Addon: Clear CompareObject

Next thing to do is change properties on the previously created column available in the grid. To do this go to ‘MaingroupRightGroupEntryPointsGridEntryPointsGrid_CompareUserRight’. The properties should look like this:

AutoDeclaration = Yes
Visible = No

To make the contextmenu visible on this column as it is on other columns create a method on this column like this:

//BWAL Security Development Tool Addon: compare User rights
public int showContextMenu(int _menuHandle)
{
    return EntryPointsGrid.showContextMenu(_menuHandle);
}
//END BWAL Security Development Tool Addon: compare User rights

The earlier created ‘modified’ method on the ‘Compare’ combobox calls the ‘SelectObject’ method of the form.
When looking at this method we see that it needs some modifications like:
insert at line 15(after: element.MapDutyOrPrivilegeToRole();)

//BWAL Security Development Tool Addon: compare User rights
        if(CompareObject.text() != '')
        {
            element.MapCompareDutyOrPrivilegeToRole();
        }
//END BWAL Security Development Tool Addon: compare User rights

What does it do? This is a call to a method that we are going to create. That method will hold the code that creates a role from a duty or privilege.

And insert at line 38 (after: else
{
error(strFmt(“@SDT107”, CurrentObject.text()));
}
}):

//BWAL Security Development Tool Addon: compare User rights
        if(CompareObject.text() != '')
        {
            //Verify role exists
            select firstOnly recid, Name, AOTName from role where role.Name == CompareObject.text();
            if(role.RecId != 0)
            {
                compareRoleID = role.RecId;
                compareRoleAOTName = role.AotName;
            }
            else
            {
                error(strFmt("@SDT107", CompareObject.text()));
            }
        }
//BWAL Security Development Tool Addon: compare User rights

What does it do? The actual insert/update of data in the temp table.
Now let’s create the method we’re calling for in the ‘SelectObject’ method. Create it under ‘methods’ in the root of the form.
Create a method called ‘MapCompareDutyOrPrivilegeToRole’:

//BWAL Security Development Tool Addon: compare User rights
public void MapCompareDutyOrPrivilegeToRole()
{
    TreeNode devRole = SysSecEntryPointManager::FindOrCreateUniqueRoleForCompareUser();
    SecurityRole securityRole;
    TreeNode childNode;
    TreeNodeIterator tIterator;
    str typeToAdd;
    SecurityTask task;

    if(!SysSecEntryPointManager::ObjectEditableVCS(devRole))
    {
        error(strFmt("@SDT89", devRole.AOTname()));
        return;
    }

    //Remove existing duty/privileges
    tIterator = devRole.AOTfindChild('Duties').AOTiterator();
    childNode = tIterator.next();
    while(childNode)
    {
        childNode.AOTdelete();
        childNode = tIterator.next();
    }

    tIterator = devRole.AOTfindChild('Privileges').AOTiterator();
    childNode = tIterator.next();
    while(childNode)
    {
        childNode.AOTdelete();
        childNode = tIterator.next();
    }

    if(CompareObject.text() != '')
    {
        //Add duty/privilege
        typeToAdd = 'Privileges';
        if(element.CurrentDevelopmentType() == SysSecDevelopmentType::Duty)
        {
            typeToAdd = 'Duties';
            select firstOnly AOTName from task where task.Name == CompareObject.text() && task.Type == SecurityTaskType::Duty;
        }
        else
        {
            select firstOnly AOTName from task where task.Name == CompareObject.text() && task.Type == SecurityTaskType::Privilege;
        }

        if(task.AotName == '')
        {
            devRole.AOTrestore();
            error(strFmt("@SDT90", element.CurrentDevelopmentType(), CompareObject.text()));
            return;
        }

        //Validate object exists
        if(TreeNode::findNode(strFmt('%1\%2', #SecurityPath, typeToAdd)).AOTfindChild(task.AotName) == null)
        {
            devRole.AOTrestore();
            error(strFmt("@SDT91", CompareObject.text()));
            return;
        }

        compareDevelopmentObject = task.AotName;
        compareRoleAOTName = devRole.AOTname();
        compareRoleName = devRole.AOTgetProperty(#PropertyLabel);

        select RecId from securityRole where securityRole.AotName == compareRoleAOTName;
        compareRoleId = securityRole.RecId;

        childNode = devRole.AOTfindChild(typeToAdd).AOTadd(task.AotName);
        childNode.AOTsetProperty('Name', element.CompareDevelopmentObject());
    }

    //Avoid current form loosing focus due to compiler output
    WinAPI::enableWindow(infolog.hWnd(), false);
    try
    {
        devRole.AOTsave();
    }
    catch
    {
        WinAPI::enableWindow(infolog.hWnd(), true);
        throw Exception::Error;
    }
    WinAPI::enableWindow(infolog.hWnd(), true);
}
//END BWAL Security Development Tool Addon: compare User rights

Great the first line of this code is calling a method that doesn’t exist on the class ‘SysSecEntryPointManager’… Yes I know, Let’s create it then.
Have a look at the ‘FindOrCreateUniqueRoleForCurrentUser’ it creates a Role in the AOT for the security object chosen on the form. We have to do almost the same for the ‘CompareObject’ we just created on the form. So let’s copy it and rename it to ‘FindOrCreateUniqueRoleForCompareUser’. (I know this isn’t best practice not even good practice but I didn’t want to change the parameters of the method already used because then I had to replace more code of the Security Development Tool and had to explain it which would be beside the point of this blogpost).
Only thing we need to change is in line 6 we replace ‘%1%2’ with ‘%1%2Compare’ to make it look like this:

str roleAOTName = strfmt('%1%2Compare',#SecGeneratedRoleAOTNamePrefix,CurUserId());

Now a bit more difficult to find but we need to change the ‘SetPermissions’ method as well. Because :

  1. In the ‘SelectObject’ method we call the ‘LoadPermissions’ method which calls the ‘SetPermissions’ method.
  2. The ‘Refresh’ button calls it.
In method ‘SetPermissions’ insert at line 38 (after: element.MapDutyOrPrivilegeToRole();):
//BWAL Security Development Tool Addon: compare User rights
            if(CompareObject.text() != '')
            {
                compareDevelopmentObject = '';
                compareRoleName = '';
                compareRoleAOTName = '';
                element.MapCompareDutyOrPrivilegeToRole();
            }
//END BWAL Security Development Tool Addon: compare User rights

And at line 94 (after: EntryPointsGrid_AccessRight.visible((currentRoleAOTName != ”));):

//BWAL Security Development Tool Addon: compare User rights
        EntryPointsGrid_CompareUserRight.visible((compareRoleAOTName != ''));
//END BWAL Security Development Tool Addon: compare User rights

This line toggles the columns visibility.
The ‘SetPermissions’ method in it’s turn calls the method ‘LoadEntryPointPermissions’ which we also have to alter:
Insert in method ‘LoadEntryPointPermissions’ at line 116 (before: //Update permissions that no longer exist and so are now NoAccess
):

//BWAL Security Development Tool Addon: compare User rights
    entryPointPermissions = SysSecEntryPointManager::GetEntryPointPermissionsForRole(compareRoleID);
    for(i = 1; i <= conLen(entryPointPermissions); i = i+2)
    {
        objectKey = conPeek(entryPointPermissions, i);
        accessRightVar = conPeek(entryPointPermissions, i+1);

        update_recordSet SysSecEntryPointTmp setting compareUserRight = accessRightVar
            where SysSecEntryPointTmp.ObjectKey == objectKey;
    }
//END BWAL Security Development Tool Addon: compare User rights

Part 2 filtering in Security Development Tool

Data Dictionary Change

We first create an enum called ‘SysSecFilterCompare’:

Enum created for Security Development Tool Filtering

Enum created for Security Development Tool Filtering

Change the ‘Style’ property to ‘Radio button’ so we can drop it on the form later. Don’t forget to fill out the label properties as well.

Designfunctionality change

Next we drop the previously create enum ‘SysSecFilterCompare’ into the previously created group ‘Compare’ and rename the enum to ‘FilterCompare’.

Set the following properties accordingly:

Auto Declaration = yes
Columns = 5

On this radiobutton we only need 1 method:

//BWAL Security Development Tool Addon: modified method Fiter control.
public boolean modified()
{
    boolean ret;

    ret = super();

    element.AddCompareFilter();
    SysSecEntryPointTmp_ds.executeQuery();
    return ret;
}
//END BWAL Security Development Tool Addon: modified method Filter control.

What does it do? It calls the functionality to add a filter on the datasource and re-executes the query.

Create a method ‘AddCompareFilter’ to make the filter work, like this:

//BWAL Security Development Tool Addon: Actual Filter code
public void AddCompareFilter()
{
    QueryBuildDataSource    qbds;
    QueryBuildRange         qbr;
    str                     compareFieldStr = fieldStr(SysSecEntryPointTmp,SystemUserRight);
    str                     currentFieldStr = fieldStr(SysSecEntryPointTmp,AccessRight);

    if(CurrentObject.text() != '')
    {
        if(Compareobject.text() != '')
        {
            compareFieldStr = fieldStr(SysSecEntryPointTmp,CompareUserRight);
        }
        qbds = SysSecEntryPointTmp_ds.query().dataSourceTable(tableNum(SysSecEntryPointTmp));
        qbds.clearRange(fieldNum(SysSecEntryPointTmp, DataAreaId);
        qbr = qbds.addRange(fieldNum(SysSecEntryPointTmp, DataAreaId));
        switch(FilterCompare.selection())
        {
            case SysSecFilterCompare::Equal :
                qbr.value(strFmt('(%1 == %2)',currentFieldStr,compareFieldStr));
                break;
            case SysSecFilterCompare::NotEqual :
                qbr.value(strFmt('(%1 != %2)',currentFieldStr,compareFieldStr));
                break;
            case SysSecFilterCompare::More :
                qbr.value(strFmt('(%1 > %2)',currentFieldStr,compareFieldStr));
                break;
            case SysSecFilterCompare::Less :
                qbr.value(strFmt('(%1 < %2)',currentFieldStr,compareFieldStr));
                break;
            default :
                break;
        }
        qbr.status(RangeStatus::Locked);
    }
}
//END BWAL Security Development Tool Addon: Actual Filter Code

What does it do? If there is a value in the ‘Compare’ combobox it will compare the rights in the ‘Compare Results’ column with the ‘Acces Right’ column. The type of comparison is depending on the option selected. If there is no selection in the ‘compare’ combobox then the ‘SystemUser’ rights column is compared to the ‘Access right’ column.

TIP: Use Comparison with filter and recording

You can even use this new feature in combination with the standard filter which is applied after recording security object. So if you want know the difference in access rights between 2 security objects in a certain area of AX. Just record it and use comparison and filtering.

Interesting links:

Installing the Security Development Tool

Security Development Tool Explained by Andre – Part 1

Security Development Tool Explained by Andre – Part 2

Security Development Tool Explained by Andre – Part 3

Security Development Tool Explained by Andre – Part 4

Security Development Tool Explained by Andre – Part 5

Security Development Tool Explained by Andre – Part 6

All code samples, walkthroughs and other instructions are provided as-is. Use any and all information provided in this blog at your own risk. It is never advised to import or write code in a production environment without rigorous testing in a test or development environment.
All opinions expressed in this blog are solely my own and do not necessarily reflect the opinions of my employer.
The names of actual companies and products mentioned in this blog may be the trademarks of their respective owners.
Microsoft and Dynamics AX are registered trademarks of Microsoft Corporation in the United States and other countries.
Comments2

Leave a Comment!

Your email address will not be published. Required fields are marked *