Article #4: Mark Case Confidential And Allow Access To A User-Defined Audience

Context

A recurring requirement i face in almost every project that involve an application or a case processing is to make information confidential and only allow access to user-defined audience for each application or case.

This requirement comes when there is a need of restricting information for everyone expect certain users in the organization due to confidential policy or conflict of interest that may occur depending on the context.

Power Platform offers a large panel of options to establish various security models, but sometimes its get tricky to answer complex security requirements.

Overview

In this article, we will discuss the ability to mark a specific case entity record confidential by restricting access to everyone at the company expect for the user marking the case confidential.

The user will also be capable to extend the access to additional members of the organization on demand. We will cover the ability to reverse this confidential marking in order to provide access to everyone as a public access.

This approach uses access team, entity and form configuration, a small plugin and JavaScript code.

Security Configuration

Business Unit

Business units in Power Platform represent deparments or divisions in an organization. It’s an important component to consider while designing your security model.

We will consider in this article that all users that are accessing “public” cases are part of the same business unit Case Management Division.

Lets create a new business unit named Confidential which is either located in the same hierarchy level of the case management division or will be its parent.

By Creating a business unit in Power Platform, the system automatically creates a owner team that has the same name and assign it to the BU. the team Confidential will have no user assigned to it by default and we will keep it as it is without members.

Privileges for the Security Role

Assuming that the users in the Case Management Division are accessing Dynamics 365 Customer Service, they are already assigned to one or many security roles. Insure that the security role or all the cumulated security role privileges are defined at most with the local (Business Unit) privilge for the case entity.

In this article we use the Out Of The Box security role Customer Service representative.

By Setting the privilege to the Local (Business Unit) level, all users part of the Case Management Division and assigned to the Customer Service Representative security role will be able to see cases owned by their users in the same business unit Case Management Division.

Assign your security role also to the owner team Case Management Division even if the team doesn’t have any member assigned to it, this will void having security permission denied error.

Access Team

First, navigate to the case entity in your solution explorer using the make.powerapps.com customization portal, Click on the Settings in the command bar and expend Advanced options.

Check the option Have Access Team. Click on Save.

This manipulation will allow you to create an access team modal for the case entity.

Navigate to advanced settings and go to the security and click on access team model menu item.

Create an access team that provides the following privileges:

You can reduce privileges accordingly to your context, but at least provide the Read privileges.

Click on Save and Close.

Case Entity Configuration

Create a Two Option field Confidential in the entity and add it to the main form used for the case entity.

Change the appearance of the button to be displayed as a toggle.

Add a new section to the form that will hold the sub grid of members that will be added by the user to extend the access privilege.

LabelSchema Name
Audiencesect_audience

In this Post we add the section to the Summary Tab.

Add a sub grid to the Audience section with the following properties (table: Users, default View: Associated Record Team Members). I recommend that you add the sub grid using the old classic customization experience, for some reason, when i did it with the new make experience, the grid shows all users.

Save and publish your form.

JavaScript Control

We will use JavaScript to show or hide the whole audience section depending on the selected value of the toggle confidential.

Create a web resource and add it as a library in your form and register the javascript function checkConfidential in the save and load event of the form.

general” is the schema name of the tab “Summary” and “sect_audience” is the schema name of the “Audience” section we added previously above.

function checkConfidential(executionContext) {
var formContext = executionContext.getFormContext();
if (formContext.getAttribute("tr_confidential").getValue()) 
        formContext.ui.tabs.get("general").sections.get("sect_audience").setVisible(true);
    else 
        formContext.ui.tabs.get("general").sections.get("sect_audience").setVisible(false);
}

Don’t forget to pass the execution context as the first parameter of your function.

Plugin implementation

Using Visual Studio, create a plugin ConfidentialMarking using the following code snippet:

using Microsoft.Crm.Sdk.Messages;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConfidentialMarkingCRMPlugin
{
    public class ConfidentialMarking : IPlugin
    {
        public void Execute(IServiceProvider serviceProvider)
        {
            IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
            IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
            IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);
            Entity caseEntity = context.PostEntityImages["postImage"] as Entity;
            if (caseEntity.GetAttributeValue<bool>("tr_confidential"))
            {
                AddUserToRecordTeamRequest addUserRequest = new AddUserToRecordTeamRequest()
                {
                    Record = caseEntity.ToEntityReference(),
                    SystemUserId = context.UserId,
                    TeamTemplateId = Guid.Parse("28D8EDE8-A2A2-EC11-983F-0022480B1F05"), // replace the empty guid with the guid of your access team model
                };
                service.Execute(addUserRequest);
                caseEntity.Attributes["ownerid"] = new EntityReference("team", Guid.Parse("01a55e80-a6a2-ec11-983f-0022480b1f05")); // replace the empty guid with the guid of your Confidential owner model
                caseEntity.Attributes.Remove("tr_confidential");
                service.Update(caseEntity);
            }
            else
            {
                string fetchXML = @"<fetch>
                             <entity name='incident' >
                               <attribute name='ticketnumber' />
                                   <link-entity name='principalobjectaccess' from='objectid' to='incidentid' link-type='inner' alias='poa' >
                                      <attribute name='objectid' alias='objectid' />
                                      <link-entity name='team' from='teamid' to='principalid' link-type='inner' >
                                        <link-entity name='teamtemplate' from='teamtemplateid' to='teamtemplateid' >
                                          <attribute name='teamtemplatename' />
                                        </link-entity>
                                        <link-entity name='teammembership' from='teamid' to='teamid' link-type='inner' intersect='true' >
                                          <link-entity name='systemuser' from='systemuserid' to='systemuserid' link-type='inner' alias='user'>
                                            <attribute name='systemuserid' />
                                         </link-entity>
                                       </link-entity>
                                      </link-entity>  
                                      <filter type='and'>
                                       <condition attribute='objectid' operator='eq' value='{0}' />
                                      </filter> 
                                    </link-entity>
                                  </entity>
                                </fetch>";
                fetchXML = String.Format(fetchXML, caseEntity.Id);
                EntityCollection accessTeamMembers = service.RetrieveMultiple(new FetchExpression(fetchXML));
                if (accessTeamMembers.Entities.Count > 0)
                {
                    foreach (Entity record in accessTeamMembers.Entities)
                    {
                        RemoveUserFromRecordTeamRequest removeUser = new RemoveUserFromRecordTeamRequest()
                        {
                            Record = new EntityReference(record.LogicalName, record.Id),
                            SystemUserId = (Guid)((AliasedValue)(record["user.systemuserid"])).Value,
                            TeamTemplateId = Guid.Parse("28D8EDE8-A2A2-EC11-983F-0022480B1F05")  // replace the empty guid with the guid of your access team model
                        };
                        service.Execute(removeUser);
                    }
                }
                caseEntity.Attributes["ownerid"] = new EntityReference("systemuser", context.UserId);
                caseEntity.Attributes.Remove("tr_confidential");
                service.Update(caseEntity);
            }
        }
    }
}

Deploy your plugin with the plugin registration tool for which we will create two steps :

MessagePrimary EntityFiltering AttributesStep NameUser ContextEvent Pipeline Stage of executionExecution ModePost Image
CreateincidentN/AMarkConfidential:Create of IncidentCalling UserPost OperationSynchronousName and Entity Alias: postImage
Parameter: tr_confidential
Updateincidenttr_confidentialMarkConfidential:Update of IncidentCalling UserPost OperationSynchronousName and Entity Alias: postImage
Parameter : tr_confidential

You should see something like this on your screen.

The plugin is responsible of assigning the case entity record to the Confidential owner team when the toggle is set to Confidential by the user and add the same user as a member to the access team which is by default empty and does not hold any member. The result of this automation is that no one has access to the case record due to the ownership set to owner team Confidential expect for the initiator of the action which is added as a member in the access team that provide access to the case record.

The reverse action will set the ownership of the record back to the initiator of the action, allowing everyone in the same business unit to access the record again. it also remove all members from the access team to keep everything clean.

Conclusion

Now that everything is setup, lets see how it works. First, a user is opening a case entity record that he owns.

The exhibit shows that the case is not confidential and the owner is the user System Administrator.

When the user mark the case confidential, the owner change automatically to prevent all users in the same business unit to access the case. The audience section appears and we can see that the user is already part of it in the member’s sub grid.

The user is now capable of adding new members by clicking on add members.

This extends the access to the members added to the list.

When user unlocks the confidential toggle, the case ownership is defined back to the initiator of the action, the sub grid audience is cleared and hidden.

The same mechanism can apply to other scenarios and be adjusted to cover the confidentiality requirement.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s