Sunday, November 10, 2019

D365/AX7 – How to Implement Security Policy – Context Type Context String

For a quick introduction to the security policies, please check the blog on How to Implement Security Policy – Context Type Role Name.

Problem

For some security requirements, the security policy using Context type – Role property can be used if a context is used to determine whether the policy should be applied. Whenever it is required this context string needs to be set by the application using the XDS::setXDSContext.

Solution

Let’s have a quick demonstration on how security policy can be implemented using ContextString. Consider you have to restrict project manager to view the purchase requisitions of the project which is managed by him. But making sure that user is not granted the access on all purchase requisitions at the same time and policy is applied while opening the project purchase requisitions form.

Step#1
Create a temporary table to store the record Ids of the table records which need to be restricted for the user.

Override the Xds method on table to insert the records in temporary table which should be allowed for user to view. Here we determine the viewable record set based on the permission provided to the user on the specified menu items for the same form.


public RefreshFrequency xds()
    {
        Unchecked(Uncheck::XDS)
        {
            Set resultSet = new Set(Types::Int64);

            if (HcmSecurity::allowAllRowsByMenuItem(AccessRight::View, SecurableType::MenuItemDisplay, menuItemDisplayStr(MY_ProjManagerPurchReqTable)))
            {
                this.insertFromPurchReqQueries(queryStr(MY_ProjManagerPurchReqPolicy), resultSet);
            }
            else if (HcmSecurity::allowAllRowsByMenuItem(AccessRight::View, SecurableType::MenuItemDisplay, menuItemDisplayStr(ProjPurchReqTable)))
            {
                this.insertFromPurchReqQueries(queryStr(ProjPurchReqCue), resultSet);
            }

            var enum = resultSet.getEnumerator();

            while (enum.moveNext())
            {
                MY_PurchReqTableXdsTmp purchReqTableXdsTmp;
                purchReqTableXdsTmp.PurchReqRefRecId = enum.current();
                purchReqTableXdsTmp.insert();
            }
        }

        return RefreshFrequency::PerSession;
    }

   
private void insertFromPurchReqQueries(str QueryName, Set _resultSet)
    {
        Query query = new Query(QueryName);
        QueryRun queryRun = new QueryRun(query);

        while (queryRun.next())
        {
            PurchReqTable purchReq = queryRun.get(tableNum(PurchReqTable));
            _resultSet.add(purchReq.RecId);
        }

    }

Step#2
Create an AOT query to join the table to restrict the records and the temporary table which holds the selective records to provide relevant access to the user as per security policy.



Step#3 Create a security policy and set the properties as below to apply the policy using the context string in application.



Context Type: ContextString
Context String: Any string value which can be used to configure the security policy using security configurations. In this case it is set to MY_PolicyForPurchReqHcmWorkers
Primary table: Select the table from the query as primary table to apply the record level security
Constraint Table: Set to ‘Yes’ if the Primary table should be used to restrict the records.
Operation: Select the operation which should be restricted on primary table using this security policy.
Query: Set the query you created in step 2.


Step#4 Now let’s apply this security based on the required conditions mentioned above. Create a class to handle operations related to required security policy.


public final class MY_PurchReqTableHcmWorkersPolicyHandler
{
    private const str PurchReqHcmWorkerPolicyContextString = 'MY_PolicyForPurchReqHcmWorkers';


    public static void flushPurchReqTableXdsTmp()
    {
        XDSServices services = new XDSServices();
        services.flushXDSMyConstructs(0, tableStr(MY_PurchReqTableXdsTmp));
    }

   
public static str getCurrentXdsContextString(XDSServices _xdsServices)
    {
        return _xdsServices.getXDSContext(0);
    }

   
public static void setCurrentXdsContextString(XDSServices _xdsServices, str _contextString = PurchReqHcmWorkerPolicyContextString)
    {
        _xdsServices.setXDSContext(0, _contextString);       
    }

   
public static void resetCurrentXdsContextString(XDSServices _xdsServices)
    {
        str prevXDSContext = _xdsServices.getXDSContext(0);
        _xdsServices.setXDSContext(0, '');

        if(prevXDSContext != PurchReqHcmWorkerPolicyContextString)
        {
            _xdsServices.setXDSContext(0, prevXDSContext);
        }
    }

