Dynamics CRM 2011 Share Secured Fields

Dynamics CRM 2011 support field level security for custom attributes. Depending on your solution and security model for that solution, there might be a requirement to share secured fields in plugin, workflow or JavaScript.

This post describe how to use late bound entities to create a custom workflow activity to share secured fields, the complete code is in the end of this post.

 Note: If users’ security role says they cannot update records, then they will not be able to update any field on that record even if there is a field level security profile says that they can update that field on that entity.

Each time when users share a secured field to other users, CRM create records of entity “principalobjectattributeaccess” with proper value set for “readaccess”, “updateaccess”, “attributeid”,”objectid”, and “principalid” attributes.

To create this entity correctly, we need the id of the attribute metadata, so we can use the following code to retrieve that id based on the attribute name


// Create the request
 RetrieveAttributeRequest attributeRequest = new RetrieveAttributeRequest
 {
 EntityLogicalName = entityName,
 LogicalName = attributeName,
 RetrieveAsIfPublished = true
 };

// Execute the request
 RetrieveAttributeResponse attributeResponse = (RetrieveAttributeResponse)service.Execute(attributeRequest);

CRM does not allow duplicate records for “principalobjectattributeaccess” entity, so we need to uses some query like below to check whether there is record exists first.


// Create the query for retrieve User Shared Attribute permissions.
 QueryExpression queryPOAA = new QueryExpression("principalobjectattributeaccess");
 queryPOAA.ColumnSet = new ColumnSet(new string[] { "readaccess", "updateaccess" });
 queryPOAA.Criteria.FilterOperator = LogicalOperator.And;
 queryPOAA.Criteria.Conditions.Add(new ConditionExpression("attributeid", ConditionOperator.Equal, metadataId));
 queryPOAA.Criteria.Conditions.Add(new ConditionExpression("objectid", ConditionOperator.Equal, objectId));
 queryPOAA.Criteria.Conditions.Add(new ConditionExpression("principalid", ConditionOperator.Equal, principalId));

// Execute the query.
 EntityCollection responsePOAA = service.RetrieveMultiple(queryPOAA);

We can now create or update the record to share and update secured field.

Here is the complete custom workflow activity


