Unbound Actions: How to share and unshare D365 records using GrantAccess and RevokeAccess


I recently had the need to automatically share and un-share records in Dynamics 365 - records stored in the Microsoft Dataverse. There are many reasons for this and many ways in which you might carry this out. For simple requirements, you might be able to get away with a classic workflow using Demian Raschkovan's Dynamics 365 Workflow Tools. However, this may not suit your needs if you have a more complex scenario. For example, I wanted to share Opportunity records with a custom access team that's related directly to the Owner's Business Unit (different team for every BU). So I turned to Power Automate...

We can accomplish this using a couple of unbound actions available in the Common Data Service (current environment) connector (still yet to be renamed to Dataverse...).  I won't go deep in bound and unbound actions in this post, but here's a few key ideas: 

  • The Common Data Service (current environment) connector is only available within solutions.
  • Bound actions are tied to a specific entity.
  • Unbound actions are not tied to a specific entity.
  • Unbound actions will often require a bit more complex formatting not often used by us non-developers. 
  • There's very little documentation on any of these actions. If there is any, it's hidden away in the developer areas of the Dynamics 365 documentation. 

The unbound actions we're interested in today are GrantAccess and RevokeAccess. Let's walk through the steps to use each of these actions in a flow to share an Opportunity record using Power Automate.


GrantAccess

Open the Power Apps Maker Portal, create a solution, and create a flow from within the solution. Select the connector, Common Data Service (current environment).

Select the Common Data Service (current environment) connector

Select the desired trigger. For my example, I'm using "When a record is created, updated or deleted". I created a field/column on which I want to trigger this flow, so I will add this field/column to the Filtering attributes section. 

Select a trigger

Add an action, and select "Perform an unbound action" from the available actions in the Common Data Service (current environment) connector. Wait a second for the options to load, and you can now search for GrantAccess

Select unbound action, GrantAccess

As I mentioned earlier in this post, we don't get much help with these unbound actions, and they're not at all intuitive. Here we see two arguments being requested: Target and PrincipalAccess.

GrantAccess parameters
When I got to this step, I was entirely lost. A quick search on Google showed nothing helpful anywhere in Microsoft documentation. However, I was saved by Aaron Richard's post for some help on the formatting needed here. 

Target

In the Target parameter, you simply need to reference the record of interest using proper Odata notation. Rather than simply providing a GUID, you must also specify the entity type using the pluralized logical name: pluralizedlogicalname(GUID). An example is opportunities(628cf01a-aed1-e411-80ef-c4346bac7be8), although you will most likely populate the GUID using dynamic content. 

Note - I found using the OData Id from dynamic content here will not work for this action as it typically does when setting lookup fields with the CDS (current environment) connector. 

GrantAccess with Target and PrincipalAccess

PrincipalAccess

For the PrincipalAccess parameter, we must pass the needed data in JSON format, as in the below images.  An important thing to note here is that the "@/at" symbol acts as an escape character when typed directly. You can type this symbol twice ("@@"), but it resolves to a single character upon save and you must re-type the second symbol every time you wish to save. Alternatively, you can create a variable for the symbol that you can use whenever needed. I've found the variable makes things simplest when working with JSONs in these unbound actions (yes, you'll need to do creates similar JSONs to use most other unbound actions ). 

I'd also encourage you to use dynamic content to populate the team or user GUID to make your flow as dynamic as possible. 

Initialize and define a variable for the "@" symbolWorkaround variable for "@/at" escape character

Compose PrincipalAccess JSON for sharing to a userCompose step for PrincipalAccess JSON, user

Compose PrincipalAccess JSON for sharing to a teamCompose step for PrincipalAccess JSON, team

You might be wondering how to know what to type as the AccessMask. Well, for simple scenarios you can either type out the access to provide as text or the equivalent numeral. Available masks can be found in this Docs article and are listed below.

AccessMask:

  • None
  • ReadAccess
  • WriteAccess
  • AppendAccess
  • AppendToAccess
  • CreateAccess
  • DeleteAccess
  • ShareAccess
  • AssignAccess

Note - Using the AccessMask of "None" will not revoke access; it will simply make no change to the access of that user/team. You must use RevokeAccess (below) in order to remove access for a user. 

Multiple Permissions

In order to combine multiple permissions, you can separate the access masks by a comma without a space. The below code snippets will provide all possible access to either a user or a team. Copy and paste to use for your needs by removing the access masks that are not relevant. The first can be used is providing access to a user, or the second can be used if providing access to a team. 