    public static boolean enablePurchReqWorkerPolicy(Args _args, boolean _callerCheck = false)
    {
        NoYesId ret = false;

        switch(_args.menuItemName())
        {
            case menuItemDisplayStr(ProjPurchReqTable):
                ret = NoYes::Yes;
                break;

            default:
                if(!_callerCheck)
                {
                    FormRun callerFormRun = _args.caller();
                    if(callerFormRun)
                    {
                        ret = MY_PurchReqTableHcmWorkersPolicyHandler::enablePurchReqWorkerPolicy(callerFormRun.args(), true);
                    }
                }
        }

       
        return ret;
    }

    public static boolean enablePurchReqWorkerPolicyForProjManager(Args _args)
    {
        NoYesId ret = false;

        ret = MY_PurchReqTableHcmWorkersPolicyHandler::enablePurchReqWorkerPolicy(_args);
       
        ret = ret && !HcmSecurity::allowAllRowsByMenuItem(AccessRight::View, SecurableType::MenuItemDisplay, menuItemDisplayStr(PurchReqTableAll));

        return ret;
    }

}


Step#5 Create an event handler class for purchase requisition list page. Implement the event handlers for data source initialized event to reset the context string clearing the security policy effect, and query executed event to apply the context string.

class MY_PurchReqTableListPageSecurityPREventHandler
{
   
 [FormDataSourceEventHandler(formDataSourceStr(PurchReqTableListPage, PurchReqTable), FormDataSourceEventType::Initialized)]
    public static void PurchReqTable_OnInitialized(FormDataSource sender, FormDataSourceEventArgs e)
    {
        MY_PurchReqTableHcmWorkersPolicyHandler::resetCurrentXdsContextString(new XDSServices());
    }

    [FormDataSourceEventHandler(formDataSourceStr(PurchReqTableListPage, PurchReqTable), FormDataSourceEventType::QueryExecuting)]
    public static void PurchReqTable_OnQueryExecuting(FormDataSource sender, FormDataSourceEventArgs e)
    {
        FormRun     formRun = sender.formRun();
        XDSServices services;
        if(MY_PurchReqTableHcmWorkersPolicyHandler::enablePurchReqWorkerPolicyForProjManager(formRun.args()))
        {
            services = new XDSServices();           MY_PurchReqTableHcmWorkersPolicyHandler::setCurrentXdsContextString(services);
        }

    }

}


Build your model/project, synchronize database and the security policy is ready to be applied with the role.

Execution

If the user is not granted the permission to view “All purchase requisitions” and assigned the permission to access menu item MY_ProjManagerPurchReqTable, the user can view only those PRs which are related to the projects managed by him.

To check how to implement security policy using other context types, please check the blog.

Saturday, November 9, 2019

D365/AX7 – How to Implement Security Policy – Context Type Role Property (Context String)

For a quick introduction to the security policies, please check the blog on How to Implement Security Policy – Context Type Role Name

Problem

For some security requirements, the security policy using Context type – Role property can be used if the policy is to be applied only if user is a member of any one of a set of roles that have the context string property set to the same value.

Solution

Let’s have a quick demonstration on how security policy can be implemented using RoleProperty
Consider an example where logged in employee should be able to view only the vendor on vendor list page which is mapped to its employee record.


Step#1

Create an AOT query which returns the set of records which should be displayed to the user conditionally. For the above mentioned scenario, the query would look like below.


Step#2
Create a new security policy and set the properties as below.


Context Type: RoleProperty
Context String: Any string value which can be used to configure the security policy using security configurations. In this case it is set to MY_WorkerVendors
Primary table: Select the table from the query as primary table to apply the record level security
Constraint Table: Set to ‘Yes’ if the Primary table should be used to restrict the records.
Operation: Select the operation which should be restricted on primary table using this security policy.
Query: Set the query you created in step 1.

Build your model/project and the security policy is set to be applied with the role.