// <copyright file="ShareSecuredField.cs" company="">
// Copyright (c) 2012 All Rights Reserved
// </copyright>
// <author></author>
// <date>6/5/2012 11:41:45 PM</date>
// <summary>Implements the ShareSecuredField Workflow Activity.</summary>
namespace Xrm.Workflow.Activities
{
 using System;
 using System.Activities;
 using System.ServiceModel;
 using Microsoft.Xrm.Sdk;
 using Microsoft.Xrm.Sdk.Workflow;
 using Microsoft.Xrm.Sdk.Messages;
 using Microsoft.Xrm.Sdk.Query;

public sealed class ShareSecuredField : CodeActivity
 {
 [RequiredArgument]
 [Input("Attribute Name")]
 public InArgument<String> AttributeName { get; set; }

[Input("Share With User")]
 [ReferenceTarget("systemuser")]
 public InArgument<EntityReference> UserToShare { get; set; }

[Input("Share With Team")]
 [ReferenceTarget("team")]
 public InArgument<EntityReference> TeamToShare { get; set; }

[RequiredArgument]
 [Input("Allow Read")]
 [Default("true")]
 public InArgument<Boolean> AllowRead { get; set; }

[RequiredArgument]
 [Input("Allow Update")]
 [Default("true")]
 public InArgument<Boolean> AllowUpdate { get; set; }

/// <summary>
 /// Executes the workflow activity.
 /// </summary>
 /// <param name="executionContext">The execution context.</param>
 protected override void Execute(CodeActivityContext executionContext)
 {
 // Create the tracing service
 ITracingService tracingService = executionContext.GetExtension<ITracingService>();

if (tracingService == null)
 {
 throw new InvalidPluginExecutionException("Failed to retrieve tracing service.");
 }

tracingService.Trace("Entered ShareSecuredField.Execute(), Activity Instance Id: {0}, Workflow Instance Id: {1}", executionContext.ActivityInstanceId, executionContext.WorkflowInstanceId);

// Create the context
 IWorkflowContext context = executionContext.GetExtension<IWorkflowContext>();

if (context == null)
 {
 throw new InvalidPluginExecutionException("Failed to retrieve workflow context.");
 }

tracingService.Trace("ShareSecuredField.Execute(), Correlation Id: {0}, Initiating User: {1}", context.CorrelationId, context.InitiatingUserId);

IOrganizationServiceFactory serviceFactory = executionContext.GetExtension<IOrganizationServiceFactory>();
 IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);

try
 {
 // TODO: Implement your custom Workflow business logic.
 ExecuteCore(executionContext, context, service);
 }
 catch (FaultException<OrganizationServiceFault> e)
 {
 tracingService.Trace("Exception: {0}", e.ToString());

// Handle the exception.
 throw;
 }

tracingService.Trace("Exiting ShareSecuredField.Execute(), Correlation Id: {0}", context.CorrelationId);
 }

private void ExecuteCore(CodeActivityContext executionContext, IWorkflowContext context, IOrganizationService service)
 {
 string entityName = context.PrimaryEntityName;
 Guid entityId = context.PrimaryEntityId;
 string attributeName = this.AttributeName.Get(executionContext);
 EntityReference userToShare = this.UserToShare.Get(executionContext);
 EntityReference teamToShare = this.TeamToShare.Get(executionContext);
 bool allowRead = this.AllowRead.Get(executionContext);
 bool allowUpdate = this.AllowUpdate.Get(executionContext);

if (userToShare != null)
 {
 Guid objectId = userToShare.Id;
 ShareSecuredFieldCore(service, entityName, attributeName, entityId, objectId, allowRead, allowUpdate, false);
 }

if (teamToShare != null)
 {
 Guid objectId = teamToShare.Id;
 ShareSecuredFieldCore(service, entityName, attributeName, entityId, objectId, allowRead, allowUpdate);
 }
 }

private void ShareSecuredFieldCore(IOrganizationService service, string entityName, string attributeName, Guid objectId, Guid principalId, bool allowRead, bool allowUpdate, bool shareWithTeam = true)
 {
 // Create the request
 RetrieveAttributeRequest attributeRequest = new RetrieveAttributeRequest
 {
 EntityLogicalName = entityName,
 LogicalName = attributeName,
 RetrieveAsIfPublished = true
 };

// Execute the request
 RetrieveAttributeResponse attributeResponse = (RetrieveAttributeResponse)service.Execute(attributeRequest);

if (attributeResponse.AttributeMetadata != null && attributeResponse.AttributeMetadata.IsSecured != null && attributeResponse.AttributeMetadata.IsSecured.HasValue && attributeResponse.AttributeMetadata.IsSecured.Value)
 {
 // Create the query for retrieve User Shared Attribute permissions.
 QueryExpression queryPOAA = new QueryExpression("principalobjectattributeaccess");
 queryPOAA.ColumnSet = new ColumnSet(new string[] { "readaccess", "updateaccess" });
 queryPOAA.Criteria.FilterOperator = LogicalOperator.And;
 queryPOAA.Criteria.Conditions.Add(new ConditionExpression("attributeid", ConditionOperator.Equal, attributeResponse.AttributeMetadata.MetadataId));
 queryPOAA.Criteria.Conditions.Add(new ConditionExpression("objectid", ConditionOperator.Equal, objectId));
 queryPOAA.Criteria.Conditions.Add(new ConditionExpression("principalid", ConditionOperator.Equal, principalId));

// Execute the query.
 EntityCollection responsePOAA = service.RetrieveMultiple(queryPOAA);

if (responsePOAA.Entities.Count > 0)
 {
 Entity poaa = responsePOAA.Entities[0];

if (allowRead || allowUpdate)
 {
 poaa["readaccess"] = allowRead;
 poaa["updateaccess"] = allowUpdate;

service.Update(poaa);
 }
 else
 {
 service.Delete("principalobjectattributeaccess", poaa.Id);
 }
 }
 else
 {
 if (allowRead || allowUpdate)
 {
 // Create POAA entity for user
 Entity poaa = new Entity("principalobjectattributeaccess");
 poaa["attributeid"] = attributeResponse.AttributeMetadata.MetadataId;
 poaa["objectid"] = new EntityReference(entityName, objectId);
 poaa["readaccess"] = allowRead;
 poaa["updateaccess"] = allowUpdate;
 if (shareWithTeam)
 {
 poaa["principalid"] = new EntityReference("team", principalId);
 }
 else
 {
 poaa["principalid"] = new EntityReference("systemuser", principalId);
 }

service.Create(poaa);
 }
 }
 }
 }
 }
}

There are sample code in the following location in the SDK download:

SampleCode\CS\FieldSecurity\RetrieveUserSharedAttributePermissions.cs

SampleCode\VB\FieldSecurity\RetrieveUserSharedAttributePermissions.vb

Advertisements

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 )

Twitter picture

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

Facebook photo

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

Google+ photo

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

Connecting to %s