Reverse ‘Parent: Child Business Unit’ access CRM 2011 Plugin

Paul Nieuwelaar, 15 August 2013

In Dynamics CRM 2011, when configuring security roles with business units, we have the ability to give a user the following levels of access to records:

User: Any records they own.
Business Unit: Any records a user or team in their business unit owns.
Parent: Child Business Unit: Any records owned by users or teams in their business unit or ANY business unit that sits below the users business unit.
Organization: Any record in the system, regardless of which business unit the record is owned by.

In most cases, these access levels work fine. The one level that it does not provide is the ability to see records owned by business units that parent the current users business unit.

For example, in the following business unit hierarchy, if we as a user were in the North-Sales business unit, we would need to see records from the North business unit, and also the National business unit, but not records from the North-Marketing or any South business units.

 Reverse Parent Child Business Unit access CRM 2011 Plugin

To simulate this kind of relationship behaviour, we can use a plugin to automatically share records with child business units. The plugin can run on create or assign of user-owned entity, and will automatically share the current record will the default team of all child business units, and any child business units of those.

Following this logic, any record created in the National business unit will automatically be shared with North and Sales. And any record created in the North business unit will automatically be shared with Sales.

A sample of the plugin required to do this can be seen below (the plugin registration steps are defined in the plugin summary).

Also make note of the following limitations if you intend to use this solution:

•  Any teams manually shared with records will be removed when a record is reassigned (can be turned off, but not recommended).

•  When a new Business Unit is created, it will not have access to records in Parent Business Units until those records are reassigned.

•  Any records that exist before this plugin is configured will not be shared until those records are reassigned.

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
using Microsoft.Crm.Sdk.Messages;

namespace Test.Plugins

{
    /// <summary> 
    /// On create or assign of a record, share the record with ALL child business units. 
    /// This allows you to provide 'Child: Parent Business Units' access to any entities you want. 
    /// 
    /// Plugin Registration Steps: 
    /// create - post - sync 
    /// update - post - sync - filtering: owningbusinessunit 
    /// </summary> 
    public class ShareWithChildBUs : IPlugin 
    {
        private IOrganizationService _service;

        public void Execute(IServiceProvider serviceProvider)
        {
            IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
            IOrganizationServiceFactory factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
            _service = factory.CreateOrganizationService(null); // Run in system context as users may have Assign but not Share 

            if (!context.InputParameters.Contains("Target")) { return; }
            Entity target = context.InputParameters["Target"] as Entity;
            if (target == null) { return; }

            if (target.Contains("owningbusinessunit") && (EntityReference)target["owningbusinessunit"] != null)
            {
                Share(target, context);
            }
        }

        private void Share(Entity target, IPluginExecutionContext context)
        {
            EntityReference targetRef = target.ToEntityReference();
            EntityReference owningBU = (EntityReference)target["owningbusinessunit"];

            if (context.MessageName.Equals("update", StringComparison.InvariantCultureIgnoreCase))
            {
                // Unshare from all existing teams (if you don't want all existing teams removed on reassign you can comment this) 
                RevokeAccessFromTeams(targetRef);
            }

            // Share with ALL child business units 
            ShareWithChildBusinessUnits(targetRef, owningBU.Id);
        }

        private void RevokeAccessFromTeams(EntityReference target)
        {
            // Get all the users/teams the record is already shared with 
            var sharedWith = (RetrieveSharedPrincipalsAndAccessResponse)_service.Execute(new RetrieveSharedPrincipalsAndAccessRequest 
            {
                Target = target
            });

            if (sharedWith != null && sharedWith.PrincipalAccesses != null)
            {
                sharedWith.PrincipalAccesses.ToList().ForEach(a =>
                {
                    // Only unshare teams 
                    if (a.Principal.LogicalName.Equals("team", StringComparison.InvariantCultureIgnoreCase))
                   {
                        // Revoke access from the team 
                        _service.Execute(new RevokeAccessRequest 
                        {
                            Target = target,
                            Revokee = a.Principal
                        });
                    }
                });
            }
        }

        private void ShareWithChildBusinessUnits(EntityReference target, Guid businessUnitId)
        {
            // Get child teams and share with them 
            List<Entity> childTeams = GetChildBusinessUnitTeams(businessUnitId);
            childTeams.ForEach(a =>
            {
                Guid childBusinessUnitId = (Guid)((AliasedValue)a["bu.businessunitid"]).Value; // The child business unit 
                EntityReference teamReference = new EntityReference("team", a.Id); // The default team of the child business unit 

                // Grant access for the team 
                _service.Execute(new GrantAccessRequest 
                {
                    Target = target,
                    PrincipalAccess = new PrincipalAccess 
                    {
                        // Only givesa Read and Write access (this can be modified as needed) 
                        AccessMask = AccessRights.ReadAccess | AccessRights.WriteAccess,
                        Principal = teamReference
                    }
                });

                // Call this method for each child business unit to also share with all their children 
                // If you only want users to see one level up, comment this line 
                ShareWithChildBusinessUnits(target, childBusinessUnitId);
            });
        }

        private List<Entity> GetChildBusinessUnitTeams(Guid businessUnitId)
        {
            List<Entity> teams = new List<Entity>();

            EntityCollection collection = _service.RetrieveMultiple(new QueryExpression("team")
            {
                Criteria =
                {
                    Conditions =
                    {
                        // We only want the default team, which contains all users of the business unit 
                        new ConditionExpression("isdefault", ConditionOperator.Equal, true)
                    }
                },
                LinkEntities =
                {
                    new LinkEntity()
                    {
                        LinkFromEntityName = "team",
                        LinkToEntityName = "businessunit",
                        LinkFromAttributeName = "businessunitid",
                        LinkToAttributeName = "businessunitid",
                        EntityAlias = "bu",
                        Columns = new ColumnSet("businessunitid"),
                        LinkCriteria =
                        {
                            Conditions =
                            {
                                new ConditionExpression("parentbusinessunitid", ConditionOperator.Equal, businessUnitId)
                            }
                        }
                    }
                }
            });

            if (collection != null && collection.Entities.Count > 0)
            {
                teams = collection.Entities.ToList();
            }

            return teams;
        }
    }
}