Execution

Create a user role and add the privilege to grant access to the vendor list page.  Set the context string property on the Role as MY_WorkerVendors. The context string can be applied to multiple security roles.
When the user login to the application, only the selected vendor is viewable to the user.





To check how to implement security policy using other context types, please check the blog.

D365/AX7 – How to Implement Security Policy – Context Type Role Name

Problem

There are several practical scenarios where we are said to provide users the access to the forms but only allow them to view a set of records based on some conditions. At the same time, other users should be able to view a whole set of records of the data.

Solution

In order to achieve the above mentioned requirement, the security policy comes into action.
The security policy can be created three ways.
·         Context string: Use it if a context is used to determine whether the policy should be applied. Whenever it is required this context string needs to be set by the application using the XDS:SetContextAPI.
·         RoleName: Use it if the policy requires to be implemented only for a user in a specific role that accesses the constrained tables.
·         RoleProperty: Use it if the policy is to be applied only if user is a member of any one of a set of roles that have the context string property set to the same value.

Let’s have a quick demonstration on how security policy can be implemented using RoleName
Consider an example where logged in employee should be able to view only the vendor on vendor list page which is mapped to its employee record.



Step#1
Create an AOT query which returns the set of records which should be displayed to the user conditionally. For the above mentioned scenario, the query would look like below.




Step#2
Create a security role as below.




Step#3
Create a new security policy and set the properties as below.




Context Type: RoleName
Primary table: Select the table from the query as primary table to apply the record level security
Constraint Table: Set to ‘Yes’ if the Primary table should be used to restrict the records.
Operation: Select the operation which should be restricted on primary table using this security policy.
Query: Set the query you created in step 1.
Role Name: Set the role name as you created in step 2.

Build your model/project and synchronize the database (to make your role available on the security configuration form) and the security policy is set to be applied with the role.

Execution

Apply the role to the user and grant the privilege to the vendors list page. When the user login to the application, only the selected vendor is viewable to the user.



To check how to implement security policy using other context types, please check the blog.

D365/AX7 – Passing Multiple Form Datasource References to Class

Problem

We often use the form data sources to get the selected records on form to execute operations within a class. Usually we work with a single data source reference to get the selected records using formDataSource.cursor() or the MultiSelectionHelper class. However, we sometimes come across few scenarios where multiple form data sources have to be passed to the class to get the selected records.

Solution

In order to pass multiple form datasource references to the class, the required datasources must have common parent datasource on form.
Following steps must be followed.

Step#1
Make sure the required form data sources have same join datasource on form. E.g. we need to pass form datasource named MY_CriticalTaskTable and MY_SubTaskTable (as shown below) to the class to get their selected records. Both the datasources have same Join Source i.e. MY_TaskTable




Step#2 Create a new class and add main method with the code below to get the form data source references.

public static void main(Args _args)
    {
        MY_TaskCompleteAction action = new MY_TaskCompleteAction();
        FormRun callerForm = _args.caller();
        FormDataSource subTaskDs, criticalTaskDs;

        subTaskDs       = callerForm.dataHelper().FindDataSource(formDataSourceStr(MY_TasksListDetail, MY_SubTaskTable));
        criticalTaskDs  = callerForm.dataHelper().FindDataSource(formDataSourceStr(MY_TasksListDetail, MY_CriticalTaskTable));

        action.iterateSubTasks(subTaskDs);
        action.iterateSubTasks(criticalTaskDs);
    }

public void iterateSubTasks(FormDataSource _subTaskDs)
    {
        MY_SubTaskTable subTask;
        MultiSelectionHelper helper = MultiSelectionHelper::construct();
        helper.parmDatasource(_subTaskDs);

        subTask = helper.getFirst();

        while(subTask)
        {
            info(subTask.Name);
            subTask = helper.getNext();
        }

    }

Step#3 Create an action menu item for the class and add to the form. Set Datasource property of the menu item on form to the parent datasource of both the datasources i.e. MY_TaskTable in the current scenario.


Conclusion


If parent datasource reference is passed to the action menu item, then the child datasource references can be get within the operating class.