Security challenge
Microsoft CRM 2013 has quite good security system based on security roles and teams. But….
There is always some but. That type of security system is good if:
- We do not have confidential information stored in CRM in those standard entities that are used by all users
- We do not have a lot of collaboration between users from different business units (BU) inside CRM
But, what if :
- We have crucial information stored in common use entities (e.g. activities)
- We have a lot of processes where there is interaction between users from many BU
In theory, a user can manually share records with other users but in real life, no one wants to do that.
Business challenge
In our CRM, we have some confidential information stored in activities (it was very hard to store that information in other entities because there are related directly with activity). Because of that, a typical user (in our case sales rep) can see just his or her activities and activities related to his / her customers (To do that we created a special plugin to share specified entities based on some business logic).
Activates access vs Processes automation
The problem appeared when we started to use workflows where sales rep was creating approval tasks for other users (If we want to create a task or any activity for other users, we need to have a write and read permission to that activity).
How did we solve the problem?
We created a custom activity to share activities inside workflows. By using this custom activity we are able to give any type of permission to any activity created in the workflow.
How does it work?
1. First, we need to create activity and we need to assign it to process owner (in our case it was the case owner).
2. Then we share that activity with process owner by using sharing a custom activity (we need to do that if we want assign activity to someone else).
3. Next step is changing the owner of the activity to required user.
As a result, we get activity with all needed permissions for the owner of the activity and the owner of the process.
Some of you can say that we can get a similar function by using real-time workflows, but in our case we use Wait conditions in WF, and this is something that we cannot use in Real-time WF.
Custom activity sharing challenge
When we create custom activity we need to set specific parameters
In this case, we need to send information:
- Who should have access to activity
- What type of access should be given that user or team
- What type of activity
If you check how many activity and permission types we have in CRM, you will notice that in order to handle all possible variations, you need more than 40 parameters.
To simplify that, we tried to use the activitypointer as a universal activity type parameter.
From the code side, that solution works very well and it is compatible with CRM SDK.
SAMPLE CODE
using System; using System.Activities; using Microsoft.Crm.Sdk.Messages; using Microsoft.Xrm.Sdk; using Microsoft.Xrm.Sdk.Workflow; namespace Workflows.Activity { public class Sharing : CodeActivity { /// <summary> /// Identifier of ActivityPointer to share /// </summary> /// <value>ActivityPointer reference</value> [RequiredArgument] [Input("Activity identifier")] [ReferenceTarget("activitypointer")] public InArgument<EntityReference> ActivityReference { get; set; } /// <summary> /// Identifier of SystemUser to share Activity with /// </summary> /// <value>SystemUser reference</value> [Input("User identifier")] [ReferenceTarget("systemuser")] public InArgument<EntityReference> UserReference { get; set; } /// <summary> /// Identifier of Team to share Activity with /// </summary> /// <value>Team reference</value> [Input("Team identifier")] [ReferenceTarget("team")] public InArgument<EntityReference> TeamReference { get; set; } [Input("Create")] [Default("False")] public InArgument<Boolean> CreatePermission { get; set; } [Input("Read")] [Default("False")] public InArgument<Boolean> ReadPermission { get; set; } [Input("Write")] [Default("False")] public InArgument<Boolean> WritePermission { get; set; } [Input("Delete")] [Default("False")] public InArgument<Boolean> DeletePermission { get; set; } [Input("Append")] [Default("False")] public InArgument<Boolean> AppendPermission { get; set; } [Input("AppendTo")] [Default("False")] public InArgument<Boolean> AppendToPermission { get; set; } [Input("Assign")] [Default("False")] public InArgument<Boolean> AssignPermission { get; set; } [Input("Share")] [Default("False")] public InArgument<Boolean> SharePermission { get; set; } protected override void Execute(CodeActivityContext executionContext) { try { IWorkflowContext context = executionContext.GetExtension<IWorkflowContext>(); IOrganizationServiceFactory serviceFactory = executionContext.GetExtension<IOrganizationServiceFactory>(); IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId); // Introduce variables EntityReference activityPointerReference = this.ActivityReference.Get(executionContext); EntityReference userReference = this.UserReference.Get(executionContext); EntityReference teamReference = this.TeamReference.Get(executionContext); // Check validity conditions if (activityPointerReference == null) { throw new ArgumentNullException(nameof(activityPointerReference)); } if (teamReference == null && userReference == null) { throw new ArgumentException("User and Team references cannot both be null."); } // Initialize rights AccessRights rights = AccessRights.None; // Sharing activity with teams specified in finn settings if (this.CreatePermission.Get(executionContext)) { rights = rights | AccessRights.CreateAccess; } if (this.ReadPermission.Get(executionContext)) { rights = rights | AccessRights.ReadAccess; } if (this.WritePermission.Get(executionContext)) { rights = rights | AccessRights.WriteAccess; } if (this.DeletePermission.Get(executionContext)) { rights = rights | AccessRights.DeleteAccess; } if (this.AppendPermission.Get(executionContext)) { rights = rights | AccessRights.AppendAccess; } if (this.AppendPermission.Get(executionContext)) { rights = rights | AccessRights.AppendToAccess; } if (this.AssignPermission.Get(executionContext)) { rights = rights | AccessRights.AssignAccess; } if (this.SharePermission.Get(executionContext)) { rights = rights | AccessRights.ShareAccess; } if (userReference != null) { service.Execute( new GrantAccessRequest() { // Create new access descriptor PrincipalAccess = new PrincipalAccess() { // Set sharing rights AccessMask = rights, // Set team to share activityPointer with Principal = userReference }, // Set reference to activityPointer entity, which should be shared Target = activityPointerReference }); } if (teamReference != null) { service.Execute( new GrantAccessRequest() { // Create new access descriptor PrincipalAccess = new PrincipalAccess() { // Set sharing rights AccessMask = rights, // Set team to share activityPointer with Principal = teamReference }, // Set reference to activityPointer entity, which should be shared Target = activityPointerReference }); } } catch (Exception exception) { throw new InvalidPluginExecutionException("Exception occured during activity sharing.", exception); } } } }
As a result, we should get custom activity:
One more challenge – Workflow designer does not see the activitypointer value!
Workflow designer is not able to show relations if we do not specify the type of activities (it is probably a bug of CRM 2013 or a missing functionality).
How to do some magic 🙂
To resolve that problem we need to use browser developer tools and update that parameter manually
1. Open browser developer tools (in Chrome F12)
2. Select valueselector field
3. Edit section in html mode
4. Paste Value <option title=”Task” value=”task.CreateStep1.task.activityid”>Task</option> – it is ID of WF element that we need to set as the parameter (You need to adjust it base on CRM syntax).
5. If we do this correctly, the WF window should look like:
6. Accept the new value. It should be visible in Activity identifier value field:
All done 🙂
It is not the cleanest solution but it works and does not generate any errors.
If you have any questions do not hesitate to ask.
Marek Popkowicz is a Microsoft CRM Specialist working in the FINN CRM team at Schibsted Tech Polska. The team located in Krakow works with the Microsoft Dynamics CRM 2013/2016, Microsoft ASP.Net MVC 4/5 and Microsoft SSIS technologies.