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.
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).
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;
}
}
}