Checking Security Privileges or Duties of a User in X++

 

Security is a big part of D365FO and in this article we will go over some particulars of checking user privileges/duties, which might not be immediately obvious.

Usually security is taken care of, for the most part, by the security framework, which is utilized by creating privileges, duties and roles and assigning them to UI artifacts, for example menu items.

All of this is more or less straightforward and you can learn more about role-based security from Microsoft documentation on the topic.

But what if we want to create a new functionality that needs to check whether a user has a privilege or duty in X++?

It may seem simple, but there is a pitfall to avoid in order to get the expected results. Before that we need to first look at how users are added to roles and how it all works in the background.

An examination of assigning users to roles

The two most common ways of adding users to roles is either via the Users form or through the Assign users to roles form.

The Users form:

The Assign users to roles form:

One key difference to note here is that, from the Users form you only have the option to assign roles and to remove them, while on the Assign users to roles form you have the additional option to exclude a user from a role.

In the background both of these forms write data to the database which can be read from these security related tables:

For example, here we have added the Test user to the security role Accountant and this is how it looks in the SecurityUserRole table:

And in the exploded graph tables we can see all the duties/privileges that are assigned to the roles:

   

A thing to note here is that in the SecurityRoleDutyExplodedGraph table all of the privileges are shown for a role, even if they’re granted via a duty and not directly assigned to the role.

For example in the above screenshots we can see the Generate customer account statement report privilege on the Accountant role, even though it is granted via the Inquire into collections status duty.

We can confirm this on the Security configuration form:

The SecurityDutySecurityPrivilege and SecurityRole tables contain DescriptionsIdentifiers (AOT names) and names (en-us labels) of all duties/privileges/roles in the system. For example, here we can see the Generate customer account statement report privilege in the SecurityPrivilege table:

Checking if a user has a privilege/duty in code

Why was all of this important? Well, let’s say for example that we want to create a method that checks, in code, whether a user has a privilege.

After understanding how the background tables work, we can try to do something like this:

This code looks like it will be sufficient for what we want, right? Well, here’s the part that can be easily overlooked.

Remember how on the Assign users to roles form there’s an option to exclude a user from a role, in addition to the Remove option? Let’s try both of these options on our Test user and the Accountant role.

First let’s try to remove the user from the role:

After removing the Test user from the role, we can no longer see them on the form:

Checking the SecurityUserRole table, we can see that the Test user no longer has the Accountant role:

Good, that means that our code will work in that case, let’s now try out the other option we have, which is to “exclude” a user from a role:

   

After excluding the Test user from the role, we can again no longer see them in the form:

That means that when we run our code, it should return false for the Generate customer account statement report privilege, as the Test user no longer has the Accountant role. Well, let’s see how it looks in the SecurityUserRole table:

Here we can see that the Test user is still associated with the Accountant role, but now the AssignmentStatus field is set to Disabled.

This means that our code in this instance would return the wrong value, because we didn’t take into account that a user can still “have” a role, but just not be “assigned” to it.

Luckily, once we know about this, we can easily incorporate this into our code, with just adding a line that checks the AssignmentStatus field:

Now that we’re looking more closely at the SecurityUserRole table, we can also see that there are values for the ValidFrom and ValidTo dates.

These values seem to be legacy code, can’t be entered through the UI and do not seem to be checked by the security framework, that is to say, even if a user is “assigned” to a role outside of the valid dates, they will still have all the privileges associated with that role.

But some standard code still checks these values, for example the getUsersForRole() method of the SecurityRoleUtil class:

So if we want to be in line with MS code, we should also incorporate these checks into our method:

So, now that we know about the pitfalls, our finished code should look something like this:

Comments