The Role Folk Blues: Using Custom Microsoft Dynamics NAV Permission Sets to Lock Down Special Activities
Sometimes, my phone rings, and one of my customers has a specific type of request. I’ve heard this request multiple times throughout my long and storied career of development, and I’ll share the solution with you, so you can do it, too.
The request goes like this: The customer needs to have certain activities restricted to certain people, but those activities don’t fit neatly into the standard Microsoft Dynamics NAV (Navision) paradigm of permission sets and the way that they lock down access to tables into just reading, writing, and deleting records. (And if you’re curious about where I come up with blog titles, the Permission Sets used to be called Roles, and I’m a big Cowboy Bebop fan, and the end theme for that show is called “The Real Folk Blues,” so there you go.)
You might have something like, “Only Alice the Sales Manager can delete an order over $20,000” or “Only Bob in customer service can enter a drop ship order.” A lot of developers will hear that request, and then run off to enter code in the OnDelete trigger or in the Drop Ship flag on a Sales Line that’s specifically looking for Alice or Bob’s username, and that works out pretty well—at least, until something happens. Alice goes on vacation, or Bob needs to stay home with his sick kids, and then no one can do that special thing that got locked down, but it needs to happen RIGHT NOW! And so then the poor developer has to rush a change into production that points at a backup user, and they might forget to remove the change when Alice comes back, and so on.
There’s a better answer, and it looks like this. Instead of hard-coding a username into your code, start by adding a new field to the appropriate setup table. For the examples above, you’d probably want Sales & Receivables Setup, but where it goes depends on the action and where it belongs in the organization. You’ll want to relate the new field to the Permission Set table, and call it something that makes sense, like “Large Order Deletion Permission Set” or “Drop Ship Order Entry Permission Set”. (For the first example, I’d also add a “Large Order Deletion Threshold” so that I didn’t have to hard-code the number 20000 into my code. The concept of a “large order” may go up as the company’s fortunes increase—or the company might need to expand to use the Japanese yen as an additional currency, and 20,000 yen is a considerably smaller sum than 20,000 dollars.)
Once you have your new field in place, add a function to a general-purpose codeunit that looks like this:
CheckPermissionSet(CheckUserID : Text;RoleID : Code;CheckForSuper : Boolean) : Boolean IF RoleID = '' THEN BEGIN EXIT(TRUE); END; User.RESET; User.SETRANGE("User Name",USERID); IF NOT User.FINDFIRST THEN BEGIN EXIT(FALSE); END; AccessControl.RESET; AccessControl.SETRANGE("User Security ID",User."User Security ID"); IF CheckForSuper THEN BEGIN AccessControl.SETFILTER("Role ID",'SUPER|%1',RoleID); END ELSE BEGIN AccessControl.SETRANGE("Role ID",RoleID); END; AccessControl.SETFILTER("Company Name",'''''|' + COMPANYNAME); IF AccessControl.FINDFIRST THEN BEGIN EXIT(TRUE); END; EXIT(FALSE);
There are two local variables in that function: AccessControl, which is a Record with a Subtype of Access Control; and User, which is a Record with a Subtype of User. Note that if you’re in a scenario where you have users from multiple domains, you’ll need to do some extra coding to handle them, since USERID doesn’t give you the domain name. That should be pretty unusual, though; I’ve been using some variation on this function for nearly 10 years, and I’ve only seen that once. The function should also return a Boolean value. I intentionally do not have it throwing up any error messages, because what happens to a user without the right permission is generally dependent on the context of the action—so you’ll need to handle that.
The filter on AccessControl for two empty quotes or COMPANYNAME lets you give the permission to a user in just one company, or across every company, just like other NAV permissions. I’ve also hard-coded the SUPER role into the search; you can add SUPER (DATA) if your users with that permission generally need access to these functions. (I suppose it is hard-coded, and generally hard-coding things is bad, but I figure that they’re probably not going to change the name of the SUPER role often enough to justify making that one dynamic. You can if you’re really adamantly against hard-coding, though.)
If you’re working with a version of NAV before 2013, you’ll have to made some adjustments to deal with the fact that the table names are a little different—the Access Control table was called Windows Access Control, for example, and there are some other adjustments you’ll have to make if you’re using database users instead of Windows authentication for your logins. Still, you should be able to make it work with just a little bit of tweaking.
Once you’ve put both the new setup table field and the new function to check for it into place, add some code in the appropriate trigger/codeunit/wherever to do whatever it is you need to do.
There are some out-of-the-box solutions for NAV that have some overlap with this, but they tend to be considerably more effort to implement (although they are generally more robust). For example, the document approval system has some stuff that goes beyond this, as does the Easy Security add-on.
Happy coding! Leave any weird stuff you find in the Comments section.