{"Principal": {
"systemuserid": "GUID",
"@@odata.type": "Microsoft.Dynamics.CRM.systemuser"
},
"AccessMask": "ReadAccess,WriteAccess,AppendAccess,AppendToAccess,CreateAccess,DeleteAccess,ShareAccess,AssignAccess"}
{"Principal": {
"teamid": "GUID",
"@@odata.type": "Microsoft.Dynamics.CRM.team"
},
"AccessMask": "ReadAccess,WriteAccess,AppendAccess,AppendToAccess,CreateAccess,DeleteAccess,ShareAccess,AssignAccess"}

Side note - I originally went about providing multiple permissions in a much more complicated way. I tried a few methods including commas but with spaces, which does not work. However, I discovered each combination of AccessMaks has a specific numeric representation that you can find by using the developer tools and reviewing the request payload of a share request. Inside, you can see a value provided in the <accessrights> element. This is entirely unnecessary, though, and I thank EY Kalman for suggesting the use of commas without spaces. This way is far simpler and easier to understand. 


RevokeAccess

Now that you know how to use GrantAccess to share a record with a user, you might run into a scenario where you need to remove access from a user. To do this, we can use a different unbound action, RevokeAccess. This one is quite a bit simpler to use. 

Target - use the same values as in GrantAccess
Revokee - specify the user or team to remove access from with proper odata notation, as referenced earlier in the post

RevokeAccess for user and team

The Next Step

One thing I'd like to do now is to know who has access to a record. This would tell me who I need to put in the Revokee field of RevokeAccess. From some research, I've found a function, RetrieveSharedPrincipalsAndAccess, that does just this. This function returns a collection of teams and users who have access to this record. I'm not a developer, though, and this function is only available through the Web API from what I can see.

Perhaps we can get this added as an unbound action in the future? This would take security handling through flows to the next level. 


Common Errors

I've spent a large part of my time in unbound actions debugging error messages. I've compiled a few of the most common error messages you might encounter to help you in building a successful flow.

Incorrect syntax in JSON

These errors are most likely from the "@" escape character, a missing/misplaced comma, or a typo. Be sure to double check all punctuation and use either a double "@@" or a variable for "@". Copy my code above to avoid these issues. 

  • "An error occurred while validating input parameters: Microsoft.OData.ODataException: Does not support untyped value in non-open type."
  • "The power flow's logic app flow template was invalid. Unable to parse template language expression 'odata.type'..."
  • "Fix invalid expression(s) for the input parameter(s)..."

Improper reference of an entity. 

Be sure to use the pluralized logical name for the entity when referencing the Target. A simple GUID or misspelled entity reference will result in errors like the below. E.g. "opportunities(GUID)"

  • "Resource not found for the segment <segmentname>"


Helpful Links

Comments

  1. Is the syntax for ModifyAccess essentially equivalent to GrantAccess? I have users with read write access that I want to modify to read only access.

    ReplyDelete
    Replies
    1. Yes, I believe you can use the exact same syntax for ModifyAccess as you would for GrantAccess. I have not tested this one, but my understanding is that by providing ReadAccess in the AccessMask for the ModifyAccess action, you will remove the write privileges the users currently have.

      Delete
  2. Thank you Chad! Worked like a charm.

    ReplyDelete
  3. Hi Chad, thanks for the clear explanation. Do you have any idea how to dynamically set the systemuserid? Since I can only easily fetch the Azure Object ID within Powerapps.

    Kind regards,

    Felix

    ReplyDelete
    Replies
    1. Hi Chad, never mind, I found the ID in the user table!

      Thanks again,

      Felix

      Delete
    2. Excellent! Yes, in Power Automate you just have to find the proper user or team record to get the needed GUID.

      I'm glad this helped you!

      Delete
  4. Great article that covers all the details. I spent hours researching this before and the syntax really was a pain to get right. That @ symbol threw me for a loop. Thanks for putting it all together.

    ReplyDelete
    Replies
    1. I'm glad I could help you get that right. It's frustrating how little things like that just aren't included in documentation from Microsoft.

      Delete
  5. Hi Chad,
    Is there a way to share to multiple users or teams at one time in the compose action?

    ReplyDelete
    Replies
    1. I don't know of a way to share with multiple user/teams with a single request. However, I'd recommend looping through them in an Apply to Each. If it's a manual list, you can create your own array variable with these values and feed them into the compose step within a loop.

      Delete
  6. Thank you so much for this detailed explanation. You healed one headache ('Invalid expression(s) for the input parameters' == pesky @-sign!!!) and saved another (Commas w/o spaces!! The nerve!).

    The Retrieve*Access* functions are WebAPI Bound Functions and absolutely should be available through PowerAutomate! Except that it may have something to do with their asynchronous nature...?
    I hope you have since found you can List rows from 'principalobjectaccessset' (plural name for table 'principalobjectaccess')

    ReplyDelete

Post a Comment

Popular Posts